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