]> git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/common/vfs.c
Garux: replace undefined stricmp with Q_stricmp
[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 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                 return;
196         }
197
198         Sys_Printf( "VFS Init: %s\n", path );
199
200         strncpy( g_strDirs[g_numDirs], path, PATH_MAX );
201         g_strDirs[g_numDirs][PATH_MAX] = 0;
202         vfsFixDOSName( g_strDirs[g_numDirs] );
203         vfsAddSlash( g_strDirs[g_numDirs] );
204         g_numDirs++;
205
206         if ( g_bUsePak ) {
207                 dir = g_dir_open( path, 0, NULL );
208
209                 if ( dir != NULL ) {
210                         while ( 1 )
211                         {
212                                 const char* name = g_dir_read_name( dir );
213                                 if ( name == NULL ) {
214                                         break;
215                                 }
216
217                                 for ( j = 0; j < g_numForbiddenDirs; ++j )
218                                 {
219                                         const char *p = strrchr( name, '/' );
220                                         p = ( p ? ( p + 1 ) : name );
221                                         if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) {
222                                                 break;
223                                         }
224                                 }
225                                 if ( j < g_numForbiddenDirs ) {
226                                         continue;
227                                 }
228
229                                 dirlist = g_strdup( name );
230
231                                 {
232                                         char *ext = strrchr( dirlist, '.' );
233
234                                         if ( ext != NULL && ( !Q_stricmp( ext, ".pk3dir" ) || !Q_stricmp( ext, ".dpkdir" ) ) ) {
235                                                 if ( g_numDirs == VFS_MAXDIRS ) {
236                                                         continue;
237                                                 }
238                                                 snprintf( g_strDirs[g_numDirs], PATH_MAX, "%s/%s", path, name );
239                                                 g_strDirs[g_numDirs][PATH_MAX-1] = '\0';
240                                                 vfsFixDOSName( g_strDirs[g_numDirs] );
241                                                 vfsAddSlash( g_strDirs[g_numDirs] );
242                                                 ++g_numDirs;
243                                         }
244
245                                         if ( ext == NULL || ( Q_stricmp( ext, ".pk3" ) != 0 && Q_stricmp( ext, ".dpk" ) != 0 ) ) {
246                                                 continue;
247                                         }
248                                 }
249
250                                 sprintf( filename, "%s/%s", path, dirlist );
251                                 vfsInitPakFile( filename );
252
253                                 g_free( dirlist );
254                         }
255                         g_dir_close( dir );
256                 }
257         }
258 }
259
260
261 // lists all .shader files
262 void vfsListShaderFiles( char list[512][64], int *num ){
263         //char filename[PATH_MAX];
264         char *dirlist;
265         GDir *dir;
266         int i, k;
267         char path[NAME_MAX];
268 /* search in dirs */
269         for ( i = 0; i < g_numDirs; i++ ){
270                 strncpy( path, g_strDirs[ i ], NAME_MAX );
271                 strcat( path, "scripts/" );
272
273                 dir = g_dir_open( path, 0, NULL );
274
275                 if ( dir != NULL ) {
276                         while ( 1 )
277                         {
278                                 const char* name = g_dir_read_name( dir );
279                                 if ( name == NULL ) {
280                                         break;
281                                 }
282                                 dirlist = g_strdup( name );
283                                 char *ext = strrchr( dirlist, '.' );
284
285                                 if ( ( ext == NULL ) || ( Q_stricmp( ext, ".shader" ) != 0 ) ) {
286                                         continue;
287                                 }
288
289                                 for ( k = 0; k < *num; k++ ){
290                                         if ( !Q_stricmp( list[k], dirlist ) ) goto shISdouplicate;
291                                 }
292                                 strcpy( list[*num], dirlist );
293                                 (*num)++;
294 shISdouplicate:
295                                 g_free( dirlist );
296                         }
297                         g_dir_close( dir );
298                 }
299         }
300         /* search in packs */
301         GSList *lst;
302
303         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
304         {
305                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
306
307                 char *ext = strrchr( file->name, '.' );
308
309                 if ( ( ext == NULL ) || ( Q_stricmp( ext, ".shader" ) != 0 ) ) {
310                         continue;
311                 }
312                 //name + ext this time
313                 ext = strrchr( file->name, '/' );
314                 ext++;
315
316                 for ( k = 0; k < *num; k++ ){
317                         if ( !Q_stricmp( list[k], ext ) ) goto shISdouplicate2;
318                 }
319                 strcpy( list[*num], ext );
320                 (*num)++;
321 shISdouplicate2:
322                 continue;
323         }
324 }
325
326 // frees all memory that we allocated
327 void vfsShutdown(){
328         while ( g_unzFiles )
329         {
330                 unzClose( (unzFile)g_unzFiles->data );
331                 g_unzFiles = g_slist_remove( g_unzFiles, g_unzFiles->data );
332         }
333
334         while ( g_pakFiles )
335         {
336                 VFS_PAKFILE* file = (VFS_PAKFILE*)g_pakFiles->data;
337                 free( file->name );
338                 free( file );
339                 g_pakFiles = g_slist_remove( g_pakFiles, file );
340         }
341 }
342
343 // return the number of files that match
344 int vfsGetFileCount( const char *filename ){
345         int i, count = 0;
346         char fixed[NAME_MAX], tmp[NAME_MAX];
347         char *lower;
348         GSList *lst;
349
350         strcpy( fixed, filename );
351         vfsFixDOSName( fixed );
352         lower = g_ascii_strdown( fixed, -1 );
353
354         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
355         {
356                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
357
358                 if ( strcmp( file->name, lower ) == 0 ) {
359                         count++;
360                 }
361         }
362
363         for ( i = 0; i < g_numDirs; i++ )
364         {
365                 strcpy( tmp, g_strDirs[i] );
366                 strcat( tmp, lower );
367                 if ( access( tmp, R_OK ) == 0 ) {
368                         count++;
369                 }
370         }
371         g_free( lower );
372         return count;
373 }
374
375 // NOTE: when loading a file, you have to allocate one extra byte and set it to \0
376 int vfsLoadFile( const char *filename, void **bufferptr, int index ){
377         int i, count = 0;
378         char tmp[NAME_MAX], fixed[NAME_MAX];
379         char *lower;
380         GSList *lst;
381
382         // filename is a full path
383         if ( index == -1 ) {
384                 long len;
385                 FILE *f;
386
387                 f = fopen( filename, "rb" );
388                 if ( f == NULL ) {
389                         return -1;
390                 }
391
392                 fseek( f, 0, SEEK_END );
393                 len = ftell( f );
394                 rewind( f );
395
396                 *bufferptr = safe_malloc( len + 1 );
397                 if ( *bufferptr == NULL ) {
398                         fclose( f );
399                         return -1;
400                 }
401
402                 if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) {
403                         fclose( f );
404                         return -1;
405                 }
406                 fclose( f );
407
408                 // we need to end the buffer with a 0
409                 ( (char*) ( *bufferptr ) )[len] = 0;
410
411                 return len;
412         }
413
414         *bufferptr = NULL;
415         strcpy( fixed, filename );
416         vfsFixDOSName( fixed );
417         lower = g_ascii_strdown( fixed, -1 );
418
419         for ( i = 0; i < g_numDirs; i++ )
420         {
421                 strcpy( tmp, g_strDirs[i] );
422                 strcat( tmp, filename );
423                 if ( access( tmp, R_OK ) == 0 ) {
424                         if ( count == index ) {
425                                 long len;
426                                 FILE *f;
427
428                                 f = fopen( tmp, "rb" );
429                                 if ( f == NULL ) {
430                                         return -1;
431                                 }
432
433                                 fseek( f, 0, SEEK_END );
434                                 len = ftell( f );
435                                 rewind( f );
436
437                                 *bufferptr = safe_malloc( len + 1 );
438                                 if ( *bufferptr == NULL ) {
439                                         fclose( f );
440                                         return -1;
441                                 }
442
443                                 if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) {
444                                         fclose( f );
445                                         return -1;
446                                 }
447                                 fclose( f );
448
449                                 // we need to end the buffer with a 0
450                                 ( (char*) ( *bufferptr ) )[len] = 0;
451
452                                 return len;
453                         }
454
455                         count++;
456                 }
457         }
458
459         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
460         {
461                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
462
463                 if ( strcmp( file->name, lower ) != 0 ) {
464                         continue;
465                 }
466
467                 if ( count == index ) {
468
469                 if ( unzGoToFilePos( file->zipfile, &file->zippos ) != UNZ_OK ) {
470                         return -1;
471                 }
472                         if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
473                                 return -1;
474                         }
475
476                         *bufferptr = safe_malloc( file->size + 1 );
477                         // we need to end the buffer with a 0
478                         ( (char*) ( *bufferptr ) )[file->size] = 0;
479
480                         i = unzReadCurrentFile( file->zipfile, *bufferptr, file->size );
481                         unzCloseCurrentFile( file->zipfile );
482                         if ( i < 0 ) {
483                                 return -1;
484                         }
485                         else{
486                                 g_free( lower );
487                                 return file->size;
488                         }
489                 }
490
491                 count++;
492         }
493         g_free( lower );
494         return -1;
495 }
496
497
498 qboolean vfsPackFile( const char *filename, const char *packname ){
499 #ifndef BAD_MINIZ
500         int i;
501         char tmp[NAME_MAX], fixed[NAME_MAX];
502         GSList *lst;
503
504         byte *bufferptr = NULL;
505         strcpy( fixed, filename );
506         vfsFixDOSName( fixed );
507         g_strdown( fixed );
508
509         for ( i = 0; i < g_numDirs; i++ )
510         {
511                 strcpy( tmp, g_strDirs[i] );
512                 strcat( tmp, filename );
513                 if ( access( tmp, R_OK ) == 0 ) {
514                         if ( access( packname, R_OK ) == 0 ) {
515                                 mz_zip_archive zip;
516                                 memset( &zip, 0, sizeof(zip) );
517                                 mz_zip_reader_init_file( &zip, packname, 0 );
518                                 mz_zip_writer_init_from_reader( &zip, packname );
519
520                                 mz_bool success = MZ_TRUE;
521                                 success &= mz_zip_writer_add_file( &zip, filename, tmp, 0, 0, 10 );
522                                 if ( !success || !mz_zip_writer_finalize_archive( &zip ) ){
523                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
524                                 }
525                                 mz_zip_reader_end( &zip);
526                                 mz_zip_writer_end( &zip );
527                         }
528                         else{
529                                 mz_zip_archive zip;
530                                 memset( &zip, 0, sizeof(zip) );
531                                 if( !mz_zip_writer_init_file( &zip, packname, 0 ) ){
532                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
533                                 }
534                                 mz_bool success = MZ_TRUE;
535                                 success &= mz_zip_writer_add_file( &zip, filename, tmp, 0, 0, 10 );
536                                 if ( !success || !mz_zip_writer_finalize_archive( &zip ) ){
537                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
538                                 }
539                                 mz_zip_writer_end( &zip );
540                         }
541
542                         return qtrue;
543                 }
544         }
545
546         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
547         {
548                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
549
550                 if ( strcmp( file->name, fixed ) != 0 ) {
551                         continue;
552                 }
553
554                 memcpy( file->zipfile, &file->zipinfo, sizeof( unz_s ) );
555
556                 if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
557                         return qfalse;
558                 }
559
560                 bufferptr = safe_malloc( file->size + 1 );
561                 // we need to end the buffer with a 0
562                 ( (char*) ( bufferptr ) )[file->size] = 0;
563
564                 i = unzReadCurrentFile( file->zipfile, bufferptr, file->size );
565                 unzCloseCurrentFile( file->zipfile );
566                 if ( i < 0 ) {
567                         return qfalse;
568                 }
569                 else{
570                         mz_bool success = MZ_TRUE;
571                         success &= mz_zip_add_mem_to_archive_file_in_place( packname, filename, bufferptr, i, 0, 0, 10 );
572                                 if ( !success ){
573                                         Error( "Failed creating zip archive \"%s\"!\n", packname );
574                                 }
575                         free( bufferptr );
576                         return qtrue;
577                 }
578         }
579
580         return qfalse;
581 #else
582                 Error( "Disabled because of miniz issue" );
583 #endif
584 }