Commit | Line | Data |
---|---|---|
3719602c PC |
1 | /* Copyright (C) 2010-2020 The RetroArch team |
2 | * | |
3 | * --------------------------------------------------------------------------------------- | |
4 | * The following license statement only applies to this file (archive_file_zlib.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 | #include <string.h> | |
25 | ||
26 | #include <file/archive_file.h> | |
27 | #include <streams/file_stream.h> | |
28 | #include <retro_inline.h> | |
29 | #include <retro_miscellaneous.h> | |
30 | #include <encodings/crc32.h> | |
31 | ||
32 | #include <zlib.h> | |
33 | ||
34 | #ifndef CENTRAL_FILE_HEADER_SIGNATURE | |
35 | #define CENTRAL_FILE_HEADER_SIGNATURE 0x02014b50 | |
36 | #endif | |
37 | ||
38 | #ifndef END_OF_CENTRAL_DIR_SIGNATURE | |
39 | #define END_OF_CENTRAL_DIR_SIGNATURE 0x06054b50 | |
40 | #endif | |
41 | ||
42 | #define _READ_CHUNK_SIZE (128*1024) /* Read 128KiB compressed chunks */ | |
43 | ||
44 | enum file_archive_compression_mode | |
45 | { | |
46 | ZIP_MODE_STORED = 0, | |
47 | ZIP_MODE_DEFLATED = 8 | |
48 | }; | |
49 | ||
50 | typedef struct | |
51 | { | |
52 | struct file_archive_transfer *state; | |
53 | uint8_t *directory; | |
54 | uint8_t *directory_entry; | |
55 | uint8_t *directory_end; | |
56 | uint64_t fdoffset; | |
57 | uint32_t boffset, csize, usize; | |
58 | unsigned cmode; | |
59 | z_stream *zstream; | |
60 | uint8_t *tmpbuf; | |
61 | uint8_t *decompressed_data; | |
62 | } zip_context_t; | |
63 | ||
64 | static INLINE uint32_t read_le(const uint8_t *data, unsigned size) | |
65 | { | |
66 | unsigned i; | |
67 | uint32_t val = 0; | |
68 | ||
69 | size *= 8; | |
70 | for (i = 0; i < size; i += 8) | |
71 | val |= (uint32_t)*data++ << i; | |
72 | ||
73 | return val; | |
74 | } | |
75 | ||
76 | static void zip_context_free_stream( | |
77 | zip_context_t *zip_context, bool keep_decompressed) | |
78 | { | |
79 | if (zip_context->zstream) | |
80 | { | |
81 | inflateEnd(zip_context->zstream); | |
82 | free(zip_context->zstream); | |
83 | zip_context->fdoffset = 0; | |
84 | zip_context->csize = 0; | |
85 | zip_context->usize = 0; | |
86 | zip_context->zstream = NULL; | |
87 | } | |
88 | if (zip_context->tmpbuf) | |
89 | { | |
90 | free(zip_context->tmpbuf); | |
91 | zip_context->tmpbuf = NULL; | |
92 | } | |
93 | if (zip_context->decompressed_data && !keep_decompressed) | |
94 | { | |
95 | free(zip_context->decompressed_data); | |
96 | zip_context->decompressed_data = NULL; | |
97 | } | |
98 | } | |
99 | ||
100 | static bool zlib_stream_decompress_data_to_file_init( | |
101 | void *context, file_archive_file_handle_t *handle, | |
102 | const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size) | |
103 | { | |
104 | zip_context_t *zip_context = (zip_context_t *)context; | |
105 | struct file_archive_transfer *state = zip_context->state; | |
106 | uint8_t local_header_buf[4]; | |
107 | uint8_t *local_header; | |
108 | uint32_t offsetNL, offsetEL; | |
109 | int64_t offsetData; | |
110 | ||
111 | /* free previous data and stream if left unfinished */ | |
112 | zip_context_free_stream(zip_context, false); | |
113 | ||
114 | /* seek past most of the local directory header */ | |
115 | #ifdef HAVE_MMAP | |
116 | if (state->archive_mmap_data) | |
117 | { | |
118 | local_header = state->archive_mmap_data + (size_t)cdata + 26; | |
119 | } | |
120 | else | |
121 | #endif | |
122 | { | |
123 | filestream_seek(state->archive_file, (int64_t)(size_t)cdata + 26, RETRO_VFS_SEEK_POSITION_START); | |
124 | if (filestream_read(state->archive_file, local_header_buf, 4) != 4) | |
125 | goto error; | |
126 | local_header = local_header_buf; | |
127 | } | |
128 | ||
129 | offsetNL = read_le(local_header, 2); /* file name length */ | |
130 | offsetEL = read_le(local_header + 2, 2); /* extra field length */ | |
131 | offsetData = (int64_t)(size_t)cdata + 26 + 4 + offsetNL + offsetEL; | |
132 | ||
133 | zip_context->fdoffset = offsetData; | |
134 | zip_context->usize = size; | |
135 | zip_context->csize = csize; | |
136 | zip_context->boffset = 0; | |
137 | zip_context->cmode = cmode; | |
138 | zip_context->decompressed_data = (uint8_t*)malloc(size); | |
139 | zip_context->zstream = NULL; | |
140 | zip_context->tmpbuf = NULL; | |
141 | ||
142 | if (cmode == ZIP_MODE_DEFLATED) | |
143 | { | |
144 | /* Initialize the zlib inflate machinery */ | |
145 | zip_context->zstream = (z_stream*)malloc(sizeof(z_stream)); | |
146 | zip_context->tmpbuf = malloc(_READ_CHUNK_SIZE); | |
147 | ||
148 | zip_context->zstream->next_in = NULL; | |
149 | zip_context->zstream->avail_in = 0; | |
150 | zip_context->zstream->total_in = 0; | |
151 | zip_context->zstream->next_out = zip_context->decompressed_data; | |
152 | zip_context->zstream->avail_out = size; | |
153 | zip_context->zstream->total_out = 0; | |
154 | ||
155 | zip_context->zstream->zalloc = NULL; | |
156 | zip_context->zstream->zfree = NULL; | |
157 | zip_context->zstream->opaque = NULL; | |
158 | ||
159 | if (inflateInit2(zip_context->zstream, -MAX_WBITS) != Z_OK) { | |
160 | free(zip_context->zstream); | |
161 | zip_context->zstream = NULL; | |
162 | goto error; | |
163 | } | |
164 | } | |
165 | ||
166 | return true; | |
167 | ||
168 | error: | |
169 | zip_context_free_stream(zip_context, false); | |
170 | return false; | |
171 | } | |
172 | ||
173 | static int zlib_stream_decompress_data_to_file_iterate( | |
174 | void *context, file_archive_file_handle_t *handle) | |
175 | { | |
176 | zip_context_t *zip_context = (zip_context_t *)context; | |
177 | struct file_archive_transfer *state = zip_context->state; | |
178 | int64_t rd; | |
179 | ||
180 | if (zip_context->cmode == ZIP_MODE_STORED) | |
181 | { | |
182 | #ifdef HAVE_MMAP | |
183 | if (zip_context->state->archive_mmap_data) | |
184 | { | |
185 | /* Simply copy the data to the output buffer */ | |
186 | memcpy(zip_context->decompressed_data, | |
187 | zip_context->state->archive_mmap_data + (size_t)zip_context->fdoffset, | |
188 | zip_context->usize); | |
189 | } | |
190 | else | |
191 | #endif | |
192 | { | |
193 | /* Read the entire file to memory */ | |
194 | filestream_seek(state->archive_file, zip_context->fdoffset, RETRO_VFS_SEEK_POSITION_START); | |
195 | if (filestream_read(state->archive_file, | |
196 | zip_context->decompressed_data, | |
197 | zip_context->usize) < 0) | |
198 | return -1; | |
199 | } | |
200 | ||
201 | handle->data = zip_context->decompressed_data; | |
202 | return 1; | |
203 | } | |
204 | else if (zip_context->cmode == ZIP_MODE_DEFLATED) | |
205 | { | |
206 | int to_read = MIN(zip_context->csize - zip_context->boffset, _READ_CHUNK_SIZE); | |
207 | uint8_t *dptr; | |
208 | if (!zip_context->zstream) | |
209 | { | |
210 | /* file was uncompressed or decompression finished before */ | |
211 | return 1; | |
212 | } | |
213 | ||
214 | #ifdef HAVE_MMAP | |
215 | if (state->archive_mmap_data) | |
216 | { | |
217 | /* Decompress from the mapped file */ | |
218 | dptr = state->archive_mmap_data + (size_t)zip_context->fdoffset + zip_context->boffset; | |
219 | rd = to_read; | |
220 | } | |
221 | else | |
222 | #endif | |
223 | { | |
224 | /* Read some compressed data from file to the temp buffer */ | |
225 | filestream_seek(state->archive_file, zip_context->fdoffset + zip_context->boffset, | |
226 | RETRO_VFS_SEEK_POSITION_START); | |
227 | rd = filestream_read(state->archive_file, zip_context->tmpbuf, to_read); | |
228 | if (rd < 0) | |
229 | return -1; | |
230 | dptr = zip_context->tmpbuf; | |
231 | } | |
232 | ||
233 | zip_context->boffset += rd; | |
234 | zip_context->zstream->next_in = dptr; | |
235 | zip_context->zstream->avail_in = (uInt)rd; | |
236 | ||
237 | if (inflate(zip_context->zstream, 0) < 0) | |
238 | return -1; | |
239 | ||
240 | if (zip_context->boffset >= zip_context->csize) | |
241 | { | |
242 | inflateEnd(zip_context->zstream); | |
243 | free(zip_context->zstream); | |
244 | zip_context->zstream = NULL; | |
245 | ||
246 | handle->data = zip_context->decompressed_data; | |
247 | return 1; | |
248 | } | |
249 | ||
250 | return 0; /* still more data to process */ | |
251 | } | |
252 | ||
253 | /* No idea what kind of compression this is */ | |
254 | return -1; | |
255 | } | |
256 | ||
257 | static uint32_t zlib_stream_crc32_calculate(uint32_t crc, | |
258 | const uint8_t *data, size_t length) | |
259 | { | |
260 | return encoding_crc32(crc, data, length); | |
261 | } | |
262 | ||
263 | static bool zip_file_decompressed_handle( | |
264 | file_archive_transfer_t *transfer, | |
265 | file_archive_file_handle_t* handle, | |
266 | const uint8_t *cdata, unsigned cmode, uint32_t csize, | |
267 | uint32_t size, uint32_t crc32) | |
268 | { | |
269 | int ret = 0; | |
270 | ||
271 | transfer->backend = &zlib_backend; | |
272 | ||
273 | if (!transfer->backend->stream_decompress_data_to_file_init( | |
274 | transfer->context, handle, cdata, cmode, csize, size)) | |
275 | return false; | |
276 | ||
277 | do | |
278 | { | |
279 | ret = transfer->backend->stream_decompress_data_to_file_iterate( | |
280 | transfer->context, handle); | |
281 | if (ret < 0) | |
282 | return false; | |
283 | }while (ret == 0); | |
284 | ||
285 | return true; | |
286 | } | |
287 | ||
288 | typedef struct | |
289 | { | |
290 | char *opt_file; | |
291 | char *needle; | |
292 | void **buf; | |
293 | size_t size; | |
294 | bool found; | |
295 | } decomp_state_t; | |
296 | ||
297 | /* Extract the relative path (needle) from a | |
298 | * ZIP archive (path) and allocate a buffer for it to write it in. | |
299 | * | |
300 | * optional_outfile if not NULL will be used to extract the file to. | |
301 | * buf will be 0 then. | |
302 | */ | |
303 | ||
304 | static int zip_file_decompressed( | |
305 | const char *name, const char *valid_exts, | |
306 | const uint8_t *cdata, unsigned cmode, | |
307 | uint32_t csize, uint32_t size, | |
308 | uint32_t crc32, struct archive_extract_userdata *userdata) | |
309 | { | |
310 | decomp_state_t* decomp_state = (decomp_state_t*)userdata->cb_data; | |
311 | char last_char = name[strlen(name) - 1]; | |
312 | /* Ignore directories. */ | |
313 | if (last_char == '/' || last_char == '\\') | |
314 | return 1; | |
315 | ||
316 | if (strstr(name, decomp_state->needle)) | |
317 | { | |
318 | file_archive_file_handle_t handle = {0}; | |
319 | ||
320 | if (zip_file_decompressed_handle(userdata->transfer, | |
321 | &handle, cdata, cmode, csize, size, crc32)) | |
322 | { | |
323 | if (decomp_state->opt_file != 0) | |
324 | { | |
325 | /* Called in case core has need_fullpath enabled. */ | |
326 | bool success = filestream_write_file(decomp_state->opt_file, handle.data, size); | |
327 | ||
328 | /* Note: Do not free handle.data here - this | |
329 | * will be done when stream is deinitialised */ | |
330 | handle.data = NULL; | |
331 | ||
332 | decomp_state->size = 0; | |
333 | ||
334 | if (!success) | |
335 | return -1; | |
336 | } | |
337 | else | |
338 | { | |
339 | /* Called in case core has need_fullpath disabled. | |
340 | * Will move decompressed content directly into | |
341 | * RetroArch's ROM buffer. */ | |
342 | zip_context_t *zip_context = (zip_context_t *)userdata->transfer->context; | |
343 | ||
344 | decomp_state->size = 0; | |
345 | *decomp_state->buf = handle.data; | |
346 | decomp_state->size = size; | |
347 | /* We keep the data, prevent its deallocation during free */ | |
348 | zip_context->decompressed_data = NULL; | |
349 | handle.data = NULL; | |
350 | } | |
351 | } | |
352 | ||
353 | decomp_state->found = true; | |
354 | } | |
355 | ||
356 | return 1; | |
357 | } | |
358 | ||
359 | static int64_t zip_file_read( | |
360 | const char *path, | |
361 | const char *needle, void **buf, | |
362 | const char *optional_outfile) | |
363 | { | |
364 | file_archive_transfer_t state = {0}; | |
365 | decomp_state_t decomp = {0}; | |
366 | struct archive_extract_userdata userdata = {0}; | |
367 | bool returnerr = true; | |
368 | int ret = 0; | |
369 | ||
370 | if (needle) | |
371 | decomp.needle = strdup(needle); | |
372 | if (optional_outfile) | |
373 | decomp.opt_file = strdup(optional_outfile); | |
374 | ||
375 | state.type = ARCHIVE_TRANSFER_INIT; | |
376 | userdata.transfer = &state; | |
377 | userdata.cb_data = &decomp; | |
378 | decomp.buf = buf; | |
379 | ||
380 | do | |
381 | { | |
382 | ret = file_archive_parse_file_iterate(&state, &returnerr, path, | |
383 | "", zip_file_decompressed, &userdata); | |
384 | if (!returnerr) | |
385 | break; | |
386 | }while (ret == 0 && !decomp.found); | |
387 | ||
388 | file_archive_parse_file_iterate_stop(&state); | |
389 | ||
390 | if (decomp.opt_file) | |
391 | free(decomp.opt_file); | |
392 | if (decomp.needle) | |
393 | free(decomp.needle); | |
394 | ||
395 | if (!decomp.found) | |
396 | return -1; | |
397 | ||
398 | return (int64_t)decomp.size; | |
399 | } | |
400 | ||
401 | static int zip_parse_file_init(file_archive_transfer_t *state, | |
402 | const char *file) | |
403 | { | |
404 | uint8_t footer_buf[1024]; | |
405 | uint8_t *footer = footer_buf; | |
406 | int64_t read_pos = state->archive_size; | |
407 | int64_t read_block = MIN(read_pos, (ssize_t)sizeof(footer_buf)); | |
408 | int64_t directory_size, directory_offset; | |
409 | zip_context_t *zip_context = NULL; | |
410 | ||
411 | /* Minimal ZIP file size is 22 bytes */ | |
412 | if (read_block < 22) | |
413 | return -1; | |
414 | ||
415 | /* Find the end of central directory record by scanning | |
416 | * the file from the end towards the beginning. | |
417 | */ | |
418 | for (;;) | |
419 | { | |
420 | if (--footer < footer_buf) | |
421 | { | |
422 | if (read_pos <= 0) | |
423 | return -1; /* reached beginning of file */ | |
424 | ||
425 | /* Read 21 bytes of overlaps except on the first block. */ | |
426 | if (read_pos == state->archive_size) | |
427 | read_pos = read_pos - read_block; | |
428 | else | |
429 | read_pos = MAX(read_pos - read_block + 21, 0); | |
430 | ||
431 | /* Seek to read_pos and read read_block bytes. */ | |
432 | filestream_seek(state->archive_file, read_pos, RETRO_VFS_SEEK_POSITION_START); | |
433 | if (filestream_read(state->archive_file, footer_buf, read_block) != read_block) | |
434 | return -1; | |
435 | ||
436 | footer = footer_buf + read_block - 22; | |
437 | } | |
438 | if (read_le(footer, 4) == END_OF_CENTRAL_DIR_SIGNATURE) | |
439 | { | |
440 | unsigned comment_len = read_le(footer + 20, 2); | |
441 | if (read_pos + (footer - footer_buf) + 22 + comment_len == state->archive_size) | |
442 | break; /* found it! */ | |
443 | } | |
444 | } | |
445 | ||
446 | /* Read directory info and do basic sanity checks. */ | |
447 | directory_size = read_le(footer + 12, 4); | |
448 | directory_offset = read_le(footer + 16, 4); | |
449 | if (directory_size > state->archive_size | |
450 | || directory_offset > state->archive_size) | |
451 | return -1; | |
452 | ||
453 | /* This is a ZIP file, allocate one block of memory for both the | |
454 | * context and the entire directory, then read the directory. | |
455 | */ | |
456 | zip_context = (zip_context_t*)malloc(sizeof(zip_context_t) + (size_t)directory_size); | |
457 | zip_context->state = state; | |
458 | zip_context->directory = (uint8_t*)(zip_context + 1); | |
459 | zip_context->directory_entry = zip_context->directory; | |
460 | zip_context->directory_end = zip_context->directory + (size_t)directory_size; | |
461 | zip_context->zstream = NULL; | |
462 | zip_context->tmpbuf = NULL; | |
463 | zip_context->decompressed_data = NULL; | |
464 | ||
465 | filestream_seek(state->archive_file, directory_offset, RETRO_VFS_SEEK_POSITION_START); | |
466 | if (filestream_read(state->archive_file, zip_context->directory, directory_size) != directory_size) | |
467 | { | |
468 | free(zip_context); | |
469 | return -1; | |
470 | } | |
471 | ||
472 | state->context = zip_context; | |
473 | state->step_total = read_le(footer + 10, 2); /* total entries */; | |
474 | ||
475 | return 0; | |
476 | } | |
477 | ||
478 | static int zip_parse_file_iterate_step_internal( | |
479 | zip_context_t * zip_context, char *filename, | |
480 | const uint8_t **cdata, | |
481 | unsigned *cmode, uint32_t *size, uint32_t *csize, | |
482 | uint32_t *checksum, unsigned *payback) | |
483 | { | |
484 | uint8_t *entry = zip_context->directory_entry; | |
485 | uint32_t signature, namelength, extralength, commentlength, offset; | |
486 | ||
487 | if (entry < zip_context->directory || entry >= zip_context->directory_end) | |
488 | return 0; | |
489 | ||
490 | signature = read_le(zip_context->directory_entry + 0, 4); | |
491 | ||
492 | if (signature != CENTRAL_FILE_HEADER_SIGNATURE) | |
493 | return 0; | |
494 | ||
495 | *cmode = read_le(zip_context->directory_entry + 10, 2); /* compression mode, 0 = store, 8 = deflate */ | |
496 | *checksum = read_le(zip_context->directory_entry + 16, 4); /* CRC32 */ | |
497 | *csize = read_le(zip_context->directory_entry + 20, 4); /* compressed size */ | |
498 | *size = read_le(zip_context->directory_entry + 24, 4); /* uncompressed size */ | |
499 | ||
500 | namelength = read_le(zip_context->directory_entry + 28, 2); /* file name length */ | |
501 | extralength = read_le(zip_context->directory_entry + 30, 2); /* extra field length */ | |
502 | commentlength = read_le(zip_context->directory_entry + 32, 2); /* file comment length */ | |
503 | ||
504 | if (namelength >= PATH_MAX_LENGTH) | |
505 | return -1; | |
506 | ||
507 | memcpy(filename, zip_context->directory_entry + 46, namelength); /* file name */ | |
508 | filename[namelength] = '\0'; | |
509 | ||
510 | offset = read_le(zip_context->directory_entry + 42, 4); /* relative offset of local file header */ | |
511 | ||
512 | *cdata = (uint8_t*)(size_t)offset; /* store file offset in data pointer */ | |
513 | ||
514 | *payback = 46 + namelength + extralength + commentlength; | |
515 | ||
516 | return 1; | |
517 | } | |
518 | ||
519 | static int zip_parse_file_iterate_step(void *context, | |
520 | const char *valid_exts, struct archive_extract_userdata *userdata, | |
521 | file_archive_file_cb file_cb) | |
522 | { | |
523 | zip_context_t *zip_context = (zip_context_t *)context; | |
524 | const uint8_t *cdata = NULL; | |
525 | uint32_t checksum = 0; | |
526 | uint32_t size = 0; | |
527 | uint32_t csize = 0; | |
528 | unsigned cmode = 0; | |
529 | unsigned payload = 0; | |
530 | int ret = zip_parse_file_iterate_step_internal(zip_context, | |
531 | userdata->current_file_path, &cdata, &cmode, &size, &csize, &checksum, &payload); | |
532 | ||
533 | if (ret != 1) | |
534 | return ret; | |
535 | ||
536 | userdata->crc = checksum; | |
537 | ||
538 | if (file_cb && !file_cb(userdata->current_file_path, valid_exts, cdata, cmode, | |
539 | csize, size, checksum, userdata)) | |
540 | return 0; | |
541 | ||
542 | zip_context->directory_entry += payload; | |
543 | ||
544 | return 1; | |
545 | } | |
546 | ||
547 | static void zip_parse_file_free(void *context) | |
548 | { | |
549 | zip_context_t *zip_context = (zip_context_t *)context; | |
550 | zip_context_free_stream(zip_context, false); | |
551 | free(zip_context); | |
552 | } | |
553 | ||
554 | const struct file_archive_file_backend zlib_backend = { | |
555 | zip_parse_file_init, | |
556 | zip_parse_file_iterate_step, | |
557 | zip_parse_file_free, | |
558 | zlib_stream_decompress_data_to_file_init, | |
559 | zlib_stream_decompress_data_to_file_iterate, | |
560 | zlib_stream_crc32_calculate, | |
561 | zip_file_read, | |
562 | "zlib" | |
563 | }; |