]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/common/vfs.c
Merge commit 'bf6dd1f2d186c799adf11f1e744a1ff57aa8d335' into garux-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 <minizip/unzip.h>
54 #include <glib.h>
55 #define GARUX_DISABLE_BAD_MINIZ
56 #ifndef GARUX_DISABLE_BAD_MINIZ
57 #include "miniz.h"
58 #endif
59
60 typedef struct
61 {
62         char* unzFilePath;
63         char*   name;
64         unzFile zipfile;
65         unz_file_pos zippos;
66         guint32 size;
67 } VFS_PAKFILE;
68
69 // =============================================================================
70 // Global variables
71
72 static GSList*  g_unzFiles;
73 static GSList*  g_pakFiles;
74 static char g_strDirs[VFS_MAXDIRS][PATH_MAX + 1];
75 static int g_numDirs;
76 char g_strForbiddenDirs[VFS_MAXDIRS][PATH_MAX + 1];
77 int g_numForbiddenDirs = 0;
78 static gboolean g_bUsePak = TRUE;
79 char g_strLoadedFileLocation[1024];
80
81 // =============================================================================
82 // Static functions
83
84 static void vfsAddSlash( char *str ){
85         int n = strlen( str );
86         if ( n > 0 ) {
87                 if ( str[n - 1] != '\\' && str[n - 1] != '/' ) {
88                         strcat( str, "/" );
89                 }
90         }
91 }
92
93 static void vfsFixDOSName( char *src ){
94         if ( src == NULL ) {
95                 return;
96         }
97
98         while ( *src )
99         {
100                 if ( *src == '\\' ) {
101                         *src = '/';
102                 }
103                 src++;
104         }
105 }
106
107 //!\todo Define globally or use heap-allocated string.
108 #define NAME_MAX 255
109
110 static void vfsInitPakFile( const char *filename ){
111         unz_global_info gi;
112         unzFile uf;
113         guint32 i;
114         int err;
115
116         uf = unzOpen( filename );
117         if ( uf == NULL ) {
118                 return;
119         }
120
121         g_unzFiles = g_slist_append( g_unzFiles, uf );
122
123         err = unzGetGlobalInfo( uf,&gi );
124         if ( err != UNZ_OK ) {
125                 return;
126         }
127         unzGoToFirstFile( uf );
128
129         char* unzFilePath = strdup( filename );
130
131         for ( i = 0; i < gi.number_entry; i++ )
132         {
133                 char filename_inzip[NAME_MAX];
134                 char *filename_lower;
135                 unz_file_info file_info;
136                 VFS_PAKFILE* file;
137
138                 err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof( filename_inzip ), NULL, 0, NULL, 0 );
139                 if ( err != UNZ_OK ) {
140                         break;
141                 }
142                 unz_file_pos pos;
143                 err = unzGetFilePos( uf, &pos );
144                 if ( err != UNZ_OK ) {
145                         break;
146                 }
147
148                 file = (VFS_PAKFILE*)safe_malloc( sizeof( VFS_PAKFILE ) );
149                 g_pakFiles = g_slist_append( g_pakFiles, file );
150
151                 vfsFixDOSName( filename_inzip );
152                  //-1 null terminated string
153                 filename_lower = g_ascii_strdown( filename_inzip, -1 );
154
155                 file->name = strdup( filename_lower );
156                 file->size = file_info.uncompressed_size;
157                 file->zipfile = uf;
158                 file->unzFilePath = unzFilePath;
159                 file->zippos = pos;
160
161                 if ( ( i + 1 ) < gi.number_entry ) {
162                         err = unzGoToNextFile( uf );
163                         if ( err != UNZ_OK ) {
164                                 break;
165                         }
166                 }
167                 g_free( filename_lower );
168         }
169 }
170
171 // =============================================================================
172 // Global functions
173
174 // reads all pak files from a dir
175 void vfsInitDirectory( const char *path ){
176         char filename[PATH_MAX];
177         char *dirlist;
178         GDir *dir;
179         int j;
180
181         for ( j = 0; j < g_numForbiddenDirs; ++j )
182         {
183                 char* dbuf = g_strdup( path );
184                 if ( *dbuf && dbuf[strlen( dbuf ) - 1] == '/' ) {
185                         dbuf[strlen( dbuf ) - 1] = 0;
186                 }
187                 const char *p = strrchr( dbuf, '/' );
188                 p = ( p ? ( p + 1 ) : dbuf );
189                 if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) {
190                         g_free( dbuf );
191                         break;
192                 }
193                 g_free( dbuf );
194         }
195         if ( j < g_numForbiddenDirs ) {
196                 return;
197         }
198
199         if ( g_numDirs == VFS_MAXDIRS ) {
200                 return;
201         }
202
203         Sys_Printf( "VFS Init: %s\n", path );
204
205         strncpy( g_strDirs[g_numDirs], path, PATH_MAX );
206         g_strDirs[g_numDirs][PATH_MAX] = 0;
207         vfsFixDOSName( g_strDirs[g_numDirs] );
208         vfsAddSlash( g_strDirs[g_numDirs] );
209         g_numDirs++;
210
211         if ( g_bUsePak ) {
212                 dir = g_dir_open( path, 0, NULL );
213
214                 if ( dir != NULL ) {
215                         while ( 1 )
216                         {
217                                 const char* name = g_dir_read_name( dir );
218                                 if ( name == NULL ) {
219                                         break;
220                                 }
221
222                                 for ( j = 0; j < g_numForbiddenDirs; ++j )
223                                 {
224                                         const char *p = strrchr( name, '/' );
225                                         p = ( p ? ( p + 1 ) : name );
226                                         if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) {
227                                                 break;
228                                         }
229                                 }
230                                 if ( j < g_numForbiddenDirs ) {
231                                         continue;
232                                 }
233
234                                 dirlist = g_strdup( name );
235
236                                 {
237                                         char *ext = strrchr( dirlist, '.' );
238
239                                         if ( ext != NULL && ( !Q_stricmp( ext, ".pk3dir" ) || !Q_stricmp( ext, ".dpkdir" ) ) ) {
240                                                 if ( g_numDirs == VFS_MAXDIRS ) {
241                                                         continue;
242                                                 }
243                                                 snprintf( g_strDirs[g_numDirs], PATH_MAX, "%s/%s", path, name );
244                                                 g_strDirs[g_numDirs][PATH_MAX-1] = '\0';
245                                                 vfsFixDOSName( g_strDirs[g_numDirs] );
246                                                 vfsAddSlash( g_strDirs[g_numDirs] );
247                                                 ++g_numDirs;
248                                         }
249
250                                         if ( ext == NULL || ( Q_stricmp( ext, ".pk3" ) != 0 && Q_stricmp( ext, ".dpk" ) != 0 ) ) {
251                                                 continue;
252                                         }
253                                 }
254
255                                 sprintf( filename, "%s/%s", path, dirlist );
256                                 vfsInitPakFile( filename );
257
258                                 g_free( dirlist );
259                         }
260                         g_dir_close( dir );
261                 }
262         }
263 }
264
265
266 // lists all .shader files
267 void vfsListShaderFiles( char* list, int *num ){
268         //char filename[PATH_MAX];
269         char *dirlist;
270         GDir *dir;
271         int i, k;
272         char path[NAME_MAX];
273 /* search in dirs */
274         for ( i = 0; i < g_numDirs; i++ ){
275                 strncpy( path, g_strDirs[ i ], NAME_MAX );
276                 strcat( path, "scripts/" );
277
278                 dir = g_dir_open( path, 0, NULL );
279
280                 if ( dir != NULL ) {
281                         while ( 1 )
282                         {
283                                 const char* name = g_dir_read_name( dir );
284                                 if ( name == NULL ) {
285                                         break;
286                                 }
287                                 dirlist = g_strdup( name );
288                                 char *ext = strrchr( dirlist, '.' );
289
290                                 if ( ( ext == NULL ) || ( Q_stricmp( ext, ".shader" ) != 0 ) ) {
291                                         continue;
292                                 }
293
294                                 for ( k = 0; k < *num; k++ ){
295                                         if ( !Q_stricmp( list + k*65, dirlist ) ) goto shISdouplicate;
296                                 }
297                                 strcpy( list + (*num)*65, dirlist );
298                                 (*num)++;
299 shISdouplicate:
300                                 g_free( dirlist );
301                         }
302                         g_dir_close( dir );
303                 }
304         }
305         /* search in packs */
306         GSList *lst;
307
308         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
309         {
310                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
311
312                 char *ext = strrchr( file->name, '.' );
313
314                 if ( ( ext == NULL ) || ( Q_stricmp( ext, ".shader" ) != 0 ) ) {
315                         continue;
316                 }
317                 //name + ext this time
318                 ext = strrchr( file->name, '/' );
319                 ext++;
320
321                 for ( k = 0; k < *num; k++ ){
322                         if ( !Q_stricmp( list + k*65, ext ) ) goto shISdouplicate2;
323                 }
324                 strcpy( list + (*num)*65, ext );
325                 (*num)++;
326 shISdouplicate2:
327                 continue;
328         }
329 }
330
331 // frees all memory that we allocated
332 void vfsShutdown(){
333         while ( g_unzFiles )
334         {
335                 unzClose( (unzFile)g_unzFiles->data );
336                 g_unzFiles = g_slist_remove( g_unzFiles, g_unzFiles->data );
337         }
338
339         while ( g_pakFiles )
340         {
341                 VFS_PAKFILE* file = (VFS_PAKFILE*)g_pakFiles->data;
342                 free( file->unzFilePath );
343                 free( file->name );
344                 free( file );
345                 g_pakFiles = g_slist_remove( g_pakFiles, file );
346         }
347 }
348
349 // return the number of files that match
350 int vfsGetFileCount( const char *filename ){
351         int i, count = 0;
352         char fixed[NAME_MAX], tmp[NAME_MAX];
353         char *lower;
354         GSList *lst;
355
356         strcpy( fixed, filename );
357         vfsFixDOSName( fixed );
358         lower = g_ascii_strdown( fixed, -1 );
359
360         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
361         {
362                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
363
364                 if ( strcmp( file->name, lower ) == 0 ) {
365                         count++;
366                 }
367         }
368
369         for ( i = 0; i < g_numDirs; i++ )
370         {
371                 strcpy( tmp, g_strDirs[i] );
372                 strcat( tmp, lower );
373                 if ( access( tmp, R_OK ) == 0 ) {
374                         count++;
375                 }
376         }
377         g_free( lower );
378         return count;
379 }
380
381 // NOTE: when loading a file, you have to allocate one extra byte and set it to \0
382 int vfsLoadFile( const char *filename, void **bufferptr, int index ){
383         int i, count = 0;
384         char tmp[NAME_MAX], fixed[NAME_MAX];
385         char *lower;
386         GSList *lst;
387
388         // filename is a full path
389         if ( index == -1 ) {
390                 strcpy( g_strLoadedFileLocation, filename );
391                 long len;
392                 FILE *f;
393
394                 f = fopen( filename, "rb" );
395                 if ( f == NULL ) {
396                         return -1;
397                 }
398
399                 fseek( f, 0, SEEK_END );
400                 len = ftell( f );
401                 rewind( f );
402
403                 *bufferptr = safe_malloc( len + 1 );
404                 if ( *bufferptr == NULL ) {
405                         fclose( f );
406                         return -1;
407                 }
408
409                 if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) {
410                         fclose( f );
411                         return -1;
412                 }
413                 fclose( f );
414
415                 // we need to end the buffer with a 0
416                 ( (char*) ( *bufferptr ) )[len] = 0;
417
418                 return len;
419         }
420
421         *bufferptr = NULL;
422         strcpy( fixed, filename );
423         vfsFixDOSName( fixed );
424         lower = g_ascii_strdown( fixed, -1 );
425
426         for ( i = 0; i < g_numDirs; i++ )
427         {
428                 strcpy( tmp, g_strDirs[i] );
429                 strcat( tmp, filename );
430                 if ( access( tmp, R_OK ) == 0 ) {
431                         if ( count == index ) {
432                                 strcpy( g_strLoadedFileLocation, tmp );
433
434                                 long len;
435                                 FILE *f;
436
437                                 f = fopen( tmp, "rb" );
438                                 if ( f == NULL ) {
439                                         return -1;
440                                 }
441
442                                 fseek( f, 0, SEEK_END );
443                                 len = ftell( f );
444                                 rewind( f );
445
446                                 *bufferptr = safe_malloc( len + 1 );
447                                 if ( *bufferptr == NULL ) {
448                                         fclose( f );
449                                         return -1;
450                                 }
451
452                                 if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) {
453                                         fclose( f );
454                                         return -1;
455                                 }
456                                 fclose( f );
457
458                                 // we need to end the buffer with a 0
459                                 ( (char*) ( *bufferptr ) )[len] = 0;
460
461                                 return len;
462                         }
463
464                         count++;
465                 }
466         }
467
468         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
469         {
470                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
471
472                 if ( strcmp( file->name, lower ) != 0 ) {
473                         continue;
474                 }
475
476                 if ( count == index ) {
477                         strcpy( g_strLoadedFileLocation, file->unzFilePath );
478                         strcat( g_strLoadedFileLocation, " :: " );
479                         strcat( g_strLoadedFileLocation, filename );
480
481
482                 if ( unzGoToFilePos( file->zipfile, &file->zippos ) != UNZ_OK ) {
483                         return -1;
484                 }
485                         if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
486                                 return -1;
487                         }
488
489                         *bufferptr = safe_malloc( file->size + 1 );
490                         // we need to end the buffer with a 0
491                         ( (char*) ( *bufferptr ) )[file->size] = 0;
492
493                         i = unzReadCurrentFile( file->zipfile, *bufferptr, file->size );
494                         unzCloseCurrentFile( file->zipfile );
495                         if ( i < 0 ) {
496                                 return -1;
497                         }
498                         else{
499                                 g_free( lower );
500                                 return file->size;
501                         }
502                 }
503
504                 count++;
505         }
506         g_free( lower );
507         return -1;
508 }
509
510
511 qboolean vfsPackFile( const char *filename, const char *packname, const int compLevel ){
512 #ifndef GARUX_DISABLE_BAD_MINIZ
513         int i;
514         char tmp[NAME_MAX], fixed[NAME_MAX];
515         GSList *lst;
516
517         byte *bufferptr = NULL;
518         strcpy( fixed, filename );
519         vfsFixDOSName( fixed );
520         g_strdown( fixed );
521
522         for ( i = 0; i < g_numDirs; i++ )
523         {
524                 strcpy( tmp, g_strDirs[i] );
525                 strcat( tmp, filename );
526                 if ( access( tmp, R_OK ) == 0 ) {
527                         if ( access( packname, R_OK ) == 0 ) {
528                                 mz_zip_archive zip;
529                                 memset( &zip, 0, sizeof(zip) );
530                                 mz_zip_reader_init_file( &zip, packname, 0 );
531                                 mz_zip_writer_init_from_reader( &zip, packname );
532
533                                 mz_bool success = MZ_TRUE;
534                                 success &= mz_zip_writer_add_file( &zip, filename, tmp, 0, 0, compLevel );
535                                 if ( !success || !mz_zip_writer_finalize_archive( &zip ) ){
536                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
537                                 }
538                                 mz_zip_reader_end( &zip);
539                                 mz_zip_writer_end( &zip );
540                         }
541                         else{
542                                 mz_zip_archive zip;
543                                 memset( &zip, 0, sizeof(zip) );
544                                 if( !mz_zip_writer_init_file( &zip, packname, 0 ) ){
545                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
546                                 }
547                                 mz_bool success = MZ_TRUE;
548                                 success &= mz_zip_writer_add_file( &zip, filename, tmp, 0, 0, compLevel );
549                                 if ( !success || !mz_zip_writer_finalize_archive( &zip ) ){
550                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
551                                 }
552                                 mz_zip_writer_end( &zip );
553                         }
554
555                         return qtrue;
556                 }
557         }
558
559         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
560         {
561                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
562
563                 if ( strcmp( file->name, fixed ) != 0 ) {
564                         continue;
565                 }
566
567                 memcpy( file->zipfile, &file->zipinfo, sizeof( unz_s ) );
568
569                 if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
570                         return qfalse;
571                 }
572
573                 bufferptr = safe_malloc( file->size + 1 );
574                 // we need to end the buffer with a 0
575                 ( (char*) ( bufferptr ) )[file->size] = 0;
576
577                 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));
578                 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);
579
580                 i = unzReadCurrentFile( file->zipfile, bufferptr, file->size );
581                 unzCloseCurrentFile( file->zipfile );
582                 if ( i < 0 ) {
583                         return qfalse;
584                 }
585                 else{
586                         mz_bool success = MZ_TRUE;
587                         success &= mz_zip_add_mem_to_archive_file_in_place_with_time( packname, filename, bufferptr, i, 0, 0, compLevel, DOS_time, DOS_date );
588                                 if ( !success ){
589                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
590                                 }
591                         free( bufferptr );
592                         return qtrue;
593                 }
594         }
595
596         return qfalse;
597 #else
598                 Error( "Disabled because of miniz issue" );
599 #endif
600 }
601
602 qboolean vfsPackFile_Absolute_Path( const char *filepath, const char *filename, const char *packname, const int compLevel ){
603 #ifndef GARUX_DISABLE_BAD_MINIZ
604         char tmp[NAME_MAX];
605         strcpy( tmp, filepath );
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, compLevel );
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, compLevel );
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         return qfalse;
639 #else
640                 Error( "Disabled because of miniz issue" );
641 #endif
642 }