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