libretro: adjust psxclock description
[pcsx_rearmed.git] / deps / libretro-common / file / archive_file_7z.c
1 /* Copyright  (C) 2010-2020 The RetroArch team
2  *
3  * ---------------------------------------------------------------------------------------
4  * The following license statement only applies to this file (archive_file_sevenzip.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
25 #include <boolean.h>
26 #include <file/archive_file.h>
27 #include <streams/file_stream.h>
28 #include <retro_miscellaneous.h>
29 #include <encodings/utf.h>
30 #include <encodings/crc32.h>
31 #include <string/stdstring.h>
32 #include <lists/string_list.h>
33 #include <file/file_path.h>
34 #include <compat/strl.h>
35 #include <7zip/7z.h>
36 #include <7zip/7zCrc.h>
37 #include <7zip/7zFile.h>
38
39 #define SEVENZIP_MAGIC "7z\xBC\xAF\x27\x1C"
40 #define SEVENZIP_MAGIC_LEN 6
41 #define SEVENZIP_LOOKTOREAD_BUF_SIZE (1 << 14)
42
43 /* Assume W-functions do not work below Win2K and Xbox platforms */
44 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX)
45 #ifndef LEGACY_WIN32
46 #define LEGACY_WIN32
47 #endif
48 #endif
49
50 struct sevenzip_context_t
51 {
52    uint8_t *output;
53    CFileInStream archiveStream;
54    CLookToRead2 lookStream;
55    ISzAlloc allocImp;
56    ISzAlloc allocTempImp;
57    CSzArEx db;
58    size_t temp_size;
59    uint32_t parse_index;
60    uint32_t decompress_index;
61    uint32_t packIndex;
62    uint32_t   block_index;
63 };
64
65 static void *sevenzip_stream_alloc_impl(ISzAllocPtr p, size_t size)
66 {
67    if (size == 0)
68       return 0;
69    return malloc(size);
70 }
71
72 static void sevenzip_stream_free_impl(ISzAllocPtr p, void *address)
73 {
74    (void)p;
75
76    if (address)
77       free(address);
78 }
79
80 static void *sevenzip_stream_alloc_tmp_impl(ISzAllocPtr p, size_t size)
81 {
82    (void)p;
83    if (size == 0)
84       return 0;
85    return malloc(size);
86 }
87
88 static void* sevenzip_stream_new(void)
89 {
90    struct sevenzip_context_t *sevenzip_context =
91          (struct sevenzip_context_t*)calloc(1, sizeof(struct sevenzip_context_t));
92
93    /* These are the allocation routines - currently using
94     * the non-standard 7zip choices. */
95    sevenzip_context->allocImp.Alloc     = sevenzip_stream_alloc_impl;
96    sevenzip_context->allocImp.Free      = sevenzip_stream_free_impl;
97    sevenzip_context->allocTempImp.Alloc = sevenzip_stream_alloc_tmp_impl;
98    sevenzip_context->allocTempImp.Free  = sevenzip_stream_free_impl;
99    sevenzip_context->block_index        = 0xFFFFFFFF;
100    sevenzip_context->output             = NULL;
101
102    sevenzip_context->lookStream.bufSize = SEVENZIP_LOOKTOREAD_BUF_SIZE * sizeof(Byte);
103    sevenzip_context->lookStream.buf     = (Byte*)malloc(sevenzip_context->lookStream.bufSize);
104
105    if (!sevenzip_context->lookStream.buf)
106       sevenzip_context->lookStream.bufSize = 0;
107
108    return sevenzip_context;
109 }
110
111 static void sevenzip_parse_file_free(void *context)
112 {
113    struct sevenzip_context_t *sevenzip_context = (struct sevenzip_context_t*)context;
114
115    if (!sevenzip_context)
116       return;
117
118    if (sevenzip_context->output)
119    {
120       IAlloc_Free(&sevenzip_context->allocImp, sevenzip_context->output);
121       sevenzip_context->output       = NULL;
122    }
123
124    SzArEx_Free(&sevenzip_context->db, &sevenzip_context->allocImp);
125    File_Close(&sevenzip_context->archiveStream.file);
126
127    if (sevenzip_context->lookStream.buf)
128       free(sevenzip_context->lookStream.buf);
129
130    free(sevenzip_context);
131 }
132
133 /* Extract the relative path (needle) from a 7z archive
134  * (path) and allocate a buf for it to write it in.
135  * If optional_outfile is set, extract to that instead
136  * and don't allocate buffer.
137  */
138 static int64_t sevenzip_file_read(
139       const char *path,
140       const char *needle, void **buf,
141       const char *optional_outfile)
142 {
143    CFileInStream archiveStream;
144    CLookToRead2 lookStream;
145    ISzAlloc allocImp;
146    ISzAlloc allocTempImp;
147    CSzArEx db;
148    uint8_t *output      = 0;
149    int64_t outsize      = -1;
150
151    /*These are the allocation routines.
152     * Currently using the non-standard 7zip choices. */
153    allocImp.Alloc       = sevenzip_stream_alloc_impl;
154    allocImp.Free        = sevenzip_stream_free_impl;
155    allocTempImp.Alloc   = sevenzip_stream_alloc_tmp_impl;
156    allocTempImp.Free    = sevenzip_stream_free_impl;
157
158    lookStream.bufSize   = SEVENZIP_LOOKTOREAD_BUF_SIZE * sizeof(Byte);
159    lookStream.buf       = (Byte*)malloc(lookStream.bufSize);
160
161    if (!lookStream.buf)
162       lookStream.bufSize = 0;
163
164 #if defined(_WIN32) && defined(USE_WINDOWS_FILE) && !defined(LEGACY_WIN32)
165    if (!string_is_empty(path))
166    {
167       wchar_t *pathW = utf8_to_utf16_string_alloc(path);
168
169       if (pathW)
170       {
171          /* Could not open 7zip archive? */
172          if (InFile_OpenW(&archiveStream.file, pathW))
173          {
174             free(pathW);
175             return -1;
176          }
177
178          free(pathW);
179       }
180    }
181 #else
182    /* Could not open 7zip archive? */
183    if (InFile_Open(&archiveStream.file, path))
184       return -1;
185 #endif
186
187    FileInStream_CreateVTable(&archiveStream);
188    LookToRead2_CreateVTable(&lookStream, false);
189    lookStream.realStream = &archiveStream.vt;
190    LookToRead2_Init(&lookStream);
191    CrcGenerateTable();
192
193    memset(&db, 0, sizeof(db));
194
195    SzArEx_Init(&db);
196
197    if (SzArEx_Open(&db, &lookStream.vt, &allocImp, &allocTempImp) == SZ_OK)
198    {
199       uint32_t i;
200       bool file_found      = false;
201       uint16_t *temp       = NULL;
202       size_t temp_size     = 0;
203       uint32_t block_index   = 0xFFFFFFFF;
204       SRes res             = SZ_OK;
205
206       for (i = 0; i < db.NumFiles; i++)
207       {
208          size_t len;
209          char infile[PATH_MAX_LENGTH];
210          size_t offset                = 0;
211          size_t outSizeProcessed      = 0;
212
213          /* We skip over everything which is not a directory.
214           * FIXME: Why continue then if IsDir is true?*/
215          if (SzArEx_IsDir(&db, i))
216             continue;
217
218          len = SzArEx_GetFileNameUtf16(&db, i, NULL);
219
220          if (len > temp_size)
221          {
222             if (temp)
223                free(temp);
224             temp_size = len;
225             temp = (uint16_t *)malloc(temp_size * sizeof(temp[0]));
226
227             if (temp == 0)
228             {
229                res = SZ_ERROR_MEM;
230                break;
231             }
232          }
233
234          SzArEx_GetFileNameUtf16(&db, i, temp);
235          res       = SZ_ERROR_FAIL;
236          infile[0] = '\0';
237
238          if (temp)
239             res = utf16_to_char_string(temp, infile, sizeof(infile))
240                ? SZ_OK : SZ_ERROR_FAIL;
241
242          if (string_is_equal(infile, needle))
243          {
244             size_t output_size   = 0;
245
246             /* C LZMA SDK does not support chunked extraction - see here:
247              * sourceforge.net/p/sevenzip/discussion/45798/thread/6fb59aaf/
248              * */
249             file_found = true;
250             res = SzArEx_Extract(&db, &lookStream.vt, i, &block_index,
251                   &output, &output_size, &offset, &outSizeProcessed,
252                   &allocImp, &allocTempImp);
253
254             if (res != SZ_OK)
255                break; /* This goes to the error section. */
256
257             outsize = (int64_t)outSizeProcessed;
258
259             if (optional_outfile)
260             {
261                const void *ptr = (const void*)(output + offset);
262
263                if (!filestream_write_file(optional_outfile, ptr, outsize))
264                {
265                   res        = SZ_OK;
266                   file_found = true;
267                   outsize    = -1;
268                }
269             }
270             else
271             {
272                /*We could either use the 7Zip allocated buffer,
273                 * or create our own and use it.
274                 * We would however need to realloc anyways, because RetroArch
275                 * expects a \0 at the end, therefore we allocate new,
276                 * copy and free the old one. */
277                *buf = malloc((size_t)(outsize + 1));
278                ((char*)(*buf))[outsize] = '\0';
279                memcpy(*buf,output + offset,outsize);
280             }
281             break;
282          }
283       }
284
285       if (temp)
286          free(temp);
287       IAlloc_Free(&allocImp, output);
288
289       if (!(file_found && res == SZ_OK))
290       {
291          /* Error handling
292           *
293           * Failed to open compressed file inside 7zip archive.
294           */
295
296          outsize    = -1;
297       }
298    }
299
300    SzArEx_Free(&db, &allocImp);
301    File_Close(&archiveStream.file);
302
303    if (lookStream.buf)
304       free(lookStream.buf);
305
306    return outsize;
307 }
308
309 static bool sevenzip_stream_decompress_data_to_file_init(
310       void *context, file_archive_file_handle_t *handle,
311       const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size)
312 {
313    struct sevenzip_context_t *sevenzip_context =
314          (struct sevenzip_context_t*)context;
315
316    if (!sevenzip_context)
317       return false;
318
319    sevenzip_context->decompress_index = (uint32_t)(size_t)cdata;
320
321    return true;
322 }
323
324 static int sevenzip_stream_decompress_data_to_file_iterate(
325       void *context, file_archive_file_handle_t *handle)
326 {
327    struct sevenzip_context_t *sevenzip_context =
328          (struct sevenzip_context_t*)context;
329
330    SRes res                = SZ_ERROR_FAIL;
331    size_t output_size      = 0;
332    size_t offset           = 0;
333    size_t outSizeProcessed = 0;
334
335    res = SzArEx_Extract(&sevenzip_context->db,
336          &sevenzip_context->lookStream.vt, sevenzip_context->decompress_index,
337          &sevenzip_context->block_index, &sevenzip_context->output,
338          &output_size, &offset, &outSizeProcessed,
339          &sevenzip_context->allocImp, &sevenzip_context->allocTempImp);
340
341    if (res != SZ_OK)
342       return 0;
343
344    if (handle)
345       handle->data = sevenzip_context->output + offset;
346
347    return 1;
348 }
349
350 static int sevenzip_parse_file_init(file_archive_transfer_t *state,
351       const char *file)
352 {
353    uint8_t magic_buf[SEVENZIP_MAGIC_LEN];
354    struct sevenzip_context_t *sevenzip_context = NULL;
355
356    if (state->archive_size < SEVENZIP_MAGIC_LEN)
357       goto error;
358
359    filestream_seek(state->archive_file, 0, SEEK_SET);
360    if (filestream_read(state->archive_file, magic_buf, SEVENZIP_MAGIC_LEN) != SEVENZIP_MAGIC_LEN)
361       goto error;
362
363    if (string_is_not_equal_fast(magic_buf, SEVENZIP_MAGIC, SEVENZIP_MAGIC_LEN))
364       goto error;
365
366    sevenzip_context = (struct sevenzip_context_t*)sevenzip_stream_new();
367    state->context = sevenzip_context;
368
369 #if defined(_WIN32) && defined(USE_WINDOWS_FILE) && !defined(LEGACY_WIN32)
370    if (!string_is_empty(file))
371    {
372       wchar_t *fileW = utf8_to_utf16_string_alloc(file);
373
374       if (fileW)
375       {
376          /* could not open 7zip archive? */
377          if (InFile_OpenW(&sevenzip_context->archiveStream.file, fileW))
378          {
379             free(fileW);
380             goto error;
381          }
382
383          free(fileW);
384       }
385    }
386 #else
387    /* could not open 7zip archive? */
388    if (InFile_Open(&sevenzip_context->archiveStream.file, file))
389       goto error;
390 #endif
391
392    FileInStream_CreateVTable(&sevenzip_context->archiveStream);
393    LookToRead2_CreateVTable(&sevenzip_context->lookStream, false);
394    sevenzip_context->lookStream.realStream = &sevenzip_context->archiveStream.vt;
395    LookToRead2_Init(&sevenzip_context->lookStream);
396    CrcGenerateTable();
397    SzArEx_Init(&sevenzip_context->db);
398
399    if (SzArEx_Open(&sevenzip_context->db, &sevenzip_context->lookStream.vt,
400          &sevenzip_context->allocImp, &sevenzip_context->allocTempImp) != SZ_OK)
401       goto error;
402
403    state->step_total = sevenzip_context->db.NumFiles;
404
405    return 0;
406
407 error:
408    if (sevenzip_context)
409       sevenzip_parse_file_free(sevenzip_context);
410    return -1;
411 }
412
413 static int sevenzip_parse_file_iterate_step_internal(
414       struct sevenzip_context_t *sevenzip_context, char *filename,
415       const uint8_t **cdata, unsigned *cmode,
416       uint32_t *size, uint32_t *csize, uint32_t *checksum,
417       unsigned *payback, struct archive_extract_userdata *userdata)
418 {
419    if (sevenzip_context->parse_index < sevenzip_context->db.NumFiles)
420    {
421       size_t len = SzArEx_GetFileNameUtf16(&sevenzip_context->db,
422             sevenzip_context->parse_index, NULL);
423       uint64_t compressed_size = 0;
424
425       if (sevenzip_context->packIndex < sevenzip_context->db.db.NumPackStreams)
426       {
427          compressed_size = sevenzip_context->db.db.PackPositions[sevenzip_context->packIndex + 1] -
428                sevenzip_context->db.db.PackPositions[sevenzip_context->packIndex];
429
430          sevenzip_context->packIndex++;
431       }
432
433       if (len < PATH_MAX_LENGTH &&
434           !SzArEx_IsDir(&sevenzip_context->db, sevenzip_context->parse_index))
435       {
436          char infile[PATH_MAX_LENGTH];
437          SRes res                     = SZ_ERROR_FAIL;
438          uint16_t *temp               = (uint16_t*)malloc(len * sizeof(uint16_t));
439
440          if (!temp)
441             return -1;
442
443          infile[0] = '\0';
444
445          SzArEx_GetFileNameUtf16(&sevenzip_context->db, sevenzip_context->parse_index,
446                temp);
447
448          if (temp)
449          {
450             res  = utf16_to_char_string(temp, infile, sizeof(infile))
451                ? SZ_OK : SZ_ERROR_FAIL;
452             free(temp);
453          }
454
455          if (res != SZ_OK)
456             return -1;
457
458          strlcpy(filename, infile, PATH_MAX_LENGTH);
459
460          *cmode    = 0; /* unused for 7zip */
461          *checksum = sevenzip_context->db.CRCs.Vals[sevenzip_context->parse_index];
462          *size     = (uint32_t)SzArEx_GetFileSize(&sevenzip_context->db, sevenzip_context->parse_index);
463          *csize    = (uint32_t)compressed_size;
464
465          *cdata    = (uint8_t *)(size_t)sevenzip_context->parse_index;
466       }
467    }
468    else
469       return 0;
470
471    *payback = 1;
472
473    return 1;
474 }
475
476 static int sevenzip_parse_file_iterate_step(void *context,
477       const char *valid_exts,
478       struct archive_extract_userdata *userdata, file_archive_file_cb file_cb)
479 {
480    const uint8_t *cdata = NULL;
481    uint32_t checksum    = 0;
482    uint32_t size        = 0;
483    uint32_t csize       = 0;
484    unsigned cmode       = 0;
485    unsigned payload     = 0;
486    struct sevenzip_context_t *sevenzip_context = (struct sevenzip_context_t*)context;
487    int ret;
488
489    userdata->current_file_path[0] = '\0';
490
491    ret = sevenzip_parse_file_iterate_step_internal(sevenzip_context,
492          userdata->current_file_path,
493          &cdata, &cmode, &size, &csize,
494          &checksum, &payload, userdata);
495
496    if (ret != 1)
497       return ret;
498
499    userdata->crc                 = checksum;
500
501    if (file_cb && !file_cb(userdata->current_file_path, valid_exts,
502             cdata, cmode,
503             csize, size, checksum, userdata))
504       return 0;
505
506    sevenzip_context->parse_index += payload;
507
508    return 1;
509 }
510
511 static uint32_t sevenzip_stream_crc32_calculate(uint32_t crc,
512       const uint8_t *data, size_t length)
513 {
514    return encoding_crc32(crc, data, length);
515 }
516
517 const struct file_archive_file_backend sevenzip_backend = {
518    sevenzip_parse_file_init,
519    sevenzip_parse_file_iterate_step,
520    sevenzip_parse_file_free,
521    sevenzip_stream_decompress_data_to_file_init,
522    sevenzip_stream_decompress_data_to_file_iterate,
523    sevenzip_stream_crc32_calculate,
524    sevenzip_file_read,
525    "7z"
526 };