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