]> icculus.org git repositories - taylor/freespace2.git/blob - src/cfile/cfilesystem.cpp
Userdir patch from Taylor Richards
[taylor/freespace2.git] / src / cfile / cfilesystem.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell 
5  * or otherwise commercially exploit the source or things you created based on
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/CFile/CfileSystem.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * Functions to keep track of and find files that can exist
16  * on the harddrive, cd-rom, or in a pack file on either of those.
17  * This keeps a list of all the files in packfiles or on CD-rom
18  * and when you need a file you call one function which then searches
19  * all those locations, inherently enforcing precedence orders.
20  *
21  * $Log$
22  * Revision 1.8  2003/02/20 17:41:07  theoddone33
23  * Userdir patch from Taylor Richards
24  *
25  * Revision 1.7  2002/06/22 23:57:39  relnev
26  * remove writable strings.
27  *
28  * fix compile for intel compiler.
29  *
30  * Revision 1.6  2002/06/09 04:41:15  relnev
31  * added copyright header
32  *
33  * Revision 1.5  2002/06/05 04:03:32  relnev
34  * finished cfilesystem.
35  *
36  * removed some old code.
37  *
38  * fixed mouse save off-by-one.
39  *
40  * sound cleanups.
41  *
42  * Revision 1.4  2002/05/28 17:26:57  theoddone33
43  * Fill in some timer and palette setting stubs.  Still no display
44  *
45  * Revision 1.3  2002/05/28 06:45:38  theoddone33
46  * Cleanup some stuff
47  *
48  * Revision 1.2  2002/05/28 06:28:20  theoddone33
49  * Filesystem mods, actually reads some data files now
50  *
51  * Revision 1.1.1.1  2002/05/03 03:28:08  root
52  * Initial import.
53  *
54  * 
55  * 6     9/08/99 10:01p Dave
56  * Make sure game won't run in a drive's root directory. Make sure
57  * standalone routes suqad war messages properly to the host.
58  * 
59  * 5     9/03/99 1:31a Dave
60  * CD checking by act. Added support to play 2 cutscenes in a row
61  * seamlessly. Fixed super low level cfile bug related to files in the
62  * root directory of a CD. Added cheat code to set campaign mission # in
63  * main hall.
64  * 
65  * 4     2/22/99 10:31p Andsager
66  * Get rid of unneeded includes.
67  * 
68  * 3     10/13/98 9:19a Andsager
69  * Add localization support to cfile.  Optional parameter with cfopen that
70  * looks for localized files.
71  * 
72  * 2     10/07/98 10:52a Dave
73  * Initial checkin.
74  * 
75  * 1     10/07/98 10:48a Dave
76  * 
77  * 14    8/31/98 2:06p Dave
78  * Make cfile sort the ordering or vp files. Added support/checks for
79  * recognizing "mission disk" players.
80  * 
81  * 13    6/23/98 4:18p Hoffoss
82  * Fixed some bugs with AC release build.
83  * 
84  * 12    5/20/98 10:46p John
85  * Added code that doesn't include duplicate filenames in any file list
86  * functions.
87  * 
88  * 11    5/14/98 2:14p Lawrance2
89  * Use filespec filtering for packfiles
90  * 
91  * 10    5/03/98 11:53a John
92  * Fixed filename case mangling.
93  * 
94  * 9     5/02/98 11:06p Allender
95  * correctly deal with pack pathnames
96  * 
97  * 8     5/01/98 11:41a Allender
98  * Fixed bug with mission saving in Fred.
99  * 
100  * 7     5/01/98 10:21a John
101  * Added code to find all pack files in all trees.   Added code to create
102  * any directories that we write to.
103  * 
104  * 6     4/30/98 10:21p John
105  * Added code to cleanup cfilesystem
106  * 
107  * 5     4/30/98 10:18p John
108  * added source safe header
109  *
110  * $NoKeywords: $
111  */
112
113 #include <stdlib.h>
114 #include <string.h>
115 #include <stdio.h>
116 #include <errno.h>
117 #ifndef PLAT_UNIX
118 #include <io.h>
119 #include <direct.h>
120 #include <windows.h>
121 #include <winbase.h>            /* needed for memory mapping of file functions */
122 #else
123 #include <sys/types.h>
124 #include <dirent.h>
125 #include <fnmatch.h>
126 #include <sys/stat.h>
127 #include <unistd.h>
128 #endif
129
130 #include "pstypes.h"
131 //#include "outwnd.h"
132 //#include "vecmat.h"
133 //#include "timer.h"
134 #include "cfile.h"
135 #include "cfilesystem.h"
136 #include "localize.h"
137
138
139 #define CF_ROOTTYPE_PATH 0
140 #define CF_ROOTTYPE_PACK 1
141
142 //  Created by:
143 //    specifying hard drive tree
144 //    searching for pack files on hard drive            // Found by searching all known paths
145 //    specifying cd-rom tree
146 //    searching for pack files on CD-rom tree
147 typedef struct cf_root {
148         char                            path[CF_MAX_PATHNAME_LENGTH];           // Contains something like c:\projects\freespace or c:\projects\freespace\freespace.vp
149         int                             roottype;                                                               // CF_ROOTTYPE_PATH  = Path, CF_ROOTTYPE_PACK =Pack file
150 } cf_root;
151
152 // convenient type for sorting (see cf_build_pack_list())
153 typedef struct cf_root_sort { 
154         char                            path[CF_MAX_PATHNAME_LENGTH];
155         int                             roottype;
156         int                             cf_type;
157 } cf_root_sort;
158
159 #define CF_NUM_ROOTS_PER_BLOCK   32
160 #define CF_MAX_ROOT_BLOCKS                      256                             // Can store 32*256 = 8192 Roots
161 #define CF_MAX_ROOTS                                    (CF_NUM_ROOTS_PER_BLOCK * CF_MAX_ROOT_BLOCKS)
162
163 typedef struct cf_root_block {
164         cf_root                 roots[CF_NUM_ROOTS_PER_BLOCK];
165 } cf_root_block;
166
167 static int Num_roots = 0;
168 static cf_root_block  *Root_blocks[CF_MAX_ROOT_BLOCKS];
169   
170
171 // Created by searching all roots in order.   This means Files is then sorted by precedence.
172 typedef struct cf_file {
173         char            name_ext[CF_MAX_FILENAME_LENGTH];               // Filename and extension
174         int             root_index;                                                                             // Where in Roots this is located
175         int             pathtype_index;                                                         // Where in Paths this is located
176         time_t  write_time;                                                                             // When it was last written
177         int             size;                                                                                           // How big it is in bytes
178         int             pack_offset;                                                                    // For pack files, where it is at.   0 if not in a pack file.  This can be used to tell if in a pack file.
179 } cf_file;
180
181 #define CF_NUM_FILES_PER_BLOCK   256
182 #define CF_MAX_FILE_BLOCKS                      128                                             // Can store 256*128 = 32768 files
183
184 typedef struct cf_file_block {
185         cf_file                                         files[CF_NUM_FILES_PER_BLOCK];
186 } cf_file_block;
187
188 static int Num_files = 0;
189 static cf_file_block  *File_blocks[CF_MAX_FILE_BLOCKS];
190
191
192 // Return a pointer to to file 'index'.
193 cf_file *cf_get_file(int index)
194 {
195         int block = index / CF_NUM_FILES_PER_BLOCK;
196         int offset = index % CF_NUM_FILES_PER_BLOCK;
197
198         return &File_blocks[block]->files[offset];
199 }
200
201 // Create a new file and return a pointer to it.
202 cf_file *cf_create_file()
203 {
204         int block = Num_files / CF_NUM_FILES_PER_BLOCK;
205         int offset = Num_files % CF_NUM_FILES_PER_BLOCK;
206         
207         if ( File_blocks[block] == NULL )       {
208                 File_blocks[block] = (cf_file_block *)malloc( sizeof(cf_file_block) );
209                 Assert( File_blocks[block] != NULL);
210         }
211
212         Num_files++;
213
214         return &File_blocks[block]->files[offset];
215 }
216
217 extern int cfile_inited;
218
219 // Create a new root and return a pointer to it.  The structure is assumed unitialized.
220 cf_root *cf_get_root(int n)
221 {
222         int block = n / CF_NUM_ROOTS_PER_BLOCK;
223         int offset = n % CF_NUM_ROOTS_PER_BLOCK;
224
225         if (!cfile_inited)
226                 return NULL;
227
228         return &Root_blocks[block]->roots[offset];
229 }
230
231
232 // Create a new root and return a pointer to it.  The structure is assumed unitialized.
233 cf_root *cf_create_root()
234 {
235         int block = Num_roots / CF_NUM_ROOTS_PER_BLOCK;
236         int offset = Num_roots % CF_NUM_ROOTS_PER_BLOCK;
237         
238         if ( Root_blocks[block] == NULL )       {
239                 Root_blocks[block] = (cf_root_block *)malloc( sizeof(cf_root_block) );
240                 Assert(Root_blocks[block] != NULL);
241         }
242
243         Num_roots++;
244
245         return &Root_blocks[block]->roots[offset];
246 }
247
248 // return the # of packfiles which exist
249 int cf_get_packfile_count(cf_root *root)
250 {
251         char filespec[MAX_PATH_LEN];
252         int i;
253         int packfile_count;
254
255         // count up how many packfiles we're gonna have
256         packfile_count = 0;
257         for (i=CF_TYPE_ROOT; i<CF_MAX_PATH_TYPES; i++ ) {
258 #ifdef PLAT_UNIX
259                 strcpy( filespec, root->path );
260
261                 if(strlen(Pathtypes[i].path)){
262                         strcat( filespec, Pathtypes[i].path );
263                         strcat( filespec, "/" );
264                 }
265
266                 DIR *dirp;
267                 struct dirent *dir;
268
269                 dirp = opendir (filespec);
270                 if ( dirp ) {
271                         while ((dir = readdir (dirp)) != NULL)
272                         {
273                                 if (!fnmatch ("*.vp", dir->d_name, 0))
274                                         packfile_count++;
275                         }
276                         closedir(dirp);
277                 }
278 #else
279                 strcpy( filespec, root->path );
280
281                 if(strlen(Pathtypes[i].path)){
282                         strcat( filespec, Pathtypes[i].path );
283                         strcat( filespec, "\\" );
284                 }
285
286                 strcat( filespec, "*.vp" );
287
288                 int find_handle;
289                 _finddata_t find;
290                 
291                 find_handle = _findfirst( filespec, &find );
292
293                 if (find_handle != -1) {
294                         do {
295                                 if (!(find.attrib & _A_SUBDIR)) {
296                                         packfile_count++;
297                                 }
298
299                         } while (!_findnext(find_handle, &find));
300
301                         _findclose( find_handle );
302                 }       
303 #endif
304         }       
305
306         return packfile_count;
307 }
308
309 // packfile sort function
310 int cf_packfile_sort_func(const void *elem1, const void *elem2)
311 {
312         cf_root_sort *r1, *r2;
313         r1 = (cf_root_sort*)elem1;
314         r2 = (cf_root_sort*)elem2;
315
316         // if the 2 directory types are the same, do a string compare
317         if(r1->cf_type == r2->cf_type){
318                 return stricmp(r1->path, r2->path);
319         }
320
321         // otherwise return them in order of CF_TYPE_* precedence
322         return (r1->cf_type < r2->cf_type) ? -1 : 1;
323 }
324
325 // Go through a root and look for pack files
326 void cf_build_pack_list( cf_root *root )
327 {
328         char filespec[MAX_PATH_LEN];
329         int i;
330         cf_root_sort *temp_roots_sort, *rptr_sort;
331         int temp_root_count, root_index;
332
333         // determine how many packfiles there are
334         temp_root_count = cf_get_packfile_count(root);
335         if(temp_root_count <= 0){
336                 return;
337         }
338
339         // allocate a temporary array of temporary roots so we can easily sort them
340         temp_roots_sort = (cf_root_sort*)malloc(sizeof(cf_root_sort) * temp_root_count);
341         if(temp_roots_sort == NULL){
342                 Int3();
343                 return;
344         }
345
346         // now just setup all the root info
347         root_index = 0;
348         for (i=CF_TYPE_ROOT; i<CF_MAX_PATH_TYPES; i++ ) {
349
350 #ifdef PLAT_UNIX
351                 strcpy( filespec, root->path );
352
353                 if(strlen(Pathtypes[i].path)){
354                         strcat( filespec, Pathtypes[i].path );          
355                         strcat( filespec, "/" );
356                 }
357
358                 DIR *dirp;
359                 struct dirent *dir;
360
361                 dirp = opendir (filespec);
362                 if ( dirp ) {
363                         while ((dir = readdir (dirp)) != NULL)
364                         {
365                                 if (!fnmatch ("*.vp", dir->d_name, 0))
366                                 {
367                                         Assert(root_index < temp_root_count);
368
369                                         char fn[MAX_PATH];
370                                         snprintf(fn, MAX_PATH-1, "%s/%s", filespec, dir->d_name);
371                                         fn[MAX_PATH-1] = 0;
372                                                         
373                                         struct stat buf;
374                                         if (stat(fn, &buf) == -1) {
375                                                 continue;
376                                         }
377                                         
378                                         if (!S_ISREG(buf.st_mode)) {
379                                                 continue;
380                                         }
381                                         
382                                         // get a temp pointer
383                                         rptr_sort = &temp_roots_sort[root_index++];
384
385                                         // fill in all the proper info
386                                         strcpy(rptr_sort->path, root->path);
387
388                                         if(strlen(Pathtypes[i].path)){
389                                                 strcat(rptr_sort->path, Pathtypes[i].path );                                    
390                                                 strcat(rptr_sort->path, "/");
391                                         }
392
393                                         strcat(rptr_sort->path, dir->d_name );
394                                         rptr_sort->roottype = CF_ROOTTYPE_PACK;
395                                         rptr_sort->cf_type = i;
396                                 }
397                         }
398                         closedir(dirp);
399                 }
400 #else
401                 strcpy( filespec, root->path );
402
403                 if(strlen(Pathtypes[i].path)){
404                         strcat( filespec, Pathtypes[i].path );          
405                         strcat( filespec, "\\" );
406                 }
407                 strcat( filespec, "*.vp" );
408                 int find_handle;
409                 _finddata_t find;
410                 
411                 find_handle = _findfirst( filespec, &find );
412
413                 if (find_handle != -1) {
414                         do {
415                                 // add the new item
416                                 if (!(find.attrib & _A_SUBDIR)) {                                       
417                                         Assert(root_index < temp_root_count);
418
419                                         // get a temp pointer
420                                         rptr_sort = &temp_roots_sort[root_index++];
421
422                                         // fill in all the proper info
423                                         strcpy(rptr_sort->path, root->path);
424
425                                         if(strlen(Pathtypes[i].path)){
426                                                 strcat(rptr_sort->path, Pathtypes[i].path );                                    
427                                                 strcat(rptr_sort->path, "\\");
428                                         }
429
430                                         strcat(rptr_sort->path, find.name );
431                                         rptr_sort->roottype = CF_ROOTTYPE_PACK;
432                                         rptr_sort->cf_type = i;
433                                 }
434
435                         } while (!_findnext(find_handle, &find));
436
437                         _findclose( find_handle );
438                 }       
439 #endif
440         }       
441
442         // these should always be the same
443         Assert(root_index == temp_root_count);
444
445         // sort tht roots
446         qsort(temp_roots_sort,  temp_root_count, sizeof(cf_root_sort), cf_packfile_sort_func);
447
448         // now insert them all into the real root list properly
449         cf_root *new_root;
450         for(i=0; i<temp_root_count; i++){               
451                 new_root = cf_create_root();
452                 strcpy( new_root->path, root->path );
453
454                 // mwa -- 4/2/98 put in the next 2 lines because the path name needs to be there
455                 // to find the files.
456                 strcpy(new_root->path, temp_roots_sort[i].path);                
457                 new_root->roottype = CF_ROOTTYPE_PACK;          
458         }
459
460         // free up the temp list
461         free(temp_roots_sort);
462 }
463
464
465 void cf_build_root_list(char *cdrom_dir)
466 {
467         Num_roots = 0;
468
469         cf_root *root;
470
471 #ifdef PLAT_UNIX
472         // ================================================================
473         // use users HOME directory as default for loading and saving files
474         root = cf_create_root();
475         strcpy( root->path, Cfile_user_dir );
476
477         // do we already have a slash? as in the case of a root directory install
478         if(strlen(root->path) && (root->path[strlen(root->path)-1] != '/')){
479                 strcat(root->path, "/");                // put trailing backslash on for easier path construction
480         }
481         root->roottype = CF_ROOTTYPE_PATH;
482
483    //======================================================
484         // Next, check any VP files under the current directory.
485         cf_build_pack_list(root);
486 #endif
487
488    //======================================================
489         // First, check the current directory.
490         // strcpy( root->path, "d:\\projects\\freespace\\" );
491
492         root = cf_create_root();
493
494         if ( !_getcwd(root->path, CF_MAX_PATHNAME_LENGTH ) ) {
495                 Error(LOCATION, "Can't get current working directory -- %d", errno );
496         }
497
498         // do we already have a slash? as in the case of a root directory install
499 #ifdef PLAT_UNIX
500         if(strlen(root->path) && (root->path[strlen(root->path)-1] != '/')){
501                 strcat(root->path, "/");                // put trailing backslash on for easier path construction
502 #else
503         if(strlen(root->path) && (root->path[strlen(root->path)-1] != '\\')){
504                 strcat(root->path, "\\");               // put trailing backslash on for easier path construction
505 #endif
506         }
507         root->roottype = CF_ROOTTYPE_PATH;
508
509    //======================================================
510         // Next, check any VP files under the current directory.
511         cf_build_pack_list(root);
512
513
514    //======================================================
515         // Check the real CD if one...
516         if ( cdrom_dir && strlen(cdrom_dir) )   {
517                 root = cf_create_root();
518                 strcpy( root->path, cdrom_dir );
519                 root->roottype = CF_ROOTTYPE_PATH;
520
521                 //======================================================
522                 // Next, check any VP files in the CD-ROM directory.
523                 cf_build_pack_list(root);
524
525         }
526
527 }
528
529 // Given a lower case list of file extensions 
530 // separated by spaces, return zero if ext is
531 // not in the list.
532 int is_ext_in_list( char *ext_list, char *ext )
533 {
534         char tmp_ext[128];
535
536         strncpy( tmp_ext, ext, 127 );
537         strlwr(tmp_ext);
538         if ( strstr(ext_list, tmp_ext ))        {
539                 return 1;
540         }       
541
542         return 0;
543 }
544
545 void cf_search_root_path(int root_index)
546 {
547         int i;
548
549         cf_root *root = cf_get_root(root_index);
550
551         mprintf(( "Searching root '%s'\n", root->path ));
552
553         char search_path[CF_MAX_PATHNAME_LENGTH];
554
555         for (i=CF_TYPE_ROOT; i<CF_MAX_PATH_TYPES; i++ ) {
556
557 #ifdef PLAT_UNIX
558                 DIR *dirp;
559                 struct dirent *dir;
560
561                 strcpy( search_path, root->path );
562
563                 if(strlen(Pathtypes[i].path)){
564                         strcat( search_path, Pathtypes[i].path );
565                         strcat( search_path, "/" );
566                 } 
567
568                 dirp = opendir (search_path);
569                 if ( dirp ) {
570                         while ((dir = readdir (dirp)) != NULL)
571                         {
572                                 if (!fnmatch ("*.*", dir->d_name, 0))
573                                 {
574                                         char fn[MAX_PATH];
575                                         snprintf(fn, MAX_PATH-1, "%s/%s", search_path, dir->d_name);
576                                         fn[MAX_PATH-1] = 0;
577                                                         
578                                         struct stat buf;
579                                         if (stat(fn, &buf) == -1) {
580                                                 continue;
581                                         }
582                                         
583                                         if (!S_ISREG(buf.st_mode)) {
584                                                 continue;
585                                         }
586                                         
587                                         char *ext = strchr( dir->d_name, '.' );
588                                         if ( ext )      {
589                                                 if ( is_ext_in_list( Pathtypes[i].extensions, ext ) )   {
590                                                         // Found a file!!!!
591                                                         cf_file *file = cf_create_file();
592
593                                                         strcpy( file->name_ext, dir->d_name );
594                                                         file->root_index = root_index;
595                                                         file->pathtype_index = i;
596
597
598                                                         file->write_time = buf.st_mtime;
599                                                         file->size = buf.st_size;
600
601                                                         file->pack_offset = 0;                  // Mark as a non-packed file
602
603                                                         //mprintf(( "Found file '%s'\n", file->name_ext ));
604                                                 }
605                                         }
606                                 }
607                         }
608                         closedir(dirp);
609                 }
610 #else
611                 strcpy( search_path, root->path );
612
613                 if(strlen(Pathtypes[i].path)){
614                         strcat( search_path, Pathtypes[i].path );
615                         strcat( search_path, "\\" );
616                 } 
617
618                 strcat( search_path, "*.*" );
619
620                 int find_handle;
621                 _finddata_t find;
622                 
623                 find_handle = _findfirst( search_path, &find );
624
625                 if (find_handle != -1) {
626                         do {
627                                 if (!(find.attrib & _A_SUBDIR)) {
628
629                                         char *ext = strchr( find.name, '.' );
630                                         if ( ext )      {
631                                                 if ( is_ext_in_list( Pathtypes[i].extensions, ext ) )   {
632                                                         // Found a file!!!!
633                                                         cf_file *file = cf_create_file();
634
635                                                         strcpy( file->name_ext, find.name );
636                                                         file->root_index = root_index;
637                                                         file->pathtype_index = i;
638                                                         file->write_time = find.time_write;
639                                                         file->size = find.size;
640                                                         file->pack_offset = 0;                  // Mark as a non-packed file
641
642                                                         //mprintf(( "Found file '%s'\n", file->name_ext ));
643
644                                                 }
645                                         }
646
647                                 }
648
649                         } while (!_findnext(find_handle, &find));
650
651                         _findclose( find_handle );
652                 }
653 #endif
654
655         }
656 }
657
658
659 typedef struct VP_FILE_HEADER {
660         char id[4];
661         int version;
662         int index_offset;
663         int num_files;
664 } VP_FILE_HEADER;
665
666 typedef struct VP_FILE {
667         int     offset;
668         int     size;
669         char    filename[32];
670         time_t write_time;
671 } VP_FILE;
672
673 void cf_search_root_pack(int root_index)
674 {
675         int i;
676
677         cf_root *root = cf_get_root(root_index);
678
679         //mprintf(( "Searching root pack '%s'\n", root->path ));
680
681         // Open data
682                 
683         FILE *fp = fopen( root->path, "rb" );
684         // Read the file header
685         if (!fp) {
686                 return;
687         }
688
689         VP_FILE_HEADER VP_header;
690
691         Assert( sizeof(VP_header) == 16 );
692         fread(&VP_header, 1, sizeof(VP_header), fp);
693
694         // Read index info
695         fseek(fp, VP_header.index_offset, SEEK_SET);
696
697         char search_path[CF_MAX_PATHNAME_LENGTH];
698
699         strcpy( search_path, "" );
700         
701         // Go through all the files
702         for (i=0; i<VP_header.num_files; i++ )  {
703                 VP_FILE find;
704
705                 fread( &find, sizeof(VP_FILE), 1, fp );
706
707                 if ( find.size == 0 )   {
708                         if ( !stricmp( find.filename, ".." ))   {
709                                 int l = strlen(search_path);
710                                 char *p = &search_path[l-1];
711 #ifdef PLAT_UNIX
712                                 while( (p > search_path) && (*p != '/') )       {
713 #else
714                                 while( (p > search_path) && (*p != '\\') )      {
715 #endif
716                                         p--;
717                                 }
718                                 *p = 0;
719                         } else {
720                                 if ( strlen(search_path)        )       {
721 #ifdef PLAT_UNIX
722                                         strcat( search_path,    "/" );
723 #else
724                                         strcat( search_path,    "\\" );
725 #endif
726                                 }
727                                 strcat( search_path, find.filename );
728                         }
729
730                         //mprintf(( "Current dir = '%s'\n", search_path ));
731                 } else {
732         
733                         int j;
734                         for (j=CF_TYPE_ROOT; j<CF_MAX_PATH_TYPES; j++ ) {
735
736                                 if ( !stricmp( search_path, Pathtypes[j].path ))        {
737
738                                         char *ext = strchr( find.filename, '.' );
739                                         if ( ext )      {
740                                                 if ( is_ext_in_list( Pathtypes[j].extensions, ext ) )   {
741                                                         // Found a file!!!!
742                                                         cf_file *file = cf_create_file();
743                                                         
744                                                         strcpy( file->name_ext, find.filename );
745                                                         file->root_index = root_index;
746                                                         file->pathtype_index = j;
747                                                         file->write_time = find.write_time;
748                                                         file->size = find.size;
749                                                         file->pack_offset = find.offset;                        // Mark as a non-packed file
750
751                                                         //mprintf(( "Found pack file '%s'\n", file->name_ext ));
752                                                 }
753                                         }
754                                         
755
756                                 }
757                         }
758
759                 }
760         }
761         fclose(fp);
762 }
763
764
765 void cf_build_file_list()
766 {
767         int i;
768
769         Num_files = 0;
770
771         // For each root, find all files...
772         for (i=1; i<Num_roots; i++ )    {
773                 cf_root *root = cf_get_root(i);
774                 if ( root->roottype == CF_ROOTTYPE_PATH )       {
775                         cf_search_root_path(i);
776                 } else if ( root->roottype == CF_ROOTTYPE_PACK )        {
777                         cf_search_root_pack(i);
778                 }
779         }
780
781 }
782
783
784 void cf_build_secondary_filelist(char *cdrom_dir)
785 {
786         int i;
787
788         // Assume no files
789         Num_roots = 0;
790         Num_files = 0;
791
792         // Init the path types
793         for (i=0; i<CF_MAX_PATH_TYPES; i++ )    {
794                 Assert( Pathtypes[i].index == i );
795 #if 0 /* they are already lowercased -- SBF */
796                 if ( Pathtypes[i].extensions )  {
797                         strlwr(Pathtypes[i].extensions);
798                 }
799 #endif          
800         }
801         
802         // Init the root blocks
803         for (i=0; i<CF_MAX_ROOT_BLOCKS; i++ )   {
804                 Root_blocks[i] = NULL;
805         }
806
807         // Init the file blocks 
808         for (i=0; i<CF_MAX_FILE_BLOCKS; i++ )   {
809                 File_blocks[i] = NULL;
810         }
811
812         mprintf(( "Building file index...\n" ));
813         
814         // build the list of searchable roots
815         cf_build_root_list(cdrom_dir);  
816
817         // build the list of files themselves
818         cf_build_file_list();
819
820         mprintf(( "Found %d roots and %d files.\n", Num_roots, Num_files ));
821 }
822
823 void cf_free_secondary_filelist()
824 {
825         int i;
826
827         // Free the root blocks
828         for (i=0; i<CF_MAX_ROOT_BLOCKS; i++ )   {
829                 if ( Root_blocks[i] )   {
830                         free( Root_blocks[i] );
831                         Root_blocks[i] = NULL;
832                 }
833         }
834         Num_roots = 0;
835
836         // Init the file blocks 
837         for (i=0; i<CF_MAX_FILE_BLOCKS; i++ )   {
838                 if ( File_blocks[i] )   {
839                         free( File_blocks[i] );
840                         File_blocks[i] = NULL;
841                 }
842         }
843         Num_files = 0;
844 }
845
846 // Searches for a file.   Follows all rules and precedence and searches
847 // CD's and pack files.
848 // Input:  filespace   - Filename & extension
849 //         pathtype    - See CF_TYPE_ defines in CFILE.H
850 // Output: pack_filename - Absolute path and filename of this file.   Could be a packfile or the actual file.
851 //         size        - File size
852 //         offset      - Offset into pack file.  0 if not a packfile.
853 // Returns: If not found returns 0.
854 int cf_find_file_location( char *filespec, int pathtype, char *pack_filename, int *size, int *offset, bool localize )
855 {
856         int i;
857
858         Assert(filespec && strlen(filespec));
859
860         // see if we have something other than just a filename
861         // our current rules say that any file that specifies a direct
862         // path will try to be opened on that path.  If that open
863         // fails, then we will open the file based on the extension
864         // of the file
865
866         // NOTE: full path should also include localization, if so desired
867         if ( strpbrk(filespec,"/\\:")  ) {              // do we have a full path already?
868                 FILE *fp = fopen(filespec, "rb" );
869                 if (fp) {
870                         if ( size ) *size = filelength(fileno(fp));
871                         if ( offset ) *offset = 0;
872                         if ( pack_filename ) {
873                                 strcpy( pack_filename, filespec );
874                         }                               
875                         fclose(fp);
876                         return 1;               
877                 }
878
879                 return 0;               // If they give a full path, fail if not found.
880         }
881
882         // Search the hard drive for files first.
883         int num_search_dirs = 0;
884         int search_order[CF_MAX_PATH_TYPES];
885
886         if ( CF_TYPE_SPECIFIED(pathtype) )      {
887                 search_order[num_search_dirs++] = pathtype;
888         }
889
890         for (i=CF_TYPE_ROOT; i<CF_MAX_PATH_TYPES; i++)  {
891                 if ( i != pathtype )    {
892                         search_order[num_search_dirs++] = i;
893                 }
894         }
895
896         for (i=0; i<num_search_dirs; i++ )      {
897                 char longname[MAX_PATH_LEN];
898
899                 cf_create_default_path_string( longname, search_order[i], filespec, localize );
900
901                 FILE *fp = fopen(longname, "rb" );
902                 if (fp) {
903                         if ( size ) *size = filelength(fileno(fp));
904                         if ( offset ) *offset = 0;
905                         if ( pack_filename ) {
906                                 strcpy( pack_filename, longname );
907                         }                               
908                         fclose(fp);
909                         return 1;               
910                 }
911         } 
912
913         // Search the pak files and CD-ROM.
914
915                 for (i=0; i<Num_files; i++ )    {
916                         cf_file * f = cf_get_file(i);
917
918                         // only search paths we're supposed to...
919                         if ( (pathtype != CF_TYPE_ANY) && (pathtype != f->pathtype_index)  )    {
920                                 continue;
921                         }
922
923                         if (localize) {
924                                 // create localized filespec
925                                 char temp[MAX_PATH_LEN];
926                                 strcpy(temp, filespec);
927                                 lcl_add_dir_to_path_with_filename(filespec);
928                         
929                                 if ( !stricmp(filespec, f->name_ext) )  {
930                                         if ( size ) *size = f->size;
931                                         if ( offset ) *offset = f->pack_offset;
932                                         if ( pack_filename ) {
933                                                 cf_root * r = cf_get_root(f->root_index);
934
935                                                 strcpy( pack_filename, r->path );
936                                                 if ( f->pack_offset < 1 )       {
937                                                         strcat( pack_filename, Pathtypes[f->pathtype_index].path );
938 #ifdef PLAT_UNIX
939                                                         strcat( pack_filename, "/" );
940 #else
941                                                         strcat( pack_filename, "\\" );
942 #endif
943                                                         strcat( pack_filename, f->name_ext );
944                                                 }
945                                         }                               
946                                         return 1;               
947                                 }
948                                 // restore original filespec
949                                 strcpy(filespec, temp);
950                         }
951
952                         // file either not localized or localized version not found
953                         if ( !stricmp(filespec, f->name_ext) )  {
954                                 if ( size ) *size = f->size;
955                                 if ( offset ) *offset = f->pack_offset;
956                                 if ( pack_filename ) {
957                                         cf_root * r = cf_get_root(f->root_index);
958
959                                         strcpy( pack_filename, r->path );
960                                         if ( f->pack_offset < 1 )       {
961
962                                                 if(strlen(Pathtypes[f->pathtype_index].path)){
963                                                         strcat( pack_filename, Pathtypes[f->pathtype_index].path );
964 #ifdef PLAT_UNIX
965                                                         strcat( pack_filename, "/" );
966 #else
967                                                         strcat( pack_filename, "\\" );
968 #endif
969                                                 }
970
971                                                 strcat( pack_filename, f->name_ext );
972                                         }
973                                 }                               
974                                 return 1;               
975                         }
976                 }
977         
978         return 0;
979 }
980
981
982 // Returns true if filename matches filespec, else zero if not
983 int cf_matches_spec(char *filespec, char *filename)
984 {
985         char *src_ext, *dst_ext;
986
987         src_ext = strchr(filespec, '.');
988         if (!src_ext)
989                 return 1;
990         if (*src_ext == '*')
991                 return 1;
992
993         dst_ext = strchr(filename, '.');
994         if (!dst_ext)
995                 return 1;
996         
997         return !stricmp(dst_ext, src_ext);
998 }
999
1000 int (*Get_file_list_filter)(char *filename) = NULL;
1001 int Skip_packfile_search = 0;
1002
1003 int cf_file_already_in_list( int num_files, char **list, char *filename )
1004 {
1005         int i;
1006
1007         char name_no_extension[MAX_PATH_LEN];
1008
1009         strcpy(name_no_extension, filename );
1010         char *p = strchr( name_no_extension, '.' );
1011         if ( p ) *p = 0;
1012
1013         for (i=0; i<num_files; i++ )    {
1014                 if ( !stricmp(list[i], name_no_extension ) )    {
1015                         // Match found!
1016                         return 1;
1017                 }
1018         }
1019         // Not found
1020         return 0;
1021 }
1022
1023 // An alternative cf_get_file_list(), dynamic list version.
1024 // This one has a 'type', which is a CF_TYPE_* value.  Because this specifies the directory
1025 // location, 'filter' only needs to be the filter itself, with no path information.
1026 // See above descriptions of cf_get_file_list() for more information about how it all works.
1027 int cf_get_file_list( int max, char **list, int pathtype, char *filter, int sort, file_list_info *info )
1028 {
1029         char *ptr;
1030         int i, l, num_files = 0, own_flag = 0;
1031 #ifndef PLAT_UNIX
1032         int find_handle;
1033         _finddata_t find;
1034 #endif
1035
1036         if (max < 1) {
1037                 Get_file_list_filter = NULL;
1038                 return 0;
1039         }
1040
1041         Assert(list);
1042
1043         if (!info && (sort == CF_SORT_TIME)) {
1044                 info = (file_list_info *) malloc(sizeof(file_list_info) * max);
1045                 own_flag = 1;
1046         }
1047
1048         char filespec[MAX_PATH_LEN];
1049
1050 #ifdef PLAT_UNIX
1051         cf_create_default_path_string( filespec, pathtype, NULL );
1052
1053                 DIR *dirp;
1054                 struct dirent *dir;
1055
1056                 dirp = opendir (filespec);
1057                 if ( dirp ) {
1058                         while ((dir = readdir (dirp)) != NULL)
1059                         {
1060                                 if (num_files >= max)
1061                                         break;
1062                                 
1063                                 if (fnmatch(filter, dir->d_name, 0) != 0)
1064                                         continue;
1065                                 
1066                                 char fn[MAX_PATH];
1067                                 snprintf(fn, MAX_PATH-1, "%s/%s", filespec, dir->d_name);
1068                                 fn[MAX_PATH-1] = 0;
1069                                                         
1070                                 struct stat buf;
1071                                 if (stat(fn, &buf) == -1) {
1072                                         continue;
1073                                 }
1074                                 
1075                                 if (!S_ISREG(buf.st_mode)) {
1076                                         continue;
1077                                 }
1078                                 
1079                                 if ( !Get_file_list_filter || (*Get_file_list_filter)(dir->d_name) ) {
1080                                         ptr = strrchr(dir->d_name, '.');
1081                                         if (ptr)
1082                                                 l = ptr - dir->d_name;
1083                                         else
1084                                                 l = strlen(dir->d_name);
1085
1086                                         list[num_files] = (char *)malloc(l + 1);
1087                                         strncpy(list[num_files], dir->d_name, l);
1088                                         list[num_files][l] = 0;
1089                                         if (info)
1090                                                 info[num_files].write_time = buf.st_mtime;
1091
1092                                         num_files++;
1093                                 }
1094                         }
1095                         
1096                         closedir(dirp);
1097                 }
1098 #else
1099         cf_create_default_path_string( filespec, pathtype, filter );
1100         
1101         find_handle = _findfirst( filespec, &find );
1102         if (find_handle != -1) {
1103                 do {
1104                         if (num_files >= max)
1105                                 break;
1106
1107                         if (!(find.attrib & _A_SUBDIR)) {
1108                                 if ( !Get_file_list_filter || (*Get_file_list_filter)(find.name) ) {
1109                                         ptr = strrchr(find.name, '.');
1110                                         if (ptr)
1111                                                 l = ptr - find.name;
1112                                         else
1113                                                 l = strlen(find.name);
1114
1115                                         list[num_files] = (char *)malloc(l + 1);
1116                                         strncpy(list[num_files], find.name, l);
1117                                         list[num_files][l] = 0;
1118                                         if (info)
1119                                                 info[num_files].write_time = find.time_write;
1120
1121                                         num_files++;
1122                                 }
1123                         }
1124
1125                 } while (!_findnext(find_handle, &find));
1126
1127                 _findclose( find_handle );
1128         }
1129 #endif
1130
1131
1132         // Search all the packfiles and CD.
1133         if ( !Skip_packfile_search )    {
1134                 for (i=0; i<Num_files; i++ )    {
1135                         cf_file * f = cf_get_file(i);
1136
1137                         // only search paths we're supposed to...
1138                         if ( (pathtype != CF_TYPE_ANY) && (pathtype != f->pathtype_index)  )    {
1139                                 continue;
1140                         }
1141
1142                         if (num_files >= max)
1143                                 break;
1144
1145                         if ( !cf_matches_spec( filter,f->name_ext))     {
1146                                 continue;
1147                         }
1148
1149                         if ( cf_file_already_in_list(num_files,list,f->name_ext))       {
1150                                 continue;
1151                         }
1152
1153                         if ( !Get_file_list_filter || (*Get_file_list_filter)(f->name_ext) ) {
1154
1155                                 //mprintf(( "Found '%s' in root %d path %d\n", f->name_ext, f->root_index, f->pathtype_index ));
1156
1157                                         ptr = strrchr(f->name_ext, '.');
1158                                         if (ptr)
1159                                                 l = ptr - f->name_ext;
1160                                         else
1161                                                 l = strlen(f->name_ext);
1162
1163                                         list[num_files] = (char *)malloc(l + 1);
1164                                         strncpy(list[num_files], f->name_ext, l);
1165                                         list[num_files][l] = 0;
1166
1167                                 if (info)       {
1168                                         info[num_files].write_time = f->write_time;
1169                                 }
1170
1171                                 num_files++;
1172                         }
1173
1174                 }
1175         }
1176
1177
1178         if (sort != CF_SORT_NONE)       {
1179                 cf_sort_filenames( num_files, list, sort, info );
1180         }
1181
1182         if (own_flag)   {
1183                 free(info);
1184         }
1185
1186         Get_file_list_filter = NULL;
1187         return num_files;
1188 }
1189
1190 int cf_file_already_in_list_preallocated( int num_files, char arr[][MAX_FILENAME_LEN], char *filename )
1191 {
1192         int i;
1193
1194         char name_no_extension[MAX_PATH_LEN];
1195
1196         strcpy(name_no_extension, filename );
1197         char *p = strchr( name_no_extension, '.' );
1198         if ( p ) *p = 0;
1199
1200         for (i=0; i<num_files; i++ )    {
1201                 if ( !stricmp(arr[i], name_no_extension ) )     {
1202                         // Match found!
1203                         return 1;
1204                 }
1205         }
1206         // Not found
1207         return 0;
1208 }
1209
1210 // An alternative cf_get_file_list(), fixed array version.
1211 // This one has a 'type', which is a CF_TYPE_* value.  Because this specifies the directory
1212 // location, 'filter' only needs to be the filter itself, with no path information.
1213 // See above descriptions of cf_get_file_list() for more information about how it all works.
1214 int cf_get_file_list_preallocated( int max, char arr[][MAX_FILENAME_LEN], char **list, int pathtype, char *filter, int sort, file_list_info *info )
1215 {
1216         int i, num_files = 0, own_flag = 0;
1217
1218         if (max < 1) {
1219                 Get_file_list_filter = NULL;
1220                 return 0;
1221         }
1222
1223         if (list) {
1224                 for (i=0; i<max; i++)   {
1225                         list[i] = arr[i];
1226                 }
1227         } else {
1228                 sort = CF_SORT_NONE;  // sorting of array directly not supported.  Sorting done on list only
1229         }
1230
1231         if (!info && (sort == CF_SORT_TIME)) {
1232                 info = (file_list_info *) malloc(sizeof(file_list_info) * max);
1233                 own_flag = 1;
1234         }
1235
1236         char filespec[MAX_PATH_LEN];
1237
1238         // Search the default directories
1239 #ifdef PLAT_UNIX
1240         cf_create_default_path_string( filespec, pathtype, NULL );
1241         
1242                 DIR *dirp;
1243                 struct dirent *dir;
1244
1245                 dirp = opendir (filespec);
1246                 if ( dirp ) {
1247                         while ((dir = readdir (dirp)) != NULL)
1248                         {
1249                                 if (num_files >= max)
1250                                         break;
1251                                 
1252                                 if (fnmatch(filter, dir->d_name, 0) != 0)
1253                                         continue;
1254                                 
1255                                 char fn[MAX_PATH];
1256                                 snprintf(fn, MAX_PATH-1, "%s/%s", filespec, dir->d_name);
1257                                 fn[MAX_PATH-1] = 0;
1258                                                         
1259                                 struct stat buf;
1260                                 if (stat(fn, &buf) == -1) {
1261                                         continue;
1262                                 }
1263                                 
1264                                 if (!S_ISREG(buf.st_mode)) {
1265                                         continue;
1266                                 }
1267                                 
1268                                 if ( !Get_file_list_filter || (*Get_file_list_filter)(dir->d_name) ) {
1269
1270                                         strncpy(arr[num_files], dir->d_name, MAX_FILENAME_LEN - 1 );
1271                                         char *ptr = strrchr(arr[num_files], '.');
1272                                         if ( ptr ) {
1273                                                 *ptr = 0;
1274                                         }
1275
1276                                         if (info)       {
1277                                                 info[num_files].write_time = buf.st_mtime;
1278                                         }
1279
1280                                         num_files++;
1281                                 }
1282                         }
1283                         closedir(dirp);
1284                 }
1285 #else
1286         cf_create_default_path_string( filespec, pathtype, filter );
1287         
1288         int find_handle;
1289         _finddata_t find;
1290         
1291         find_handle = _findfirst( filespec, &find );
1292         if (find_handle != -1) {
1293                 do {
1294                         if (num_files >= max)
1295                                 break;
1296
1297                         if (!(find.attrib & _A_SUBDIR)) {
1298
1299                                 if ( !Get_file_list_filter || (*Get_file_list_filter)(find.name) ) {
1300
1301                                         strncpy(arr[num_files], find.name, MAX_FILENAME_LEN - 1 );
1302                                         char *ptr = strrchr(arr[num_files], '.');
1303                                         if ( ptr ) {
1304                                                 *ptr = 0;
1305                                         }
1306
1307                                         if (info)       {
1308                                                 info[num_files].write_time = find.time_write;
1309                                         }
1310
1311                                         num_files++;
1312                                 }
1313                         }
1314
1315                 } while (!_findnext(find_handle, &find));
1316
1317                 _findclose( find_handle );
1318         }
1319 #endif
1320                 
1321
1322         // Search all the packfiles and CD.
1323         if ( !Skip_packfile_search )    {
1324                 for (i=0; i<Num_files; i++ )    {
1325                         cf_file * f = cf_get_file(i);
1326
1327                         // only search paths we're supposed to...
1328                         if ( (pathtype != CF_TYPE_ANY) && (pathtype != f->pathtype_index)  )    {
1329                                 continue;
1330                         }
1331
1332                         if (num_files >= max)
1333                                 break;
1334
1335                         if ( !cf_matches_spec( filter,f->name_ext))     {
1336                                 continue;
1337                         }
1338
1339                         if ( cf_file_already_in_list_preallocated( num_files, arr, f->name_ext ))       {
1340                                 continue;
1341                         }
1342
1343                         if ( !Get_file_list_filter || (*Get_file_list_filter)(f->name_ext) ) {
1344
1345                                 //mprintf(( "Found '%s' in root %d path %d\n", f->name_ext, f->root_index, f->pathtype_index ));
1346
1347                                 strncpy(arr[num_files], f->name_ext, MAX_FILENAME_LEN - 1 );
1348                                 char *ptr = strrchr(arr[num_files], '.');
1349                                 if ( ptr ) {
1350                                         *ptr = 0;
1351                                 }
1352
1353                                 if (info)       {
1354                                         info[num_files].write_time = f->write_time;
1355                                 }
1356
1357                                 num_files++;
1358                         }
1359
1360                 }
1361         }
1362
1363         if (sort != CF_SORT_NONE) {
1364                 Assert(list);
1365                 cf_sort_filenames( num_files, list, sort, info );
1366         }
1367
1368         if (own_flag)   {
1369                 free(info);
1370         }
1371
1372         Get_file_list_filter = NULL;
1373         return num_files;
1374 }
1375
1376 // Returns the default storage path for files given a 
1377 // particular pathtype.   In other words, the path to 
1378 // the unpacked, non-cd'd, stored on hard drive path.
1379 // If filename isn't null it will also tack the filename
1380 // on the end, creating a completely valid filename.
1381 // Input:   pathtype  - CF_TYPE_??
1382 //          filename  - optional, if set, tacks the filename onto end of path.
1383 // Output:  path      - Fully qualified pathname.
1384 void cf_create_default_path_string( char *path, int pathtype, char *filename, bool localize )
1385 {
1386 #ifdef PLAT_UNIX
1387         if ( filename && strpbrk(filename,"/")  ) {  
1388 #else
1389         if ( filename && strpbrk(filename,"/\\:")  ) {  
1390 #endif
1391                 // Already has full path
1392                 strcpy( path, filename );
1393
1394         } else {
1395                 cf_root *root = cf_get_root(0);
1396
1397                 if (!root) {
1398                         strcpy(path, filename);
1399                         return;
1400                 }
1401
1402                 Assert(CF_TYPE_SPECIFIED(pathtype));
1403
1404                 strcpy(path, root->path);
1405                 strcat(path, Pathtypes[pathtype].path);
1406
1407                 // Don't add slash for root directory
1408                 if (Pathtypes[pathtype].path[0] != '\0') {
1409 #ifdef PLAT_UNIX
1410                         strcat(path, "/");
1411 #else
1412                         strcat(path, "\\");
1413 #endif
1414                 }
1415
1416                 // add filename
1417                 if (filename) {
1418                         strcat(path, filename);
1419
1420                         // localize filename
1421                         if (localize) {
1422                                 // create copy of path
1423                                 char temp_path[MAX_PATH_LEN];
1424                                 strcpy(temp_path, path);
1425
1426                                 // localize the path
1427                                 lcl_add_dir_to_path_with_filename(path);
1428
1429                                 // verify localized path
1430                                 FILE *fp = fopen(path, "rb");
1431                                 if (fp) {
1432                                         fclose(fp);
1433                                 } else {
1434                                         strcpy(path, temp_path);
1435                                 }
1436                         }
1437                 }
1438         }
1439 }
1440