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