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