libretro: adjust psxclock description
[pcsx_rearmed.git] / deps / libretro-common / file / archive_file_7z.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_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
50struct 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
65static void *sevenzip_stream_alloc_impl(ISzAllocPtr p, size_t size)
66{
67 if (size == 0)
68 return 0;
69 return malloc(size);
70}
71
72static void sevenzip_stream_free_impl(ISzAllocPtr p, void *address)
73{
74 (void)p;
75
76 if (address)
77 free(address);
78}
79
80static 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
88static 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
111static 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 */
138static 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
309static 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
324static 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
350static 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
407error:
408 if (sevenzip_context)
409 sevenzip_parse_file_free(sevenzip_context);
410 return -1;
411}
412
413static 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
476static 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
511static 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
517const 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};