initial
[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 <stdio.h>
45
46 #if defined (__linux__) || defined (__APPLE__)
47 #include <dirent.h>
48 #include <unistd.h>
49 #else
50 #include <wtypes.h>
51 #include <io.h>
52 #define R_OK 04
53 #define S_ISDIR(mode) (mode & _S_IFDIR)
54 #define PATH_MAX 260
55 #endif
56
57 #include <string.h>
58 #include <stdlib.h>
59 #include <sys/stat.h>
60
61 #include "cmdlib.h"
62 #include "mathlib.h"
63 #include <glib.h>
64 #include "inout.h"
65 #include "vfs.h"
66 #include "unzip.h"
67
68 typedef struct
69 {
70   char*   name;
71   unz_s   zipinfo;
72   unzFile zipfile;
73   guint32   size;
74 } VFS_PAKFILE;
75
76 // =============================================================================
77 // Global variables
78
79 static GSList*  g_unzFiles;
80 static GSList*  g_pakFiles;
81 static char     g_strDirs[VFS_MAXDIRS][PATH_MAX];
82 static int      g_numDirs;
83 static gboolean g_bUsePak = TRUE;
84
85 // =============================================================================
86 // Static functions
87
88 static void vfsAddSlash (char *str)
89 {
90   int n = strlen (str);
91   if (n > 0)
92   {
93     if (str[n-1] != '\\' && str[n-1] != '/')
94       strcat (str, "/");
95   }
96 }
97
98 static void vfsFixDOSName (char *src)
99 {
100   if (src == NULL)
101     return;
102   
103   while (*src)
104   {
105     if (*src == '\\')
106       *src = '/';
107     src++;
108   }
109 }
110
111 //!\todo Define globally or use heap-allocated string.
112 #define NAME_MAX 255
113
114 static void vfsInitPakFile (const char *filename)
115 {
116   unz_global_info gi;
117   unzFile uf;
118   guint32 i;
119   int err;
120   
121   uf = unzOpen (filename);
122   if (uf == NULL)
123     return;
124   
125   g_unzFiles = g_slist_append (g_unzFiles, uf);
126   
127   err = unzGetGlobalInfo (uf,&gi);
128   if (err != UNZ_OK)
129     return;
130   unzGoToFirstFile(uf);
131   
132   for (i = 0; i < gi.number_entry; i++)
133   {
134     char filename_inzip[NAME_MAX];
135     unz_file_info file_info;
136     VFS_PAKFILE* file;
137     
138     err = unzGetCurrentFileInfo (uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
139     if (err != UNZ_OK)
140       break;
141     
142     file = (VFS_PAKFILE*)safe_malloc (sizeof (VFS_PAKFILE));
143     g_pakFiles = g_slist_append (g_pakFiles, file);
144     
145     vfsFixDOSName (filename_inzip);
146     g_strdown (filename_inzip);
147     
148     file->name = strdup (filename_inzip);
149     file->size = file_info.uncompressed_size;
150     file->zipfile = uf;
151     memcpy (&file->zipinfo, uf, sizeof (unz_s));
152     
153     if ((i+1) < gi.number_entry)
154     {
155       err = unzGoToNextFile(uf);
156       if (err!=UNZ_OK)
157         break;
158     }
159   }
160 }
161
162 // =============================================================================
163 // Global functions
164
165 // reads all pak files from a dir
166 void vfsInitDirectory (const char *path)
167 {
168   char filename[PATH_MAX];
169   char *dirlist;
170   GDir *dir;
171   
172   if (g_numDirs == (VFS_MAXDIRS-1))
173     return;
174   
175   Sys_Printf ("VFS Init: %s\n", path);
176   
177   strcpy (g_strDirs[g_numDirs], path);
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         dirlist = g_strdup(name);
195
196         {
197           char *ext = strrchr (dirlist, '.');
198           if ((ext == NULL) || (Q_stricmp (ext, ".pk3") != 0))
199             continue;
200         }
201         
202         sprintf (filename, "%s/%s", path, dirlist);
203         vfsInitPakFile (filename);
204
205         g_free(dirlist);
206       }
207       g_dir_close (dir);
208     }
209   }
210 }
211
212 // frees all memory that we allocated
213 void vfsShutdown ()
214 {
215   while (g_unzFiles)
216   {
217     unzClose ((unzFile)g_unzFiles->data);
218     g_unzFiles = g_slist_remove (g_unzFiles, g_unzFiles->data);
219   }
220   
221   while (g_pakFiles)
222   {
223     VFS_PAKFILE* file = (VFS_PAKFILE*)g_pakFiles->data;
224     free (file->name);
225     free (file);
226     g_pakFiles = g_slist_remove (g_pakFiles, file);
227   }
228 }
229
230 // return the number of files that match
231 int vfsGetFileCount (const char *filename)
232 {
233   int i, count = 0;
234   char fixed[NAME_MAX], tmp[NAME_MAX];
235   GSList *lst;
236   
237   strcpy (fixed, filename);
238   vfsFixDOSName (fixed);
239   g_strdown (fixed);
240   
241   for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
242   {
243     VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
244     
245     if (strcmp (file->name, fixed) == 0)
246       count++;
247   }
248   
249   for (i = 0; i < g_numDirs; i++)
250   {
251     strcpy (tmp, g_strDirs[i]);
252     strcat (tmp, fixed);
253     if (access (tmp, R_OK) == 0)
254       count++;
255   }
256   
257   return count;
258 }
259
260 // NOTE: when loading a file, you have to allocate one extra byte and set it to \0
261 int vfsLoadFile (const char *filename, void **bufferptr, int index)
262 {
263   int i, count = 0;
264   char tmp[NAME_MAX], fixed[NAME_MAX];
265   GSList *lst;
266   
267   // filename is a full path
268   if (index == -1)
269   {
270     long len;
271     FILE *f;
272     
273     f = fopen (filename, "rb");
274     if (f == NULL)
275       return -1;
276     
277     fseek (f, 0, SEEK_END);
278     len = ftell (f);
279     rewind (f);
280     
281     *bufferptr = safe_malloc (len+1);
282     if (*bufferptr == NULL)
283       return -1;
284     
285     fread (*bufferptr, 1, len, f);
286     fclose (f);
287     
288     // we need to end the buffer with a 0
289     ((char*) (*bufferptr))[len] = 0;
290     
291     return len;
292   }
293   
294   *bufferptr = NULL;
295   strcpy (fixed, filename);
296   vfsFixDOSName (fixed);
297   g_strdown (fixed);
298   
299   for (i = 0; i < g_numDirs; i++)
300   {
301     strcpy (tmp, g_strDirs[i]);
302     strcat (tmp, filename);
303     if (access (tmp, R_OK) == 0)
304     {
305       if (count == index)
306       {
307         long len;
308         FILE *f;
309         
310         f = fopen (tmp, "rb");
311         if (f == NULL)
312           return -1;
313         
314         fseek (f, 0, SEEK_END);
315         len = ftell (f);
316         rewind (f);
317         
318         *bufferptr = safe_malloc (len+1);
319         if (*bufferptr == NULL)
320           return -1;
321         
322         fread (*bufferptr, 1, len, f);
323         fclose (f);
324         
325         // we need to end the buffer with a 0
326         ((char*) (*bufferptr))[len] = 0;
327         
328         return len;
329       }
330       
331       count++;
332     }
333   }
334   
335   for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
336   {
337     VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
338     
339     if (strcmp (file->name, fixed) != 0)
340       continue;
341     
342     if (count == index)
343     {
344       memcpy (file->zipfile, &file->zipinfo, sizeof (unz_s));
345       
346       if (unzOpenCurrentFile (file->zipfile) != UNZ_OK)
347         return -1;
348       
349       *bufferptr = safe_malloc (file->size+1);
350       // we need to end the buffer with a 0
351       ((char*) (*bufferptr))[file->size] = 0;
352       
353       i = unzReadCurrentFile (file->zipfile , *bufferptr, file->size);
354       unzCloseCurrentFile (file->zipfile);
355       if (i < 0)
356         return -1;
357       else
358         return file->size;
359     }
360     
361     count++;
362   }
363   
364   return -1;
365 }