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