1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: w_zip.c 1456 2019-09-11 12:26:00Z wesleyjohnson $
5 //
6 // Copyright (C) 1998-2016 by DooM Legacy Team.
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License
10 // as published by the Free Software Foundation; either version 2
11 // of the License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
17 //
18
19 #include "doomdef.h"
20 #ifdef ZIPWAD
21
22 #include <fcntl.h>
23 // open
24 #include <unistd.h>
25 // close, read, lseek
26
27 #include <zip.h>
28 // ziplib
29
30 #include "doomincl.h"
31 #include "doomtype.h"
32 #include "w_wad.h"
33 #include "z_zone.h"
34 #include "md5.h"
35
36
37 // [WDJ] 2020
38 // Use ziplib to access zip archives (xx.zip).
39 // It uses zlib to expand the compressed files.
40
41 // Check for libzip >= 1.2 which has zip_fseek.
42 // A loaded libzip may not have zip_fseek.
43 #if (HAVE_LIBZIP < 12) || defined(ZIPWAD_OPTIONAL)
44 // Generate WZ_zip_fseek.
45 # define GEN_ZIP_SEEK
46 #endif
47
48 #if (HAVE_LIBZIP >= 12) && defined(ZIPWAD_OPTIONAL)
49 // Test loaded libzip for zip_fseek_present
50 # define TEST_ZIP_SEEK
51 #endif
52
53
54 #ifdef ZIPWAD_OPTIONAL
55 #include <dlfcn.h>
56 // dlopen
57
58 byte ziplib_present = 0;
59 #ifdef TEST_ZIP_SEEK
60 byte zip_seek_present = 0;
61 #endif
62
63 #ifdef LINUX
64 # define LIBZIP_NAME "libzip.so"
65 #else
66 # define LIBZIP_NAME "libzip.so"
67 #endif
68
69 // Return 0 when zlib is not available.
WZ_available(void)70 void WZ_available( void )
71 {
72 // Test for libzip being loaded.
73 void * lzp = dlopen( LIBZIP_NAME, RTLD_LAZY | RTLD_NOLOAD );
74 // No reason to close it as it would dec the reference count.
75
76 ziplib_present = ( lzp != NULL );
77
78 #ifdef TEST_ZIP_SEEK
79 if( ziplib_present )
80 {
81 void * sp = dlsym( lzp, "zip_fseek" );
82 zip_seek_present = ( sp != NULL );
83 }
84 #endif
85 }
86 #undef ZIPLIB_NAME
87 #endif
88
89 // filebuf : a buffer of length MAX_WADPATH
90 // Return filename with extension, otherwise NULL.
WZ_make_name_with_extension(const char * filename,const char * extension,char * buf)91 char * WZ_make_name_with_extension( const char * filename, const char * extension, /*OUT*/ char * buf )
92 {
93 strncpy( buf, filename, MAX_WADPATH );
94 buf[MAX_WADPATH-1] = 0;
95
96 char * extp = strrchr( buf, '.' );
97 if( ! extp )
98 {
99 // append extension
100 extp = buf + strlen( buf );
101 if( extp > (buf + (MAX_WADPATH - 5)) )
102 return NULL;
103
104 extp[0] = '.';
105 }
106
107 if( extp > (buf + (MAX_WADPATH - 5)) )
108 return NULL;
109
110 strcpy( extp+1, extension );
111 return buf;
112 }
113
114 char archive_filename[MAX_WADPATH];
115
116
WZ_save_archive_name(const char * filename)117 void WZ_save_archive_name( const char * filename )
118 {
119 strncpy( archive_filename, filename, MAX_WADPATH-1 );
120 archive_filename[MAX_WADPATH-1] = 0;
121 }
122
123 // Return archive filename, otherwise NULL.
WZ_make_archive_name(const char * filename)124 char * WZ_make_archive_name( const char * filename )
125 {
126 return WZ_make_name_with_extension( filename, "zip", /*OUT*/ archive_filename );
127 }
128
129
130
131 // Return 0 when exact match.
132 // Return 1 when filename1 is zip file name.
133 // Return 2 when filename2 is zip file name.
134 // Return >= 4 when other difference.
WZ_filename_cmp(const char * filename1,const char * filename2)135 byte WZ_filename_cmp( const char * filename1, const char * filename2 )
136 {
137 int l1, l2;
138 if( strcasecmp( filename1, filename2 ) )
139 return 0; // exact match
140
141 l1 = strlen( filename1 ) - 3; // should be the '.'
142 l2 = strlen( filename2 ) - 3;
143 if( (l2 != l1) || (l1 < 1) )
144 return 9; // cannot match extensions
145
146 if( strncasecmp( filename1, filename2, l1 ) )
147 return 5; // different name
148
149 if( (strncasecmp( filename1 + l1, ".wad", 3 ) == 0 )
150 && (strncasecmp( filename2 + l1, ".zip", 3 ) == 0 ) )
151 {
152 return 2; // filename2 has ZIP extension
153 }
154 else if( (strncasecmp( filename1 + l1, ".zip", 3 ) == 0 )
155 && (strncasecmp( filename2 + l1, ".wad", 3 ) == 0 ) )
156 {
157 return 1; // filename1 has ZIP extension
158 }
159
160 return 4; // match fail
161 }
162
163 // Can only have one archive open at any time.
164 // Do not support archive in archive.
165 static zip_t * archive_z = NULL;
166 static zip_file_t * file_z = NULL;
167 static FILE * file_n = NULL;
168
169 byte archive_open = 0;
170 byte archive_filenum = 0; // filenum assigned by W_Load_WadFile
171 byte file_filenum = 0;
172
173
WZ_error_from_archive_z(void)174 const char * WZ_error_from_archive_z( void )
175 {
176 zip_error_t * error_z = zip_get_error( archive_z );
177 return zip_error_strerror( error_z );
178 }
179
180 #ifdef GEN_ZIP_SEEK
181 // Does not have zip_fseek
182 zip_uint64_t position_z;
183 char * filename_z = NULL;
184
185 // Return -1 when fail.
186 static
WZ_zip_fseek(uint32_t offset)187 int WZ_zip_fseek( uint32_t offset )
188 {
189 byte bb[1024];
190
191 #ifdef TEST_ZIP_SEEK
192 if( zip_seek_present )
193 {
194 // [WDJ] zip_fseek requires ziplib >= 1.2.0
195 return zip_fseek( file_z, offset, SEEK_SET );
196 }
197 #endif
198
199 // Does not have zip_fseek
200 if( offset < position_z )
201 {
202 // Re-open the file to position it to 0 again.
203 zip_fclose( file_z );
204 file_z = zip_fopen( archive_z, filename_z, ZIP_FL_NOCASE | ZIP_FL_NODIR );
205 position_z = 0;
206 }
207
208 while( position_z < offset )
209 {
210 uint32_t read_count = offset - position_z;
211 if( read_count > sizeof(bb) ) read_count = sizeof(bb); // buf size
212 int rs = zip_fread( file_z, bb, read_count ); // discard
213 if( rs < 0 )
214 return rs;
215
216 position_z += rs;
217 }
218 return 0;
219 }
220 #endif
221
222
WZ_open_archive(const char * archive_name)223 void WZ_open_archive( const char * archive_name )
224 {
225 int zip_err_code;
226
227 #ifdef ZIPWAD_OPTIONAL
228 if( ! ziplib_present )
229 return;
230 #endif
231
232 if( archive_z ) // Only allow one archive open at a time.
233 {
234 zip_discard( archive_z ); // does not save changes, no errors
235 archive_open = 0;
236 }
237
238 archive_z = zip_open( archive_name, ZIP_RDONLY, &zip_err_code );
239 if( archive_z == NULL )
240 {
241 zip_error_t zip_error;
242 zip_error_init_with_code( &zip_error, zip_err_code );
243 GenPrintf(EMSG_warn, "Zip file %s: %s\n", archive_name, zip_error_strerror( &zip_error ) );
244 return;
245 }
246
247 archive_open = 1;
248 }
249
WZ_close_archive(void)250 void WZ_close_archive( void )
251 {
252 if( archive_z )
253 {
254 // zip_close( archive_z );
255 zip_discard( archive_z ); // does not save changes, no errors
256 archive_z = NULL;
257 }
258 archive_open = 0;
259 }
260
WZ_open_file_z(const char * filename)261 byte WZ_open_file_z( const char * filename )
262 {
263 if( file_z )
264 zip_fclose( file_z );
265
266 #ifdef GEN_ZIP_SEEK
267 // Tracking for WZ_zip_fseek
268 if( filename_z )
269 free( filename_z );
270 filename_z = strdup( filename );
271 position_z = 0;
272 #endif
273
274 file_z = zip_fopen( archive_z, filename, ZIP_FL_NOCASE | ZIP_FL_NODIR );
275 if( file_z )
276 return FH_zip_file;
277
278 GenPrintf(EMSG_warn, "Open %s: %s\n", filename, WZ_error_from_archive_z() );
279 return FH_none;
280 }
281
282 static
WZ_close_file_z(void)283 void WZ_close_file_z( void )
284 {
285 if( file_z )
286 {
287 zip_fclose( file_z );
288 file_z = NULL;
289 }
290
291 #ifdef GEN_ZIP_SEEK
292 // Tracking for WZ_zip_fseek
293 position_z = 0;
294 free( filename_z ); // can be NULL
295 filename_z = NULL;
296 #endif
297
298 file_filenum = 0xFF;
299 }
300
301
302 // Open a file.
303 // Return the local handle.
WZ_open(const char * filename)304 byte WZ_open( const char * filename )
305 {
306 byte fc = W_filename_classify( filename );
307 if( fc == FC_zip )
308 {
309 WZ_open_archive( filename );
310 return FH_zip_archive;
311 }
312
313 if( archive_z )
314 {
315 return WZ_open_file_z( filename );
316 }
317 else
318 {
319 file_n = fopen( filename, "rb" );
320 if( file_n )
321 return FH_file;
322 }
323 return FH_none;
324 }
325
326 // Close a file.
WZ_close(byte handle)327 void WZ_close( byte handle )
328 {
329 byte fh = handle & FH_mask;
330 if( fh == FH_zip_archive )
331 {
332 WZ_close_archive();
333 }
334 else if( fh == FH_zip_file )
335 {
336 WZ_close_file_z();
337 }
338 else if( file_n )
339 {
340 fclose( file_n );
341 file_n = NULL;
342 }
343 }
344
WZ_close_all(void)345 void WZ_close_all( void )
346 {
347 WZ_close( 0 );
348 WZ_close_file_z();
349 WZ_close_archive();
350 }
351
352
353 // Position a file.
354 // Return seek status, -1 on error.
WZ_seek(byte handle,uint32_t offset)355 int WZ_seek( byte handle, uint32_t offset )
356 {
357 int rs = 0;
358 byte fh = handle & FH_mask;
359 if( (fh == FH_zip_file) && file_z )
360 {
361 #ifdef GEN_ZIP_SEEK
362 // Does not have zip_fseek, use work-around.
363 rs = WZ_zip_fseek( offset );
364 #else
365 // [WDJ] zip_fseek requires ziplib >= 1.2.0
366 rs = zip_fseek( file_z, offset, SEEK_SET );
367 #endif
368 }
369 else if( file_n )
370 {
371 rs = fseek( file_n, offset, SEEK_SET );
372 }
373 return rs;
374 }
375
376 // Read a file.
377 // Return num bytes read, -1 on error.
WZ_read(byte handle,uint32_t read_count,byte * dest)378 int WZ_read( byte handle, uint32_t read_count, /*OUT*/ byte * dest )
379 {
380 int rs = 0;
381 byte fh = handle & FH_mask;
382 if( (fh == FH_zip_file) && file_z )
383 {
384 rs = zip_fread( file_z, dest, read_count );
385
386 #ifdef GEN_ZIP_SEEK
387 // Tracking for WZ_zip_fseek
388 if( rs < 0 )
389 return rs;
390
391 position_z += rs; // update position for fseek
392 #endif
393
394 }
395 else if( file_n )
396 {
397 rs = fread( dest, read_count, 1, file_n ); // 1 item of size read_count
398 // returns num of items read (not bytes)
399 if( rs > 0 )
400 rs = read_count;
401 }
402 return rs;
403 }
404
405
406
407 // ---- ZIP file handling
408
409 // archive_name : zip archive to search
410 // when NULL, search the currently open archive
411 // Return FS_FOUND when file found.
412 // Return FS_NOTFOUND when file not found.
413 // Return other FS when other error.
WZ_find_file_in_archive(const char * filename,const char * archive_name)414 byte WZ_find_file_in_archive( const char * filename, const char * archive_name )
415 {
416 byte result = FS_NOTFOUND;
417
418 if( archive_name )
419 {
420 // Close the current archive, and open this archive.
421 WZ_open_archive( archive_name );
422 }
423
424 if( archive_z )
425 {
426 zip_int64_t zi = zip_name_locate( archive_z, filename, ZIP_FL_NOCASE | ZIP_FL_NODIR ); // index
427 if( zi >= 0 )
428 result = FS_FOUND;
429
430 // if( archive_name )
431 // WZ_close_archive( );
432 }
433 return result;
434 }
435
436
WZ_filesize(const char * filename)437 unsigned int WZ_filesize( const char * filename )
438 {
439 zip_stat_t zipstat;
440 unsigned int filesize = 0;
441
442 #ifdef ZIPWAD_OPTIONAL
443 if( ! ziplib_present )
444 return 0;
445 #endif
446 // Get info about file in archive.
447 // by name or by index
448 int r = zip_stat( archive_z, filename, ZIP_FL_NOCASE | ZIP_FL_NODIR, /*OUT*/ &zipstat );
449 if( r < 0 )
450 {
451 // on failure the error code is in archive_z
452 GenPrintf( EMSG_warn, "WZ_filesize %s: %s\n", filename, WZ_error_from_archive_z() );
453 return -1;
454 }
455
456 if( zipstat.valid & ZIP_STAT_SIZE )
457 filesize = zipstat.size;
458 #if 0
459 if( zipstat.valid & ZIP_STAT_INDEX )
460 index = zipstat.index; // index within archive
461 #endif
462
463 return filesize;
464 }
465
466
467
468 // Read from file in archive_z.
469 // offset : read at offset, optional
470 // read_size : in bytes
471 // dest : dest buffer
472 // return bytes read, or -1 when error
WZ_read_archive_file(uint32_t offset,uint32_t read_size,byte * dest)473 int WZ_read_archive_file( uint32_t offset, uint32_t read_size, /*OUT*/ byte * dest )
474 {
475 // zip_file_t * file_z;
476 const char * msg;
477 int num_read = -1;
478
479 #ifdef ZIPWAD_OPTIONAL
480 if( ! ziplib_present )
481 return -1;
482 #endif
483
484 if( archive_z == NULL )
485 return -1;
486
487 if( file_z == NULL )
488 return -1;
489
490 if( offset ) // ???
491 {
492 int se;
493 #ifdef GEN_ZIP_SEEK
494 // Does not have zip_fseek, use work-around.
495 se = WZ_zip_fseek( offset );
496 #else
497 // [WDJ] zip_fseek requires ziplib >= 1.2.0
498 se = zip_fseek( file_z, offset, SEEK_SET );
499 #endif
500 if( se < 0 )
501 {
502 msg = "Seek";
503 goto print_err;
504 }
505 }
506
507 if( read_size )
508 {
509 num_read = zip_fread( file_z, dest, read_size );
510 if( num_read < 0 )
511 goto file_err;
512
513 #ifdef GEN_ZIP_SEEK
514 // Tracking for WZ_zip_fseek
515 position_z += num_read;
516 #endif
517 }
518
519 return num_read;
520
521 file_err:
522 {
523 zip_error_t * error_z = zip_file_get_error( file_z );
524 msg = zip_error_strerror( error_z );
525 }
526 goto print_err;
527
528 print_err:
529 GenPrintf(EMSG_warn, "WZ_Read %s: offset=%i, size=%i, %s\n", wadfiles[file_filenum]->filename, offset, read_size, msg );
530 return -1;
531 }
532
533 // fn : wadfile filenum
534 // wf : read file wadfile_t, in an archive
535 // offset : read at offset
536 // read_size : in bytes
537 // dest : dest buffer
538 // return bytes read
WZ_read_wadfile_from_archive_file_offset(byte fn,wadfile_t * wf,uint32_t offset,uint32_t read_size,byte * dest)539 int WZ_read_wadfile_from_archive_file_offset( byte fn, wadfile_t * wf, uint32_t offset, uint32_t read_size, /*OUT*/ byte * dest )
540 {
541 wadfile_t * archive_wf = wadfiles[ wf->archive_parent ];
542
543 #ifdef ZIPWAD_OPTIONAL
544 if( ! ziplib_present )
545 return 0;
546 #endif
547
548 if( (archive_z == NULL) || (wf->archive_parent != archive_filenum) )
549 {
550 // Close any current archive, and open the correct one.
551 WZ_open_archive( archive_wf->filename ); // archive_z
552 if( archive_z == NULL )
553 return 0;
554 }
555
556 if( (file_z == NULL) || (file_filenum != fn) )
557 {
558 WZ_open_file_z( wf->filename ); // must perform seek fixes
559 if( file_z == NULL )
560 return 0;
561
562 file_filenum = fn;
563 }
564
565 int br = WZ_read_archive_file( offset, read_size, /*OUT*/ dest );
566 // int br = WZ_read_archive_file( WZC_OPEN | WZC_SEEK | WZC_CLOSE, wf->filename, offset, read_size, /*OUT*/ dest );
567
568 // WZ_close_archive();
569 return br;
570 }
571
572
573 // Important: BLOCKSIZE must be a multiple of 64.
574 #define MD5_BLOCKSIZE 4096
575
576 // This is same as md5_stream, rewritten to access zip files.
577 // For zip file, compute the MD5 message digest.
578 //
579 // digest_block : the md5 digest, as 16 byte array
580 // Return FS_FOUND when success.
WZ_md5_stream(const char * filename,byte * digest_block)581 filestatus_e WZ_md5_stream( const char * filename, byte * digest_block )
582 {
583 zip_file_t * file_z;
584 struct md5_ctx ctx;
585 char buffer[MD5_BLOCKSIZE + 72];
586 size_t readcnt;
587 zip_int64_t n;
588
589 #ifdef ZIPWAD_OPTIONAL
590 if( ! ziplib_present )
591 return FS_INVALID;
592 #endif
593 if( archive_z == NULL )
594 return FS_FILEERR;
595
596 file_z = zip_fopen( archive_z, filename, ZIP_FL_NODIR );
597 if( file_z == NULL )
598 return FS_NOTFOUND; // on failure the error code is in archive_z
599
600 // Initialize the computation context.
601 md5_init_ctx( &ctx );
602
603 // Iterate over full file contents.
604 for(;;)
605 {
606 // Read the zip file in MD5_BLOCKSIZE bytes.
607 // Each call of the computation processes a entire whole buffer.
608 readcnt = 0;
609
610 // Read a block.
611 do
612 {
613 // Does not trust getting a full buffer in one read.
614 n = zip_fread( file_z, buffer + readcnt, MD5_BLOCKSIZE - readcnt );
615 if( n < 0 )
616 goto file_err;
617 // If end of file is reached, end the loop.
618 if( n == 0 )
619 goto eof_reached; // have not reached MD5_BLOCKSIZE
620
621 readcnt += n;
622 }
623 while( readcnt < MD5_BLOCKSIZE );
624
625 // Process buffer with MD5_BLOCKSIZE bytes.
626 // Note that modulo( MD5_BLOCKSIZE, 64 ) == 0
627 md5_process_block( buffer, MD5_BLOCKSIZE, &ctx );
628 }
629
630 eof_reached:
631 // Add the last odd buffer size, if necessary.
632 if( readcnt > 0 )
633 md5_process_bytes( buffer, readcnt, &ctx );
634
635 // Construct result in desired memory.
636 md5_finish_ctx (&ctx, digest_block);
637
638 zip_fclose( file_z );
639 return FS_FOUND;
640
641 file_err:
642 zip_fclose( file_z );
643 return FS_FILEERR;
644 }
645
646
647 // ---- ZIP archive access
648
649
650 // Zip file. Process all files in directory.
651 // Calls wad handler function.
652 // as_archive_filenum : the wadfile index assigned to the archive
653 // Return number of wadfile processed.
654 // Return -1 when problem.
WZ_Load_zip_archive(const char * filename,int as_archive_filenum)655 int WZ_Load_zip_archive( const char * filename, int as_archive_filenum )
656 {
657 zip_stat_t zipstat;
658 int file_count = 0; // nothing loaded
659 int i;
660
661 #ifdef ZIPWAD_OPTIONAL
662 if( ! ziplib_present )
663 return -1;
664 #endif
665
666 zip_stat_init( &zipstat );
667
668 // Open zip
669 WZ_open_archive( filename ); // archive_z
670 if( archive_z == NULL )
671 return -1;
672
673 archive_filenum = as_archive_filenum;
674 for( i=0; i<1024; i++ )
675 {
676 // by name or by index
677 int r = zip_stat_index( archive_z, i, 0, /*OUT*/ &zipstat );
678 if( r < 0 )
679 break; // end of directory (one way or another).
680
681 // Recursive call of Load Wadfile.
682 int fn = W_Load_WadFile( zipstat.name ); // wadfile index
683 if( fn > 0 )
684 file_count++;
685 }
686
687 // close archive_z
688 WZ_close_archive();
689 return file_count;
690 }
691
692 #endif
693
694