libretro: adjust psxclock description
[pcsx_rearmed.git] / deps / libretro-common / file / archive_file.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.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 <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26
27#include <compat/strl.h>
28#include <file/archive_file.h>
29#include <file/file_path.h>
30#include <streams/file_stream.h>
31#include <retro_miscellaneous.h>
32#include <lists/string_list.h>
33#include <string/stdstring.h>
34
35#ifdef HAVE_MMAP
36#include <fcntl.h>
37#include <errno.h>
38#include <unistd.h>
39#include <sys/mman.h>
40#include <sys/stat.h>
41#endif
42
43static int file_archive_get_file_list_cb(
44 const char *path,
45 const char *valid_exts,
46 const uint8_t *cdata,
47 unsigned cmode,
48 uint32_t csize,
49 uint32_t size,
50 uint32_t checksum,
51 struct archive_extract_userdata *userdata)
52{
53 union string_list_elem_attr attr;
54 attr.i = 0;
55
56 if (valid_exts)
57 {
58 size_t path_len = strlen(path);
59 /* Checks if this entry is a directory or a file. */
60 char last_char = path[path_len - 1];
61 struct string_list ext_list = {0};
62
63 /* Skip if directory. */
64 if (last_char == '/' || last_char == '\\' )
65 return 1;
66
67 string_list_initialize(&ext_list);
68 if (string_split_noalloc(&ext_list, valid_exts, "|"))
69 {
70 const char *file_ext = path_get_extension(path);
71
72 if (!file_ext)
73 {
74 string_list_deinitialize(&ext_list);
75 return 1;
76 }
77
78 if (!string_list_find_elem_prefix(&ext_list, ".", file_ext))
79 {
80 /* keep iterating */
81 string_list_deinitialize(&ext_list);
82 return -1;
83 }
84
85 attr.i = RARCH_COMPRESSED_FILE_IN_ARCHIVE;
86 }
87
88 string_list_deinitialize(&ext_list);
89 }
90
91 return string_list_append(userdata->list, path, attr);
92}
93
94static int file_archive_extract_cb(const char *name, const char *valid_exts,
95 const uint8_t *cdata,
96 unsigned cmode, uint32_t csize, uint32_t size,
97 uint32_t checksum, struct archive_extract_userdata *userdata)
98{
99 const char *ext = path_get_extension(name);
100
101 /* Extract first file that matches our list. */
102 if (ext && string_list_find_elem(userdata->ext, ext))
103 {
104 char new_path[PATH_MAX_LENGTH];
105 const char *delim;
106
107 if ((delim = path_get_archive_delim(userdata->archive_path)))
108 {
109 if (!string_is_equal_noncase(
110 userdata->current_file_path, delim + 1))
111 return 1; /* keep searching for the right file */
112 }
113
114 if (userdata->extraction_directory)
115 fill_pathname_join_special(new_path, userdata->extraction_directory,
116 path_basename(name), sizeof(new_path));
117 else
118 fill_pathname_resolve_relative(new_path, userdata->archive_path,
119 path_basename(name), sizeof(new_path));
120
121 if (file_archive_perform_mode(new_path,
122 valid_exts, cdata, cmode, csize, size,
123 checksum, userdata))
124 {
125 userdata->found_file = true;
126 userdata->first_extracted_file_path = strdup(new_path);
127 }
128
129 return 0;
130 }
131
132 return 1;
133}
134
135static int file_archive_parse_file_init(file_archive_transfer_t *state,
136 const char *file)
137{
138 char path[PATH_MAX_LENGTH];
139 char *last = NULL;
140
141 strlcpy(path, file, sizeof(path));
142
143 if ((last = (char*)path_get_archive_delim(path)))
144 *last = '\0';
145
146 if (!(state->backend = file_archive_get_file_backend(path)))
147 return -1;
148
149 /* Failed to open archive. */
150 if (!(state->archive_file = filestream_open(path,
151 RETRO_VFS_FILE_ACCESS_READ,
152 RETRO_VFS_FILE_ACCESS_HINT_NONE)))
153 return -1;
154
155 state->archive_size = filestream_get_size(state->archive_file);
156
157#ifdef HAVE_MMAP
158 if (state->archive_size <= (256*1024*1024))
159 {
160 state->archive_mmap_fd = open(path, O_RDONLY);
161 if (state->archive_mmap_fd)
162 {
163 state->archive_mmap_data = (uint8_t*)mmap(NULL, (size_t)state->archive_size,
164 PROT_READ, MAP_SHARED, state->archive_mmap_fd, 0);
165
166 if (state->archive_mmap_data == (uint8_t*)MAP_FAILED)
167 {
168 close(state->archive_mmap_fd);
169 state->archive_mmap_fd = 0;
170 state->archive_mmap_data = NULL;
171 }
172 }
173 }
174#endif
175
176 state->step_current = 0;
177 state->step_total = 0;
178
179 return state->backend->archive_parse_file_init(state, path);
180}
181
182/**
183 * file_archive_decompress_data_to_file:
184 * @path : filename path of archive.
185 * @size : output file size
186 * @checksum : CRC32 checksum from input data.
187 *
188 * Write data to file.
189 *
190 * Returns: true (1) on success, otherwise false (0).
191 **/
192static int file_archive_decompress_data_to_file(
193 file_archive_transfer_t *transfer,
194 file_archive_file_handle_t *handle,
195 const char *path,
196 uint32_t size,
197 uint32_t checksum)
198{
199 if (!handle)
200 return 0;
201
202#if 0
203 handle->real_checksum = transfer->backend->stream_crc_calculate(
204 0, handle->data, size);
205 if (handle->real_checksum != checksum)
206 {
207 /* File CRC difers from archive CRC. */
208 printf("File CRC differs from archive CRC. File: 0x%x, Archive: 0x%x.\n",
209 (unsigned)handle->real_checksum, (unsigned)checksum);
210 }
211#endif
212
213 if (!filestream_write_file(path, handle->data, size))
214 return 0;
215
216 return 1;
217}
218
219void file_archive_parse_file_iterate_stop(file_archive_transfer_t *state)
220{
221 if (!state || !state->archive_file)
222 return;
223
224 state->type = ARCHIVE_TRANSFER_DEINIT;
225 file_archive_parse_file_iterate(state, NULL, NULL, NULL, NULL, NULL);
226}
227
228int file_archive_parse_file_iterate(
229 file_archive_transfer_t *state,
230 bool *returnerr,
231 const char *file,
232 const char *valid_exts,
233 file_archive_file_cb file_cb,
234 struct archive_extract_userdata *userdata)
235{
236 if (!state)
237 return -1;
238
239 switch (state->type)
240 {
241 case ARCHIVE_TRANSFER_NONE:
242 break;
243 case ARCHIVE_TRANSFER_INIT:
244 if (file_archive_parse_file_init(state, file) == 0)
245 {
246 if (userdata)
247 {
248 userdata->transfer = state;
249 strlcpy(userdata->archive_path, file,
250 sizeof(userdata->archive_path));
251 }
252 state->type = ARCHIVE_TRANSFER_ITERATE;
253 }
254 else
255 state->type = ARCHIVE_TRANSFER_DEINIT_ERROR;
256 break;
257 case ARCHIVE_TRANSFER_ITERATE:
258 if (state->backend)
259 {
260 int ret = state->backend->archive_parse_file_iterate_step(
261 state->context, valid_exts, userdata, file_cb);
262
263 if (ret == 1)
264 state->step_current++; /* found another file */
265 if (ret != 1)
266 state->type = ARCHIVE_TRANSFER_DEINIT;
267 if (ret == -1)
268 state->type = ARCHIVE_TRANSFER_DEINIT_ERROR;
269
270 /* early return to prevent deinit from never firing */
271 return 0;
272 }
273 return -1;
274 case ARCHIVE_TRANSFER_DEINIT_ERROR:
275 *returnerr = false;
276 case ARCHIVE_TRANSFER_DEINIT:
277 if (state->context)
278 {
279 if (state->backend->archive_parse_file_free)
280 state->backend->archive_parse_file_free(state->context);
281 state->context = NULL;
282 }
283
284 if (state->archive_file)
285 {
286 filestream_close(state->archive_file);
287 state->archive_file = NULL;
288 }
289
290#ifdef HAVE_MMAP
291 if (state->archive_mmap_data)
292 {
293 munmap(state->archive_mmap_data, (size_t)state->archive_size);
294 close(state->archive_mmap_fd);
295 state->archive_mmap_fd = 0;
296 state->archive_mmap_data = NULL;
297 }
298#endif
299
300 if (userdata)
301 userdata->transfer = NULL;
302 break;
303 }
304
305 if ( state->type == ARCHIVE_TRANSFER_DEINIT ||
306 state->type == ARCHIVE_TRANSFER_DEINIT_ERROR)
307 return -1;
308
309 return 0;
310}
311
312/**
313 * file_archive_walk:
314 * @file : filename path of archive
315 * @valid_exts : Valid extensions of archive to be parsed.
316 * If NULL, allow all.
317 * @file_cb : file_cb function pointer
318 * @userdata : userdata to pass to file_cb function pointer.
319 *
320 * Low-level file parsing. Enumerates over all files and calls
321 * file_cb with userdata.
322 *
323 * Returns: true (1) on success, otherwise false (0).
324 **/
325static bool file_archive_walk(const char *file, const char *valid_exts,
326 file_archive_file_cb file_cb, struct archive_extract_userdata *userdata)
327{
328 file_archive_transfer_t state;
329 bool returnerr = true;
330
331 state.type = ARCHIVE_TRANSFER_INIT;
332 state.archive_file = NULL;
333#ifdef HAVE_MMAP
334 state.archive_mmap_fd = 0;
335 state.archive_mmap_data = NULL;
336#endif
337 state.archive_size = 0;
338 state.context = NULL;
339 state.step_total = 0;
340 state.step_current = 0;
341 state.backend = NULL;
342
343 for (;;)
344 {
345 if (file_archive_parse_file_iterate(&state, &returnerr, file,
346 valid_exts, file_cb, userdata) != 0)
347 break;
348 }
349
350 return returnerr;
351}
352
353int file_archive_parse_file_progress(file_archive_transfer_t *state)
354{
355 if (!state || state->step_total == 0)
356 return 0;
357
358 return (int)((state->step_current * 100) / (state->step_total));
359}
360
361/**
362 * file_archive_extract_file:
363 * @archive_path : filename path to archive.
364 * @valid_exts : valid extensions for the file.
365 * @extraction_directory : the directory to extract temporary
366 * file to.
367 *
368 * Extract file from archive. If no file inside the archive is
369 * specified, the first file found will be used.
370 *
371 * Returns : true (1) on success, otherwise false (0).
372 **/
373bool file_archive_extract_file(
374 const char *archive_path,
375 const char *valid_exts,
376 const char *extraction_directory,
377 char *out_path, size_t len)
378{
379 struct archive_extract_userdata userdata;
380 bool ret = true;
381 struct string_list *list = string_split(valid_exts, "|");
382
383 userdata.archive_path[0] = '\0';
384 userdata.current_file_path[0] = '\0';
385 userdata.first_extracted_file_path = NULL;
386 userdata.extraction_directory = extraction_directory;
387 userdata.ext = list;
388 userdata.list = NULL;
389 userdata.found_file = false;
390 userdata.list_only = false;
391 userdata.crc = 0;
392 userdata.transfer = NULL;
393 userdata.dec = NULL;
394
395 if (!list)
396 {
397 ret = false;
398 goto end;
399 }
400
401 if (!file_archive_walk(archive_path, valid_exts,
402 file_archive_extract_cb, &userdata))
403 {
404 /* Parsing file archive failed. */
405 ret = false;
406 goto end;
407 }
408
409 if (!userdata.found_file)
410 {
411 /* Didn't find any file that matched valid extensions
412 * for libretro implementation. */
413 ret = false;
414 goto end;
415 }
416
417 if (!string_is_empty(userdata.first_extracted_file_path))
418 strlcpy(out_path, userdata.first_extracted_file_path, len);
419
420end:
421 if (userdata.first_extracted_file_path)
422 free(userdata.first_extracted_file_path);
423 if (list)
424 string_list_free(list);
425 return ret;
426}
427
428/* Warning: 'list' must zero initialised before
429 * calling this function, otherwise memory leaks/
430 * undefined behaviour will occur */
431bool file_archive_get_file_list_noalloc(struct string_list *list,
432 const char *path,
433 const char *valid_exts)
434{
435 struct archive_extract_userdata userdata;
436
437 if (!list || !string_list_initialize(list))
438 return false;
439
440 strlcpy(userdata.archive_path, path, sizeof(userdata.archive_path));
441 userdata.current_file_path[0] = '\0';
442 userdata.first_extracted_file_path = NULL;
443 userdata.extraction_directory = NULL;
444 userdata.ext = NULL;
445 userdata.list = list;
446 userdata.found_file = false;
447 userdata.list_only = true;
448 userdata.crc = 0;
449 userdata.transfer = NULL;
450 userdata.dec = NULL;
451
452 if (!file_archive_walk(path, valid_exts,
453 file_archive_get_file_list_cb, &userdata))
454 return false;
455 return true;
456}
457
458/**
459 * file_archive_get_file_list:
460 * @path : filename path of archive
461 *
462 * Returns: string listing of files from archive on success, otherwise NULL.
463 **/
464struct string_list *file_archive_get_file_list(const char *path,
465 const char *valid_exts)
466{
467 struct archive_extract_userdata userdata;
468
469 strlcpy(userdata.archive_path, path, sizeof(userdata.archive_path));
470 userdata.current_file_path[0] = '\0';
471 userdata.first_extracted_file_path = NULL;
472 userdata.extraction_directory = NULL;
473 userdata.ext = NULL;
474 userdata.list = string_list_new();
475 userdata.found_file = false;
476 userdata.list_only = true;
477 userdata.crc = 0;
478 userdata.transfer = NULL;
479 userdata.dec = NULL;
480
481 if (!userdata.list)
482 return NULL;
483 if (!file_archive_walk(path, valid_exts,
484 file_archive_get_file_list_cb, &userdata))
485 {
486 string_list_free(userdata.list);
487 return NULL;
488 }
489 return userdata.list;
490}
491
492bool file_archive_perform_mode(const char *path, const char *valid_exts,
493 const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size,
494 uint32_t crc32, struct archive_extract_userdata *userdata)
495{
496 file_archive_file_handle_t handle;
497 int ret;
498
499 if (!userdata->transfer || !userdata->transfer->backend)
500 return false;
501
502 handle.data = NULL;
503 handle.real_checksum = 0;
504
505 if (!userdata->transfer->backend->stream_decompress_data_to_file_init(
506 userdata->transfer->context, &handle, cdata, cmode, csize, size))
507 return false;
508
509 do
510 {
511 ret = userdata->transfer->backend->stream_decompress_data_to_file_iterate(
512 userdata->transfer->context, &handle);
513 }while (ret == 0);
514
515 if (ret == -1 || !file_archive_decompress_data_to_file(
516 userdata->transfer, &handle, path,
517 size, crc32))
518 return false;
519
520 return true;
521}
522
523/**
524 * file_archive_filename_split:
525 * @str : filename to turn into a string list
526 *
527 * Creates a new string list based on filename @path, delimited by a hash (#).
528 *
529 * Returns: new string list if successful, otherwise NULL.
530 */
531static struct string_list *file_archive_filename_split(const char *path)
532{
533 union string_list_elem_attr attr;
534 struct string_list *list = string_list_new();
535 const char *delim = path_get_archive_delim(path);
536
537 attr.i = 0;
538
539 if (delim)
540 {
541 /* add archive path to list first */
542 if (!string_list_append_n(list, path, (unsigned)(delim - path), attr))
543 goto error;
544
545 /* now add the path within the archive */
546 delim++;
547
548 if (*delim)
549 {
550 if (!string_list_append(list, delim, attr))
551 goto error;
552 }
553 }
554 else
555 if (!string_list_append(list, path, attr))
556 goto error;
557
558 return list;
559
560error:
561 string_list_free(list);
562 return NULL;
563}
564
565/* Generic compressed file loader.
566 * Extracts to buf, unless optional_filename != 0
567 * Then extracts to optional_filename and leaves buf alone.
568 */
569int file_archive_compressed_read(
570 const char * path, void **buf,
571 const char* optional_filename, int64_t *length)
572{
573 const struct
574 file_archive_file_backend *backend = NULL;
575 struct string_list *str_list = NULL;
576
577 /* Safety check.
578 * If optional_filename and optional_filename
579 * exists, we simply return 0,
580 * hoping that optional_filename is the
581 * same as requested.
582 */
583 if (optional_filename && path_is_valid(optional_filename))
584 {
585 *length = 0;
586 return 1;
587 }
588
589 str_list = file_archive_filename_split(path);
590 /* We assure that there is something after the '#' symbol.
591 *
592 * This error condition happens for example, when
593 * path = /path/to/file.7z, or
594 * path = /path/to/file.7z#
595 */
596 if (str_list->size <= 1)
597 {
598 /* could not extract string and substring. */
599 string_list_free(str_list);
600 *length = 0;
601 return 0;
602 }
603
604 backend = file_archive_get_file_backend(str_list->elems[0].data);
605 *length = backend->compressed_file_read(str_list->elems[0].data,
606 str_list->elems[1].data, buf, optional_filename);
607
608 string_list_free(str_list);
609
610 if (*length != -1)
611 return 1;
612
613 return 0;
614}
615
616const struct file_archive_file_backend *file_archive_get_zlib_file_backend(void)
617{
618#ifdef HAVE_ZLIB
619 return &zlib_backend;
620#else
621 return NULL;
622#endif
623}
624
625const struct file_archive_file_backend *file_archive_get_7z_file_backend(void)
626{
627#ifdef HAVE_7ZIP
628 return &sevenzip_backend;
629#else
630 return NULL;
631#endif
632}
633
634const struct file_archive_file_backend* file_archive_get_file_backend(const char *path)
635{
636#if defined(HAVE_7ZIP) || defined(HAVE_ZLIB)
637 char newpath[PATH_MAX_LENGTH];
638 const char *file_ext = NULL;
639 char *last = NULL;
640
641 strlcpy(newpath, path, sizeof(newpath));
642
643 if ((last = (char*)path_get_archive_delim(newpath)))
644 *last = '\0';
645
646 file_ext = path_get_extension(newpath);
647
648#ifdef HAVE_7ZIP
649 if (string_is_equal_noncase(file_ext, "7z"))
650 return &sevenzip_backend;
651#endif
652
653#ifdef HAVE_ZLIB
654 if ( string_is_equal_noncase(file_ext, "zip")
655 || string_is_equal_noncase(file_ext, "apk")
656 )
657 return &zlib_backend;
658#endif
659#endif
660
661 return NULL;
662}
663
664/**
665 * file_archive_get_file_crc32:
666 * @path : filename path of archive
667 *
668 * Returns: CRC32 of the specified file in the archive, otherwise 0.
669 * If no path within the archive is specified, the first
670 * file found inside is used.
671 **/
672uint32_t file_archive_get_file_crc32(const char *path)
673{
674 file_archive_transfer_t state;
675 struct archive_extract_userdata userdata = {0};
676 bool returnerr = false;
677 const char *archive_path = NULL;
678 bool contains_compressed = path_contains_compressed_file(path);
679
680 if (contains_compressed)
681 {
682 archive_path = path_get_archive_delim(path);
683
684 /* move pointer right after the delimiter to give us the path */
685 if (archive_path)
686 archive_path += 1;
687 }
688
689 state.type = ARCHIVE_TRANSFER_INIT;
690 state.archive_file = NULL;
691#ifdef HAVE_MMAP
692 state.archive_mmap_fd = 0;
693 state.archive_mmap_data = NULL;
694#endif
695 state.archive_size = 0;
696 state.context = NULL;
697 state.step_total = 0;
698 state.step_current = 0;
699 state.backend = NULL;
700
701 /* Initialize and open archive first.
702 Sets next state type to ITERATE. */
703 file_archive_parse_file_iterate(&state,
704 &returnerr, path, NULL, NULL,
705 &userdata);
706
707 for (;;)
708 {
709 /* Now find the first file in the archive. */
710 if (state.type == ARCHIVE_TRANSFER_ITERATE)
711 file_archive_parse_file_iterate(&state,
712 &returnerr, path, NULL, NULL,
713 &userdata);
714
715 /* If no path specified within archive, stop after
716 * finding the first file.
717 */
718 if (!contains_compressed)
719 break;
720
721 /* Stop when the right file in the archive is found. */
722 if (archive_path)
723 {
724 if (string_is_equal(userdata.current_file_path, archive_path))
725 break;
726 }
727 else
728 break;
729 }
730
731 file_archive_parse_file_iterate_stop(&state);
732
733 return userdata.crc;
734}