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