4e38c9458a3153abedf69689b262e750e4afadff
[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                         fclose( f );
320                         return -1;
321                 }
322
323                 fseek( f, 0, SEEK_END );
324                 len = ftell( f );
325                 rewind( f );
326
327                 *bufferptr = safe_malloc( len + 1 );
328                 if ( *bufferptr == NULL ) {
329                         fclose( f );
330                         return -1;
331                 }
332
333                 if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) {
334                         fclose( f );
335                         return -1;
336                 }
337                 fclose( f );
338
339                 // we need to end the buffer with a 0
340                 ( (char*) ( *bufferptr ) )[len] = 0;
341
342                 return len;
343         }
344
345         *bufferptr = NULL;
346         strcpy( fixed, filename );
347         vfsFixDOSName( fixed );
348         lower = g_ascii_strdown( fixed, -1 );
349
350         for ( i = 0; i < g_numDirs; i++ )
351         {
352                 strcpy( tmp, g_strDirs[i] );
353                 strcat( tmp, filename );
354                 if ( access( tmp, R_OK ) == 0 ) {
355                         if ( count == index ) {
356                                 long len;
357                                 FILE *f;
358
359                                 f = fopen( tmp, "rb" );
360                                 if ( f == NULL ) {
361                                         return -1;
362                                 }
363
364                                 fseek( f, 0, SEEK_END );
365                                 len = ftell( f );
366                                 rewind( f );
367
368                                 *bufferptr = safe_malloc( len + 1 );
369                                 if ( *bufferptr == NULL ) {
370                                         fclose( f );
371                                         return -1;
372                                 }
373
374                                 if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) {
375                                         fclose( f );
376                                         return -1;
377                                 }
378                                 fclose( f );
379
380                                 // we need to end the buffer with a 0
381                                 ( (char*) ( *bufferptr ) )[len] = 0;
382
383                                 return len;
384                         }
385
386                         count++;
387                 }
388         }
389
390         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
391         {
392                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
393
394                 if ( strcmp( file->name, lower ) != 0 ) {
395                         continue;
396                 }
397
398                 if ( count == index ) {
399
400                 if ( unzGoToFilePos( file->zipfile, &file->zippos ) != UNZ_OK ) {
401                         return -1;
402                 }
403                         if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
404                                 return -1;
405                         }
406
407                         *bufferptr = safe_malloc( file->size + 1 );
408                         // we need to end the buffer with a 0
409                         ( (char*) ( *bufferptr ) )[file->size] = 0;
410
411                         i = unzReadCurrentFile( file->zipfile, *bufferptr, file->size );
412                         unzCloseCurrentFile( file->zipfile );
413                         if ( i < 0 ) {
414                                 return -1;
415                         }
416                         else{
417                                 g_free( lower );
418                                 return file->size;
419                         }
420                 }
421
422                 count++;
423         }
424         g_free( lower );
425         return -1;
426 }