vfspk3 in q3map2: also support -fs_forbiddenpath
[divverent/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 "mathlib.h"
50 #include <glib.h>
51 #include "inout.h"
52 #include "vfs.h"
53 #include "unzip.h"
54
55 typedef struct
56 {
57   char*   name;
58   unz_s   zipinfo;
59   unzFile zipfile;
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 {
79   int n = strlen (str);
80   if (n > 0)
81   {
82     if (str[n-1] != '\\' && str[n-1] != '/')
83       strcat (str, "/");
84   }
85 }
86
87 static void vfsFixDOSName (char *src)
88 {
89   if (src == NULL)
90     return;
91   
92   while (*src)
93   {
94     if (*src == '\\')
95       *src = '/';
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 {
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   g_unzFiles = g_slist_append (g_unzFiles, uf);
115   
116   err = unzGetGlobalInfo (uf,&gi);
117   if (err != UNZ_OK)
118     return;
119   unzGoToFirstFile(uf);
120   
121   for (i = 0; i < gi.number_entry; i++)
122   {
123     char filename_inzip[NAME_MAX];
124     unz_file_info file_info;
125     VFS_PAKFILE* file;
126     
127     err = unzGetCurrentFileInfo (uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
128     if (err != UNZ_OK)
129       break;
130     
131     file = (VFS_PAKFILE*)safe_malloc (sizeof (VFS_PAKFILE));
132     g_pakFiles = g_slist_append (g_pakFiles, file);
133     
134     vfsFixDOSName (filename_inzip);
135     g_strdown (filename_inzip);
136     
137     file->name = strdup (filename_inzip);
138     file->size = file_info.uncompressed_size;
139     file->zipfile = uf;
140     memcpy (&file->zipinfo, uf, sizeof (unz_s));
141     
142     if ((i+1) < gi.number_entry)
143     {
144       err = unzGoToNextFile(uf);
145       if (err!=UNZ_OK)
146         break;
147     }
148   }
149 }
150
151 // =============================================================================
152 // Global functions
153
154 // reads all pak files from a dir
155 void vfsInitDirectory (const char *path)
156 {
157   char filename[PATH_MAX];
158   char *dirlist;
159   GDir *dir;
160   int j;
161
162   for(j = 0; j < g_numForbiddenDirs; ++j)
163   {
164     if(!Q_stricmp(path, g_strForbiddenDirs[j])
165     || (strlen(path) > strlen(g_strForbiddenDirs[j]) && path[strlen(path) - strlen(g_strForbiddenDirs[j]) - 1] == '/' && !Q_stricmp(path + strlen(path) - strlen(g_strForbiddenDirs[j]), g_strForbiddenDirs[j])))
166       break;
167   }
168   if(j < g_numForbiddenDirs)
169     return;
170   
171   if (g_numDirs == VFS_MAXDIRS)
172     return;
173   
174   Sys_Printf ("VFS Init: %s\n", path);
175   
176   strncpy (g_strDirs[g_numDirs], path, PATH_MAX);
177   g_strDirs[g_numDirs][PATH_MAX] = 0;
178   vfsFixDOSName (g_strDirs[g_numDirs]);
179   vfsAddSlash (g_strDirs[g_numDirs]);
180   g_numDirs++;
181   
182   if (g_bUsePak)
183   {
184     dir = g_dir_open (path, 0, NULL);
185
186     if (dir != NULL)
187     {
188       while (1)
189       {
190         const char* name = g_dir_read_name(dir);
191         if(name == NULL)
192           break;
193
194         for(j = 0; j < g_numForbiddenDirs; ++j)
195           if(!Q_stricmp(name, g_strForbiddenDirs[j]))
196             break;
197         if(j < g_numForbiddenDirs)
198           continue;
199
200         dirlist = g_strdup(name);
201
202         {
203           char *ext = strrchr (dirlist, '.');
204
205           if(ext && !Q_stricmp(ext, ".pk3dir"))
206           {
207             if (g_numDirs == VFS_MAXDIRS)
208               continue;
209             snprintf(g_strDirs[g_numDirs], PATH_MAX, "%s/%s", path, name);
210             g_strDirs[g_numDirs][PATH_MAX] = '\0';
211             vfsFixDOSName (g_strDirs[g_numDirs]);
212             vfsAddSlash (g_strDirs[g_numDirs]);
213             ++g_numDirs;
214           }
215
216           if ((ext == NULL) || (Q_stricmp (ext, ".pk3") != 0))
217             continue;
218         }
219         
220         sprintf (filename, "%s/%s", path, dirlist);
221         vfsInitPakFile (filename);
222
223         g_free(dirlist);
224       }
225       g_dir_close (dir);
226     }
227   }
228 }
229
230 // frees all memory that we allocated
231 void vfsShutdown ()
232 {
233   while (g_unzFiles)
234   {
235     unzClose ((unzFile)g_unzFiles->data);
236     g_unzFiles = g_slist_remove (g_unzFiles, g_unzFiles->data);
237   }
238   
239   while (g_pakFiles)
240   {
241     VFS_PAKFILE* file = (VFS_PAKFILE*)g_pakFiles->data;
242     free (file->name);
243     free (file);
244     g_pakFiles = g_slist_remove (g_pakFiles, file);
245   }
246 }
247
248 // return the number of files that match
249 int vfsGetFileCount (const char *filename)
250 {
251   int i, count = 0;
252   char fixed[NAME_MAX], tmp[NAME_MAX];
253   GSList *lst;
254   
255   strcpy (fixed, filename);
256   vfsFixDOSName (fixed);
257   g_strdown (fixed);
258   
259   for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
260   {
261     VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
262     
263     if (strcmp (file->name, fixed) == 0)
264       count++;
265   }
266   
267   for (i = 0; i < g_numDirs; i++)
268   {
269     strcpy (tmp, g_strDirs[i]);
270     strcat (tmp, fixed);
271     if (access (tmp, R_OK) == 0)
272       count++;
273   }
274   
275   return count;
276 }
277
278 // NOTE: when loading a file, you have to allocate one extra byte and set it to \0
279 int vfsLoadFile (const char *filename, void **bufferptr, int index)
280 {
281   int i, count = 0;
282   char tmp[NAME_MAX], fixed[NAME_MAX];
283   GSList *lst;
284   
285   // filename is a full path
286   if (index == -1)
287   {
288     long len;
289     FILE *f;
290     
291     f = fopen (filename, "rb");
292     if (f == NULL)
293       return -1;
294     
295     fseek (f, 0, SEEK_END);
296     len = ftell (f);
297     rewind (f);
298     
299     *bufferptr = safe_malloc (len+1);
300     if (*bufferptr == NULL)
301       return -1;
302     
303     fread (*bufferptr, 1, len, f);
304     fclose (f);
305     
306     // we need to end the buffer with a 0
307     ((char*) (*bufferptr))[len] = 0;
308     
309     return len;
310   }
311   
312   *bufferptr = NULL;
313   strcpy (fixed, filename);
314   vfsFixDOSName (fixed);
315   g_strdown (fixed);
316   
317   for (i = 0; i < g_numDirs; i++)
318   {
319     strcpy (tmp, g_strDirs[i]);
320     strcat (tmp, filename);
321     if (access (tmp, R_OK) == 0)
322     {
323       if (count == index)
324       {
325         long len;
326         FILE *f;
327         
328         f = fopen (tmp, "rb");
329         if (f == NULL)
330           return -1;
331         
332         fseek (f, 0, SEEK_END);
333         len = ftell (f);
334         rewind (f);
335         
336         *bufferptr = safe_malloc (len+1);
337         if (*bufferptr == NULL)
338           return -1;
339         
340         fread (*bufferptr, 1, len, f);
341         fclose (f);
342         
343         // we need to end the buffer with a 0
344         ((char*) (*bufferptr))[len] = 0;
345         
346         return len;
347       }
348       
349       count++;
350     }
351   }
352   
353   for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
354   {
355     VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
356     
357     if (strcmp (file->name, fixed) != 0)
358       continue;
359     
360     if (count == index)
361     {
362       memcpy (file->zipfile, &file->zipinfo, sizeof (unz_s));
363       
364       if (unzOpenCurrentFile (file->zipfile) != UNZ_OK)
365         return -1;
366       
367       *bufferptr = safe_malloc (file->size+1);
368       // we need to end the buffer with a 0
369       ((char*) (*bufferptr))[file->size] = 0;
370       
371       i = unzReadCurrentFile (file->zipfile , *bufferptr, file->size);
372       unzCloseCurrentFile (file->zipfile);
373       if (i < 0)
374         return -1;
375       else
376         return file->size;
377     }
378     
379     count++;
380   }
381   
382   return -1;
383 }