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