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