]> git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/common/vfs.c
Merge commit '19992696033a496e5c0925e950a29dc23de49b47' into master-merge
[xonotic/netradiant.git] / tools / quake3 / common / vfs.c
1 /*
2    Copyright (c) 2001, Loki software, inc.
3    All rights reserved.
4
5    Redistribution and use in source and binary forms, with or without modification,
6    are permitted provided that the following conditions are met:
7
8    Redistributions of source code must retain the above copyright notice, this list
9    of conditions and the following disclaimer.
10
11    Redistributions in binary form must reproduce the above copyright notice, this
12    list of conditions and the following disclaimer in the documentation and/or
13    other materials provided with the distribution.
14
15    Neither the name of Loki software nor the names of its contributors may be used
16    to endorse or promote products derived from this software without specific prior
17    written permission.
18
19    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22    DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23    DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 //
32 // Rules:
33 //
34 // - Directories should be searched in the following order: ~/.q3a/baseq3,
35 //   install dir (/usr/local/games/quake3/baseq3) and cd_path (/mnt/cdrom/baseq3).
36 //
37 // - Pak files are searched first inside the directories.
38 // - Case insensitive.
39 // - Unix-style slashes (/) (windows is backwards .. everyone knows that)
40 //
41 // Leonardo Zide (leo@lokigames.com)
42 //
43
44 #include <string.h>
45 #include <stdlib.h>
46 #include <sys/stat.h>
47
48 #include "cmdlib.h"
49 #include "filematch.h"
50 #include "mathlib.h"
51 #include "inout.h"
52 #include "vfs.h"
53 #include <unzip.h>
54 #include <glib.h>
55 #define BAD_MINIZ
56 #ifndef BAD_MINIZ
57 #include "miniz.h"
58 #endif
59
60 typedef struct
61 {
62         char*   name;
63         unzFile zipfile;
64         unz_file_pos zippos;
65         guint32 size;
66 } VFS_PAKFILE;
67
68 // =============================================================================
69 // Global variables
70
71 static GSList*  g_unzFiles;
72 static GSList*  g_pakFiles;
73 static char g_strDirs[VFS_MAXDIRS][PATH_MAX + 1];
74 static int g_numDirs;
75 char g_strForbiddenDirs[VFS_MAXDIRS][PATH_MAX + 1];
76 int g_numForbiddenDirs = 0;
77 static gboolean g_bUsePak = TRUE;
78
79 // =============================================================================
80 // Static functions
81
82 static void vfsAddSlash( char *str ){
83         int n = strlen( str );
84         if ( n > 0 ) {
85                 if ( str[n - 1] != '\\' && str[n - 1] != '/' ) {
86                         strcat( str, "/" );
87                 }
88         }
89 }
90
91 static void vfsFixDOSName( char *src ){
92         if ( src == NULL ) {
93                 return;
94         }
95
96         while ( *src )
97         {
98                 if ( *src == '\\' ) {
99                         *src = '/';
100                 }
101                 src++;
102         }
103 }
104
105 //!\todo Define globally or use heap-allocated string.
106 #define NAME_MAX 255
107
108 static void vfsInitPakFile( const char *filename ){
109         unz_global_info gi;
110         unzFile uf;
111         guint32 i;
112         int err;
113
114         uf = unzOpen( filename );
115         if ( uf == NULL ) {
116                 return;
117         }
118
119         g_unzFiles = g_slist_append( g_unzFiles, uf );
120
121         err = unzGetGlobalInfo( uf,&gi );
122         if ( err != UNZ_OK ) {
123                 return;
124         }
125         unzGoToFirstFile( uf );
126
127         for ( i = 0; i < gi.number_entry; i++ )
128         {
129                 char filename_inzip[NAME_MAX];
130                 char *filename_lower;
131                 unz_file_info file_info;
132                 VFS_PAKFILE* file;
133
134                 err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof( filename_inzip ), NULL, 0, NULL, 0 );
135                 if ( err != UNZ_OK ) {
136                         break;
137                 }
138                 unz_file_pos pos;
139                 err = unzGetFilePos( uf, &pos );
140                 if ( err != UNZ_OK ) {
141                         break;
142                 }
143
144                 file = (VFS_PAKFILE*)safe_malloc( sizeof( VFS_PAKFILE ) );
145                 g_pakFiles = g_slist_append( g_pakFiles, file );
146
147                 vfsFixDOSName( filename_inzip );
148                  //-1 null terminated string
149                 filename_lower = g_ascii_strdown( filename_inzip, -1 );
150
151                 file->name = strdup( filename_lower );
152                 file->size = file_info.uncompressed_size;
153                 file->zipfile = uf;
154                 file->zippos = pos;
155
156                 if ( ( i + 1 ) < gi.number_entry ) {
157                         err = unzGoToNextFile( uf );
158                         if ( err != UNZ_OK ) {
159                                 break;
160                         }
161                 }
162                 g_free( filename_lower );
163         }
164 }
165
166 // =============================================================================
167 // Global functions
168
169 // reads all pak files from a dir
170 void vfsInitDirectory( const char *path ){
171         char filename[PATH_MAX];
172         char *dirlist;
173         GDir *dir;
174         int j;
175
176         for ( j = 0; j < g_numForbiddenDirs; ++j )
177         {
178                 char* dbuf = g_strdup( path );
179                 if ( *dbuf && dbuf[strlen( dbuf ) - 1] == '/' ) {
180                         dbuf[strlen( dbuf ) - 1] = 0;
181                 }
182                 const char *p = strrchr( dbuf, '/' );
183                 p = ( p ? ( p + 1 ) : dbuf );
184                 if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) {
185                         g_free( dbuf );
186                         break;
187                 }
188                 g_free( dbuf );
189         }
190         if ( j < g_numForbiddenDirs ) {
191                 return;
192         }
193
194         if ( g_numDirs == VFS_MAXDIRS ) {
195                 Sys_FPrintf( SYS_WRN, "WARNING: too many VFS directories, can't init %s\n", path );
196                 return;
197         }
198
199         Sys_Printf( "VFS Init: %s\n", path );
200
201         strncpy( g_strDirs[g_numDirs], path, PATH_MAX );
202         g_strDirs[g_numDirs][PATH_MAX] = 0;
203         vfsFixDOSName( g_strDirs[g_numDirs] );
204         vfsAddSlash( g_strDirs[g_numDirs] );
205         g_numDirs++;
206
207         if ( g_bUsePak ) {
208                 dir = g_dir_open( path, 0, NULL );
209
210                 if ( dir != NULL ) {
211                         while ( 1 )
212                         {
213                                 const char* name = g_dir_read_name( dir );
214                                 if ( name == NULL ) {
215                                         break;
216                                 }
217
218                                 for ( j = 0; j < g_numForbiddenDirs; ++j )
219                                 {
220                                         const char *p = strrchr( name, '/' );
221                                         p = ( p ? ( p + 1 ) : name );
222                                         if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) {
223                                                 break;
224                                         }
225                                 }
226                                 if ( j < g_numForbiddenDirs ) {
227                                         continue;
228                                 }
229
230                                 dirlist = g_strdup( name );
231
232                                 {
233                                         char *ext = strrchr( dirlist, '.' );
234
235                                         if ( ext != NULL && ( !Q_stricmp( ext, ".pk3dir" ) || !Q_stricmp( ext, ".dpkdir" ) ) ) {
236                                                 if ( g_numDirs == VFS_MAXDIRS ) {
237                                                         g_free( dirlist );
238                                                         continue;
239                                                 }
240                                                 snprintf( g_strDirs[g_numDirs], PATH_MAX, "%s/%s", path, name );
241                                                 g_strDirs[g_numDirs][PATH_MAX-1] = '\0';
242                                                 vfsFixDOSName( g_strDirs[g_numDirs] );
243                                                 vfsAddSlash( g_strDirs[g_numDirs] );
244                                                 ++g_numDirs;
245                                         }
246
247                                         if ( ext == NULL || ( Q_stricmp( ext, ".pk3" ) != 0 && Q_stricmp( ext, ".dpk" ) != 0 ) ) {
248                                                 g_free( dirlist );
249                                                 continue;
250                                         }
251                                 }
252
253                                 sprintf( filename, "%s/%s", path, dirlist );
254                                 vfsInitPakFile( filename );
255
256                                 g_free( dirlist );
257                         }
258                         g_dir_close( dir );
259                 }
260         }
261 }
262
263
264 // lists all .shader files
265 void vfsListShaderFiles( char* list, int *num ){
266         //char filename[PATH_MAX];
267         char *dirlist;
268         GDir *dir;
269         int i, k;
270         char path[NAME_MAX];
271 /* search in dirs */
272         for ( i = 0; i < g_numDirs; i++ ){
273                 strncpy( path, g_strDirs[ i ], NAME_MAX );
274                 strcat( path, "scripts/" );
275
276                 dir = g_dir_open( path, 0, NULL );
277
278                 if ( dir != NULL ) {
279                         while ( 1 )
280                         {
281                                 const char* name = g_dir_read_name( dir );
282                                 if ( name == NULL ) {
283                                         break;
284                                 }
285                                 dirlist = g_strdup( name );
286                                 char *ext = strrchr( dirlist, '.' );
287
288                                 if ( ( ext == NULL ) || ( Q_stricmp( ext, ".shader" ) != 0 ) ) {
289                                         continue;
290                                 }
291
292                                 for ( k = 0; k < *num; k++ ){
293                                         if ( !Q_stricmp( list + k*65, dirlist ) ) goto shISdouplicate;
294                                 }
295                                 strcpy( list + (*num)*65, dirlist );
296                                 (*num)++;
297 shISdouplicate:
298                                 g_free( dirlist );
299                         }
300                         g_dir_close( dir );
301                 }
302         }
303         /* search in packs */
304         GSList *lst;
305
306         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
307         {
308                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
309
310                 char *ext = strrchr( file->name, '.' );
311
312                 if ( ( ext == NULL ) || ( Q_stricmp( ext, ".shader" ) != 0 ) ) {
313                         continue;
314                 }
315                 //name + ext this time
316                 ext = strrchr( file->name, '/' );
317                 ext++;
318
319                 for ( k = 0; k < *num; k++ ){
320                         if ( !Q_stricmp( list + k*65, ext ) ) goto shISdouplicate2;
321                 }
322                 strcpy( list + (*num)*65, ext );
323                 (*num)++;
324 shISdouplicate2:
325                 continue;
326         }
327 }
328
329 // frees all memory that we allocated
330 void vfsShutdown(){
331         while ( g_unzFiles )
332         {
333                 unzClose( (unzFile)g_unzFiles->data );
334                 g_unzFiles = g_slist_remove( g_unzFiles, g_unzFiles->data );
335         }
336
337         while ( g_pakFiles )
338         {
339                 VFS_PAKFILE* file = (VFS_PAKFILE*)g_pakFiles->data;
340                 free( file->name );
341                 free( file );
342                 g_pakFiles = g_slist_remove( g_pakFiles, file );
343         }
344 }
345
346 // return the number of files that match
347 int vfsGetFileCount( const char *filename ){
348         int i, count = 0;
349         char fixed[NAME_MAX], tmp[NAME_MAX];
350         char *lower;
351         GSList *lst;
352
353         strcpy( fixed, filename );
354         vfsFixDOSName( fixed );
355         lower = g_ascii_strdown( fixed, -1 );
356
357         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
358         {
359                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
360
361                 if ( strcmp( file->name, lower ) == 0 ) {
362                         count++;
363                 }
364         }
365
366         for ( i = 0; i < g_numDirs; i++ )
367         {
368                 strcpy( tmp, g_strDirs[i] );
369                 strcat( tmp, lower );
370                 if ( access( tmp, R_OK ) == 0 ) {
371                         count++;
372                 }
373         }
374         g_free( lower );
375         return count;
376 }
377
378 static qboolean isSymlink(const unz_file_info64 *fileInfo) {
379         // see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/stat.h
380         // redefine so it works outside of Unices
381         const unsigned long Q3MAP_S_IFMT = 00170000;
382         const unsigned long Q3MAP_S_IFLNK = 0120000;
383         // see https://trac.edgewall.org/attachment/ticket/8919/ZipDownload.patch
384         const unsigned long PKZIP_EXTERNAL_ATTR_FILE_TYPE_SHIFT = 16;
385
386         unsigned long attr = fileInfo->external_fa >> PKZIP_EXTERNAL_ATTR_FILE_TYPE_SHIFT;
387         return (attr & Q3MAP_S_IFMT) == Q3MAP_S_IFLNK;
388 }
389
390 // The zip format has a maximum filename size of 64K
391 static const int MAX_FILENAME_BUF = 65537;
392
393 /* The symlink implementation is ported from Dæmon engine implementation by slipher which was a complete rewrite of one illwieckz did on Dæmon by taking inspiration from Darkplaces engine.
394
395 See:
396
397 - https://github.com/DaemonEngine/Daemon/blob/master/src/common/FileSystem.cpp
398 - https://gitlab.com/xonotic/darkplaces/-/blob/div0-stable/fs.c
399
400 Some words by slipher:
401
402 > Symlinks are a bad feature which you should not use. Therefore, the implementation is as
403 > slow as possible with a full iteration of the archive performed for each symlink.
404
405 > The symlink path `relative` must be relative to the symlink's location.
406 > Only supports paths consisting of "../" 0 or more times, followed by non-magical path components.
407 */
408 void resolveSymlinkPath( const char* base, const char* relative, char* resolved ){
409
410         base = g_path_get_dirname( base );
411
412         while( g_str_has_prefix( relative, "../" ) )
413         {
414                 if ( base[0] == '\0' )
415                 {
416                         Sys_FPrintf( SYS_WRN, "Error while reading symbolic link: \"%s\": no such directory\n", base );
417                         resolved[0] = '\0';
418                         return;
419                 }
420
421                 base = g_path_get_dirname( base );
422                 relative += 3;
423         }
424
425         snprintf( resolved, MAX_FILENAME_BUF, "%s/%s", base, relative);
426 }
427
428 // NOTE: when loading a file, you have to allocate one extra byte and set it to \0
429 int vfsLoadFile( const char *filename, void **bufferptr, int index ){
430         int i, count = 0;
431         char tmp[NAME_MAX], fixed[NAME_MAX];
432         char *lower;
433         GSList *lst;
434
435         // filename is a full path
436         if ( index == -1 ) {
437                 long len;
438                 FILE *f;
439
440                 f = fopen( filename, "rb" );
441                 if ( f == NULL ) {
442                         return -1;
443                 }
444
445                 fseek( f, 0, SEEK_END );
446                 len = ftell( f );
447                 rewind( f );
448
449                 *bufferptr = safe_malloc( len + 1 );
450                 if ( *bufferptr == NULL ) {
451                         fclose( f );
452                         return -1;
453                 }
454
455                 if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) {
456                         fclose( f );
457                         return -1;
458                 }
459                 fclose( f );
460
461                 // we need to end the buffer with a 0
462                 ( (char*) ( *bufferptr ) )[len] = 0;
463
464                 return len;
465         }
466
467         *bufferptr = NULL;
468         strncpy( fixed, filename, sizeof( fixed ) );
469         vfsFixDOSName( fixed );
470         lower = g_ascii_strdown( fixed, -1 );
471
472         for ( i = 0; i < g_numDirs; i++ )
473         {
474                 strcpy( tmp, g_strDirs[i] );
475                 strcat( tmp, filename );
476                 if ( access( tmp, R_OK ) == 0 ) {
477                         if ( count == index ) {
478                                 long len;
479                                 FILE *f;
480
481                                 f = fopen( tmp, "rb" );
482                                 if ( f == NULL ) {
483                                         return -1;
484                                 }
485
486                                 fseek( f, 0, SEEK_END );
487                                 len = ftell( f );
488                                 rewind( f );
489
490                                 *bufferptr = safe_malloc( len + 1 );
491                                 if ( *bufferptr == NULL ) {
492                                         fclose( f );
493                                         return -1;
494                                 }
495
496                                 if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) {
497                                         fclose( f );
498                                         return -1;
499                                 }
500                                 fclose( f );
501
502                                 // we need to end the buffer with a 0
503                                 ( (char*) ( *bufferptr ) )[len] = 0;
504
505                                 return len;
506                         }
507
508                         count++;
509                 }
510         }
511
512         // Do not resolve more than 5 recursive symbolic links to
513         // prevent circular symbolic links.
514         int max_symlink_depth = 5;
515
516         openSymlinkTarget:
517         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
518         {
519                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
520
521                 if ( strcmp( file->name, lower ) != 0 ) {
522                         continue;
523                 }
524
525                 if ( count == index ) {
526
527                 if ( unzGoToFilePos( file->zipfile, &file->zippos ) != UNZ_OK ) {
528                         return -1;
529                 }
530                         if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
531                                 return -1;
532                         }
533
534                         unz_file_info64 fileInfo;
535                         if ( unzGetCurrentFileInfo64( file->zipfile, &fileInfo, filename, sizeof(filename), NULL, 0, NULL, 0 ) != UNZ_OK ) {
536                                 return -1;
537                         }
538
539                         *bufferptr = safe_malloc( file->size + 1 );
540                         // we need to end the buffer with a 0
541                         ( (char*) ( *bufferptr ) )[file->size] = 0;
542
543                         i = unzReadCurrentFile( file->zipfile, *bufferptr, file->size );
544                         unzCloseCurrentFile( file->zipfile );
545
546                         if ( isSymlink( &fileInfo ) ) {
547                                 Sys_FPrintf( SYS_VRB, "Found symbolic link: \"%s\"\n", filename );
548
549                                 if ( max_symlink_depth == 0 ) {
550                                         Sys_FPrintf( SYS_WRN, "Maximum symbolic link depth reached\n" );
551                                         g_free( lower );
552                                         return -1;
553                                 }
554
555                                 max_symlink_depth--;
556
557                                 const char* relative = (const char*) *bufferptr;
558                                 char resolved[MAX_FILENAME_BUF];
559
560                                 resolveSymlinkPath( file->name, relative, resolved );
561
562                                 Sys_FPrintf( SYS_VRB, "Resolved symbolic link: \"%s\"\n", resolved );
563
564                                 g_free( lower );
565                                 strncpy( fixed, resolved, sizeof( fixed ) );
566                                 vfsFixDOSName( fixed );
567                                 lower = g_ascii_strdown( fixed, -1 );
568
569                                 // slow as possible full iteration of the archive
570                                 goto openSymlinkTarget;
571                         }
572
573                         if ( i < 0 ) {
574                                 g_free( lower );
575                                 return -1;
576                         }
577                         else{
578                                 g_free( lower );
579                                 return file->size;
580                         }
581                 }
582
583                 count++;
584         }
585
586         g_free( lower );
587         return -1;
588 }
589
590
591 qboolean vfsPackFile( const char *filename, const char *packname ){
592 #ifndef BAD_MINIZ
593         int i;
594         char tmp[NAME_MAX], fixed[NAME_MAX];
595         GSList *lst;
596
597         byte *bufferptr = NULL;
598         strcpy( fixed, filename );
599         vfsFixDOSName( fixed );
600         g_strdown( fixed );
601
602         for ( i = 0; i < g_numDirs; i++ )
603         {
604                 strcpy( tmp, g_strDirs[i] );
605                 strcat( tmp, filename );
606                 if ( access( tmp, R_OK ) == 0 ) {
607                         if ( access( packname, R_OK ) == 0 ) {
608                                 mz_zip_archive zip;
609                                 memset( &zip, 0, sizeof(zip) );
610                                 mz_zip_reader_init_file( &zip, packname, 0 );
611                                 mz_zip_writer_init_from_reader( &zip, packname );
612
613                                 mz_bool success = MZ_TRUE;
614                                 success &= mz_zip_writer_add_file( &zip, filename, tmp, 0, 0, 10 );
615                                 if ( !success || !mz_zip_writer_finalize_archive( &zip ) ){
616                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
617                                 }
618                                 mz_zip_reader_end( &zip);
619                                 mz_zip_writer_end( &zip );
620                         }
621                         else{
622                                 mz_zip_archive zip;
623                                 memset( &zip, 0, sizeof(zip) );
624                                 if( !mz_zip_writer_init_file( &zip, packname, 0 ) ){
625                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
626                                 }
627                                 mz_bool success = MZ_TRUE;
628                                 success &= mz_zip_writer_add_file( &zip, filename, tmp, 0, 0, 10 );
629                                 if ( !success || !mz_zip_writer_finalize_archive( &zip ) ){
630                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
631                                 }
632                                 mz_zip_writer_end( &zip );
633                         }
634
635                         return qtrue;
636                 }
637         }
638
639         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
640         {
641                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
642
643                 if ( strcmp( file->name, fixed ) != 0 ) {
644                         continue;
645                 }
646
647                 memcpy( file->zipfile, &file->zipinfo, sizeof( unz_s ) );
648
649                 if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
650                         return qfalse;
651                 }
652
653                 bufferptr = safe_malloc( file->size + 1 );
654                 // we need to end the buffer with a 0
655                 ( (char*) ( bufferptr ) )[file->size] = 0;
656
657                 mz_uint16 DOS_time = (mz_uint16)(((file->zipinfo.cur_file_info.tmu_date.tm_hour) << 11) + ((file->zipinfo.cur_file_info.tmu_date.tm_min) << 5) + ((file->zipinfo.cur_file_info.tmu_date.tm_sec) >> 1));
658                 mz_uint16 DOS_date = (mz_uint16)(((file->zipinfo.cur_file_info.tmu_date.tm_year - 1980) << 9) + ((file->zipinfo.cur_file_info.tmu_date.tm_mon + 1) << 5) + file->zipinfo.cur_file_info.tmu_date.tm_mday);
659
660                 i = unzReadCurrentFile( file->zipfile, bufferptr, file->size );
661                 unzCloseCurrentFile( file->zipfile );
662                 if ( i < 0 ) {
663                         return qfalse;
664                 }
665                 else{
666                         mz_bool success = MZ_TRUE;
667                         success &= mz_zip_add_mem_to_archive_file_in_place_with_time( packname, filename, bufferptr, i, 0, 0, 10, DOS_time, DOS_date );
668                                 if ( !success ){
669                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
670                                 }
671                         free( bufferptr );
672                         return qtrue;
673                 }
674         }
675
676         return qfalse;
677 #else
678                 Error( "Disabled because of miniz issue" );
679 #endif
680 }