libretro: adjust psxclock description
[pcsx_rearmed.git] / deps / libretro-common / file / archive_file.c
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
43 static 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
94 static 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
135 static 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  **/
192 static 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
219 void 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
228 int 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  **/
325 static 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
353 int 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  **/
373 bool 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
420 end:
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 */
431 bool 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  **/
464 struct 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
492 bool 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  */
531 static 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
560 error:
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  */
569 int 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
616 const 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
625 const 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
634 const 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  **/
672 uint32_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 }