]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/common/vfs.c
Merge branch 'master' into Mario/winfix
[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
56 typedef struct
57 {
58         char*   name;
59         unzFile zipfile;
60         unz_file_pos zippos;
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                 char *filename_lower;
127                 unz_file_info file_info;
128                 VFS_PAKFILE* file;
129
130                 err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof( filename_inzip ), NULL, 0, NULL, 0 );
131                 if ( err != UNZ_OK ) {
132                         break;
133                 }
134                 unz_file_pos pos;
135                 err = unzGetFilePos( uf, &pos );
136                 if ( err != UNZ_OK ) {
137                         break;
138                 }
139
140                 file = (VFS_PAKFILE*)safe_malloc( sizeof( VFS_PAKFILE ) );
141                 g_pakFiles = g_slist_append( g_pakFiles, file );
142
143                 vfsFixDOSName( filename_inzip );
144                  //-1 null terminated string
145                 filename_lower = g_ascii_strdown( filename_inzip, -1 );
146
147                 file->name = strdup( filename_lower );
148                 file->size = file_info.uncompressed_size;
149                 file->zipfile = uf;
150                 file->zippos = pos;
151
152                 if ( ( i + 1 ) < gi.number_entry ) {
153                         err = unzGoToNextFile( uf );
154                         if ( err != UNZ_OK ) {
155                                 break;
156                         }
157                 }
158                 g_free( filename_lower );
159         }
160 }
161
162 // =============================================================================
163 // Global functions
164
165 // reads all pak files from a dir
166 void vfsInitDirectory( const char *path ){
167         char filename[PATH_MAX];
168         char *dirlist;
169         GDir *dir;
170         int j;
171
172         for ( j = 0; j < g_numForbiddenDirs; ++j )
173         {
174                 char* dbuf = g_strdup( path );
175                 if ( *dbuf && dbuf[strlen( dbuf ) - 1] == '/' ) {
176                         dbuf[strlen( dbuf ) - 1] = 0;
177                 }
178                 const char *p = strrchr( dbuf, '/' );
179                 p = ( p ? ( p + 1 ) : dbuf );
180                 if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) {
181                         g_free( dbuf );
182                         break;
183                 }
184                 g_free( dbuf );
185         }
186         if ( j < g_numForbiddenDirs ) {
187                 return;
188         }
189
190         if ( g_numDirs == VFS_MAXDIRS ) {
191                 return;
192         }
193
194         Sys_Printf( "VFS Init: %s\n", path );
195
196         strncpy( g_strDirs[g_numDirs], path, PATH_MAX );
197         g_strDirs[g_numDirs][PATH_MAX] = 0;
198         vfsFixDOSName( g_strDirs[g_numDirs] );
199         vfsAddSlash( g_strDirs[g_numDirs] );
200         g_numDirs++;
201
202         if ( g_bUsePak ) {
203                 dir = g_dir_open( path, 0, NULL );
204
205                 if ( dir != NULL ) {
206                         while ( 1 )
207                         {
208                                 const char* name = g_dir_read_name( dir );
209                                 if ( name == NULL ) {
210                                         break;
211                                 }
212
213                                 for ( j = 0; j < g_numForbiddenDirs; ++j )
214                                 {
215                                         const char *p = strrchr( name, '/' );
216                                         p = ( p ? ( p + 1 ) : name );
217                                         if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) {
218                                                 break;
219                                         }
220                                 }
221                                 if ( j < g_numForbiddenDirs ) {
222                                         continue;
223                                 }
224
225                                 dirlist = g_strdup( name );
226
227                                 {
228                                         char *ext = strrchr( dirlist, '.' );
229
230                                         if ( ext != NULL && ( !Q_stricmp( ext, ".pk3dir" ) || !Q_stricmp( ext, ".dpkdir" ) ) ) {
231                                                 if ( g_numDirs == VFS_MAXDIRS ) {
232                                                         continue;
233                                                 }
234                                                 snprintf( g_strDirs[g_numDirs], PATH_MAX, "%s/%s", path, name );
235                                                 g_strDirs[g_numDirs][PATH_MAX-1] = '\0';
236                                                 vfsFixDOSName( g_strDirs[g_numDirs] );
237                                                 vfsAddSlash( g_strDirs[g_numDirs] );
238                                                 ++g_numDirs;
239                                         }
240
241                                         if ( ext == NULL || ( Q_stricmp( ext, ".pk3" ) != 0 && Q_stricmp( ext, ".dpk" ) != 0 ) ) {
242                                                 continue;
243                                         }
244                                 }
245
246                                 sprintf( filename, "%s/%s", path, dirlist );
247                                 vfsInitPakFile( filename );
248
249                                 g_free( dirlist );
250                         }
251                         g_dir_close( dir );
252                 }
253         }
254 }
255
256 // frees all memory that we allocated
257 void vfsShutdown(){
258         while ( g_unzFiles )
259         {
260                 unzClose( (unzFile)g_unzFiles->data );
261                 g_unzFiles = g_slist_remove( g_unzFiles, g_unzFiles->data );
262         }
263
264         while ( g_pakFiles )
265         {
266                 VFS_PAKFILE* file = (VFS_PAKFILE*)g_pakFiles->data;
267                 free( file->name );
268                 free( file );
269                 g_pakFiles = g_slist_remove( g_pakFiles, file );
270         }
271 }
272
273 // return the number of files that match
274 int vfsGetFileCount( const char *filename ){
275         int i, count = 0;
276         char fixed[NAME_MAX], tmp[NAME_MAX];
277         char *lower;
278         GSList *lst;
279
280         strcpy( fixed, filename );
281         vfsFixDOSName( fixed );
282         lower = g_ascii_strdown( fixed, -1 );
283
284         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
285         {
286                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
287
288                 if ( strcmp( file->name, lower ) == 0 ) {
289                         count++;
290                 }
291         }
292
293         for ( i = 0; i < g_numDirs; i++ )
294         {
295                 strcpy( tmp, g_strDirs[i] );
296                 strcat( tmp, lower );
297                 if ( access( tmp, R_OK ) == 0 ) {
298                         count++;
299                 }
300         }
301         g_free( lower );
302         return count;
303 }
304
305 // NOTE: when loading a file, you have to allocate one extra byte and set it to \0
306 int vfsLoadFile( const char *filename, void **bufferptr, int index ){
307         int i, count = 0;
308         char tmp[NAME_MAX], fixed[NAME_MAX];
309         char *lower;
310         GSList *lst;
311
312         // filename is a full path
313         if ( index == -1 ) {
314                 long len;
315                 FILE *f;
316
317                 f = fopen( filename, "rb" );
318                 if ( f == NULL ) {
319                         return -1;
320                 }
321
322                 fseek( f, 0, SEEK_END );
323                 len = ftell( f );
324                 rewind( f );
325
326                 *bufferptr = safe_malloc( len + 1 );
327                 if ( *bufferptr == NULL ) {
328                         fclose( f );
329                         return -1;
330                 }
331
332                 if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) {
333                         fclose( f );
334                         return -1;
335                 }
336                 fclose( f );
337
338                 // we need to end the buffer with a 0
339                 ( (char*) ( *bufferptr ) )[len] = 0;
340
341                 return len;
342         }
343
344         *bufferptr = NULL;
345         strcpy( fixed, filename );
346         vfsFixDOSName( fixed );
347         lower = g_ascii_strdown( fixed, -1 );
348
349         for ( i = 0; i < g_numDirs; i++ )
350         {
351                 strcpy( tmp, g_strDirs[i] );
352                 strcat( tmp, filename );
353                 if ( access( tmp, R_OK ) == 0 ) {
354                         if ( count == index ) {
355                                 long len;
356                                 FILE *f;
357
358                                 f = fopen( tmp, "rb" );
359                                 if ( f == NULL ) {
360                                         return -1;
361                                 }
362
363                                 fseek( f, 0, SEEK_END );
364                                 len = ftell( f );
365                                 rewind( f );
366
367                                 *bufferptr = safe_malloc( len + 1 );
368                                 if ( *bufferptr == NULL ) {
369                                         fclose( f );
370                                         return -1;
371                                 }
372
373                                 if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) {
374                                         fclose( f );
375                                         return -1;
376                                 }
377                                 fclose( f );
378
379                                 // we need to end the buffer with a 0
380                                 ( (char*) ( *bufferptr ) )[len] = 0;
381
382                                 return len;
383                         }
384
385                         count++;
386                 }
387         }
388
389         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
390         {
391                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
392
393                 if ( strcmp( file->name, lower ) != 0 ) {
394                         continue;
395                 }
396
397                 if ( count == index ) {
398
399                 if ( unzGoToFilePos( file->zipfile, &file->zippos ) != UNZ_OK ) {
400                         return -1;
401                 }
402                         if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
403                                 return -1;
404                         }
405
406                         *bufferptr = safe_malloc( file->size + 1 );
407                         // we need to end the buffer with a 0
408                         ( (char*) ( *bufferptr ) )[file->size] = 0;
409
410                         i = unzReadCurrentFile( file->zipfile, *bufferptr, file->size );
411                         unzCloseCurrentFile( file->zipfile );
412                         if ( i < 0 ) {
413                                 return -1;
414                         }
415                         else{
416                                 g_free( lower );
417                                 return file->size;
418                         }
419                 }
420
421                 count++;
422         }
423         g_free( lower );
424         return -1;
425 }