]> icculus.org git repositories - btb/d2x.git/blob - cfile/cfile.c
Assign result of cfilelength to an int, MPW doesn't like switches of long longs
[btb/d2x.git] / cfile / cfile.c
1 /* $Id: cfile.c,v 1.32 2004-10-23 16:28:32 schaffner Exp $ */
2 /*
3 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
4 SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
5 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
6 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
7 IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
8 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
9 FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
10 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
11 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
12 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
13 */
14
15 /*
16  *
17  * Functions for accessing compressed files.
18  * (Actually, the files are not compressed, but concatenated within hogfiles)
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <conf.h>
24 #endif
25
26 #include <stdio.h>
27 #include <string.h>
28 #ifdef _WIN32_WCE
29 # include <windows.h>
30 #elif defined(macintosh)
31 # include <Files.h>
32 # include <CFURL.h>
33 #else
34 # include <sys/stat.h>
35 #endif
36
37 #include "pstypes.h"
38 #include "u_mem.h"
39 #include "strutil.h"
40 #include "d_io.h"
41 #include "error.h"
42 #include "cfile.h"
43 #include "byteswap.h"
44
45 /* a file (if offset == 0) or a part of a hog file */
46 struct CFILE {
47         FILE    *file;
48         int     size;
49         int     offset;
50         int     raw_position;
51 };
52
53 struct file_in_hog {
54         char    name[13];
55         int     offset;
56         int     length;
57 };
58
59 #define MAX_FILES_IN_HOG 300
60
61 /* a hog file is an archive, like a tar file */
62 typedef struct {
63         char filename[64];
64         int num_files;
65         struct file_in_hog files[MAX_FILES_IN_HOG];
66 } hog;
67
68 hog *builtin_hog = NULL;
69 hog *alt_hog = NULL;
70 hog *d1_hog = NULL;
71
72 void free_builtin_hog() { if (builtin_hog) d_free (builtin_hog); }
73 void free_alt_hog() { if (alt_hog) d_free (alt_hog); }
74 void free_d1_hog() { if (d1_hog) d_free (d1_hog); }
75
76 char AltHogDir[64];
77 char AltHogdir_initialized = 0;
78
79 // routine to take a DOS path and turn it into a macintosh
80 // pathname.  This routine is based on the fact that we should
81 // see a \ character in the dos path.  The sequence .\ a tthe
82 // beginning of a path is turned into a :
83
84 // routine to take a POSIX style path and turn it into a pre OS X
85 // pathname.  This routine uses CFURL's. This function is necessary
86 // because even though fopen exists in StdCLib
87 // it must take a path in the OS native format.
88
89 #ifdef macintosh
90 void macify_posix_path(char *posix_path, char *mac_path)
91 {
92     CFURLRef    url;
93
94     url = CFURLCreateWithBytes (kCFAllocatorDefault, (ubyte *) posix_path, strlen(posix_path), GetApplicationTextEncoding(), NULL);
95     CFURLGetFileSystemRepresentation (url, 0, (ubyte *) mac_path, 255);
96     CFRelease(url);
97 }
98 #endif
99
100 void cfile_use_alternate_hogdir( char * path )
101 {
102         if ( path )     {
103                 strcpy( AltHogDir, path );
104                 AltHogdir_initialized = 1;
105         } else {
106                 AltHogdir_initialized = 0;
107         }
108 }
109
110 //in case no one installs one
111 int default_error_counter=0;
112
113 //ptr to counter of how many critical errors
114 int *critical_error_counter_ptr=&default_error_counter;
115
116 //tell cfile about your critical error counter
117 void cfile_set_critical_error_counter_ptr(int *ptr)
118 {
119         critical_error_counter_ptr = ptr;
120
121 }
122
123
124 FILE * cfile_get_filehandle( char * filename, char * mode )
125 {
126     FILE * fp;
127     char temp[128];
128 #ifdef macintosh
129     char mac_path[256];
130 #endif
131
132     *critical_error_counter_ptr = 0;
133 #ifdef macintosh
134     macify_posix_path(filename, mac_path);
135     fp = fopen( mac_path, mode );
136 #else
137     fp = fopen( filename, mode );
138 #endif
139     if ( fp && *critical_error_counter_ptr )    {
140         fclose(fp);
141         fp = NULL;
142     }
143     if ( (fp==NULL) && (AltHogdir_initialized) )        {
144         strcpy( temp, AltHogDir );
145         strcat( temp, "/");
146         strcat( temp, filename );
147         *critical_error_counter_ptr = 0;
148 #ifdef macintosh
149         macify_posix_path(temp, mac_path);
150         fp = fopen( mac_path, mode );
151 #else
152         fp = fopen( temp, mode );
153 #endif
154         if ( fp && *critical_error_counter_ptr )        {
155             fclose(fp);
156             fp = NULL;
157         }
158     }
159     return fp;
160 }
161
162 hog * cfile_init_hogfile (char *fname)
163 {
164         char id[4];
165         FILE * fp;
166         int i, len;
167         hog *new_hog;
168
169         fp = cfile_get_filehandle (fname, "rb");
170         if (fp == NULL)
171                 return 0;
172
173         // verify that it is really a Descent Hog File
174         fread( id, 3, 1, fp );
175         if ( strncmp( id, "DHF", 3 ) )  {
176                 fclose(fp);
177                 return NULL;
178         }
179
180         new_hog = (hog *) d_malloc (sizeof (hog));
181
182         new_hog->num_files = 0;
183         strcpy (new_hog->filename, fname);
184
185         // read file name or reach EOF
186         while (! feof (fp)) {
187                 if (new_hog->num_files >= MAX_FILES_IN_HOG) {
188                         fclose (fp);
189                         d_free (new_hog);
190                         Error ("Exceeded max. number of files in hog (%d).\n",
191                                MAX_FILES_IN_HOG);
192                 }
193                 i = fread (new_hog->files[new_hog->num_files].name, 13, 1, fp);
194                 if (i != 1)
195                         break; // we assume it is EOF, so it is OK
196                 
197                 i = fread( &len, 4, 1, fp );
198                 if (i != 1)
199                         break;
200                 new_hog->files[new_hog->num_files].length = INTEL_INT (len);
201                 new_hog->files[new_hog->num_files].offset = ftell (fp);
202                 // skip over embedded file:
203                 i = fseek (fp, INTEL_INT (len), SEEK_CUR);
204                 new_hog->num_files++;
205         }
206         fclose (fp);
207         return new_hog;
208 }
209
210 // Opens builtin hog given its filename. Returns 1 on success.
211 int cfile_init(char *hogname)
212 {
213         Assert (builtin_hog == NULL);
214
215         builtin_hog = cfile_init_hogfile (hogname);
216         atexit (free_builtin_hog);
217         return builtin_hog != NULL ? 1 : 0;
218 }
219
220
221 int cfile_size(char *hogname)
222 {
223         CFILE *fp;
224         int size;
225
226         fp = cfopen(hogname, "rb");
227         if (fp == NULL)
228                 return -1;
229         size = ffilelength(fp->file);
230         cfclose(fp);
231         return size;
232 }
233
234 FILE * cfile_find_in_hog (char * name, int * length, hog *my_hog) {
235         FILE * fp;
236         int i;
237         for (i = 0; i < my_hog->num_files; i++ )
238                 if (! stricmp (my_hog->files[i].name, name)) {
239                         fp = cfile_get_filehandle (my_hog->filename, "rb");
240                         if (fp == NULL)
241                                 return NULL;
242                         fseek (fp,  my_hog->files[i].offset, SEEK_SET);
243                         *length = my_hog->files[i].length;
244                         return fp;
245                 }
246         return NULL;
247 }
248
249 /*
250  * return handle for file called "name", embedded in one of the hogfiles
251  */
252 FILE * cfile_find_embedded_file (char * name, int * length)
253 {
254         FILE * fp;
255         if (alt_hog) {
256                 fp = cfile_find_in_hog (name, length, alt_hog);
257                 if (fp)
258                         return fp;
259         }
260
261         if (builtin_hog) {
262                 fp = cfile_find_in_hog (name, length, builtin_hog);
263                 if (fp)
264                         return fp;
265         }
266
267         if (d1_hog) {
268                 fp = cfile_find_in_hog (name, length, d1_hog);
269                 if (fp)
270                         return fp;
271         }
272
273         return NULL;
274 }
275
276 int cfile_use_alternate_hogfile (char * name)
277 {
278         if (alt_hog) {
279                 d_free (alt_hog);
280                 alt_hog = NULL;
281         }
282         if (name) {
283                 alt_hog = cfile_init_hogfile (name);
284                 atexit (free_alt_hog);
285                 if (alt_hog)
286                         return 1; // success
287         }
288         return 0;
289 }
290
291 /* we use the d1 hog for d1 textures... */
292 int cfile_use_descent1_hogfile( char * name )
293 {
294         if (d1_hog) {
295                 d_free (d1_hog);
296                 d1_hog = NULL;
297         }
298         if (name) {
299                 d1_hog = cfile_init_hogfile (name);
300                 atexit (free_d1_hog);
301                 if (d1_hog)
302                         return 1; // success
303         }
304         return 0;
305 }
306
307 // cfeof() Tests for end-of-file on a stream
308 //
309 // returns a nonzero value after the first read operation that attempts to read
310 // past the end of the file. It returns 0 if the current position is not end of file.
311 // There is no error return.
312
313 int cfeof(CFILE *cfile)
314 {
315         Assert(cfile != NULL);
316
317         Assert(cfile->file != NULL);
318
319     return (cfile->raw_position >= cfile->size);
320 }
321
322
323 int cferror(CFILE *cfile)
324 {
325         return ferror(cfile->file);
326 }
327
328
329 int cfexist( char * filename )
330 {
331         int length;
332         FILE *fp;
333
334
335         if (filename[0] != '\x01')
336                 fp = cfile_get_filehandle( filename, "rb" );            // Check for non-hog file first...
337         else {
338                 fp = NULL;              //don't look in dir, only in hogfile
339                 filename++;
340         }
341
342         if ( fp )       {
343                 fclose(fp);
344                 return 1;
345         }
346
347         fp = cfile_find_embedded_file (filename, &length);
348         if ( fp )       {
349                 fclose(fp);
350                 return 2;               // file found in hog
351         }
352
353         return 0;               // Couldn't find it.
354 }
355
356
357 // Deletes a file.
358 int cfile_delete(char *filename)
359 {
360 #ifndef _WIN32_WCE
361         return remove(filename);
362 #else
363         return !DeleteFile(filename);
364 #endif
365 }
366
367
368 // Rename a file.
369 int cfile_rename(char *oldname, char *newname)
370 {
371 #ifndef _WIN32_WCE
372         return rename(oldname, newname);
373 #else
374         return !MoveFile(oldname, newname);
375 #endif
376 }
377
378
379 // Make a directory.
380 int cfile_mkdir(char *pathname)
381 {
382 #ifdef _WIN32
383 # ifdef _WIN32_WCE
384         return !CreateDirectory(pathname, NULL);
385 # else
386         return _mkdir(pathname);
387 # endif
388 #elif defined(macintosh)
389     char        mac_path[256];
390     Str255      pascal_path;
391     long        dirID;  // Insists on returning this
392
393     macify_posix_path(pathname, mac_path);
394     CopyCStringToPascal(mac_path, pascal_path);
395     return DirCreate(0, 0, pascal_path, &dirID);
396 #else
397         return mkdir(pathname, 0755);
398 #endif
399 }
400
401
402 CFILE * cfopen(char * filename, char * mode )
403 {
404         int length;
405         FILE * fp;
406         CFILE *cfile;
407
408         if (filename[0] != '\x01')
409                 fp = cfile_get_filehandle( filename, mode );            // Check for non-hog file first...
410         else {
411                 fp = NULL;              //don't look in dir, only in hogfile
412                 filename++;
413         }
414
415         if ( !fp ) {
416                 fp = cfile_find_embedded_file (filename, &length);
417                 if ( !fp )
418                         return NULL;            // No file found
419                 if (stricmp(mode, "rb"))
420                         Error("mode must be rb for files in hog.\n");
421                 cfile = d_malloc ( sizeof(CFILE) );
422                 if ( cfile == NULL ) {
423                         fclose(fp);
424                         return NULL;
425                 }
426                 cfile->file = fp;
427                 cfile->size = length;
428                 cfile->offset = ftell( fp );
429                 cfile->raw_position = 0;
430                 return cfile;
431         } else {
432                 cfile = d_malloc ( sizeof(CFILE) );
433                 if ( cfile == NULL ) {
434                         fclose(fp);
435                         return NULL;
436                 }
437                 cfile->file = fp;
438                 cfile->size = ffilelength(fp);
439                 cfile->offset = 0;
440                 cfile->raw_position = 0;
441                 return cfile;
442         }
443 }
444
445 int cfilelength( CFILE *fp )
446 {
447         return fp->size;
448 }
449
450
451 // cfwrite() writes to the file
452 //
453 // returns:   number of full elements actually written
454 //
455 //
456 int cfwrite(void *buf, int elsize, int nelem, CFILE *cfile)
457 {
458         int items_written;
459
460         Assert(cfile != NULL);
461         Assert(buf != NULL);
462         Assert(elsize > 0);
463
464         Assert(cfile->file != NULL);
465         Assert(cfile->offset == 0);
466
467         items_written = fwrite(buf, elsize, nelem, cfile->file);
468         cfile->raw_position = ftell(cfile->file);
469
470         return items_written;
471 }
472
473
474 // cfputc() writes a character to a file
475 //
476 // returns:   success ==> returns character written
477 //            error   ==> EOF
478 //
479 int cfputc(int c, CFILE *cfile)
480 {
481         int char_written;
482
483         Assert(cfile != NULL);
484
485         Assert(cfile->file != NULL);
486         Assert(cfile->offset == 0);
487
488         char_written = fputc(c, cfile->file);
489         cfile->raw_position = ftell(cfile->file);
490
491         return char_written;
492 }
493
494
495 int cfgetc( CFILE * fp )
496 {
497         int c;
498
499         if (fp->raw_position >= fp->size ) return EOF;
500
501         c = getc( fp->file );
502         if (c!=EOF)
503                 fp->raw_position = ftell(fp->file)-fp->offset;
504
505         return c;
506 }
507
508
509 // cfputs() writes a string to a file
510 //
511 // returns:   success ==> non-negative value
512 //            error   ==> EOF
513 //
514 int cfputs(char *str, CFILE *cfile)
515 {
516         int ret;
517
518         Assert(cfile != NULL);
519         Assert(str != NULL);
520
521         Assert(cfile->file != NULL);
522
523         ret = fputs(str, cfile->file);
524         cfile->raw_position = ftell(cfile->file);
525
526         return ret;
527 }
528
529
530 char * cfgets( char * buf, size_t n, CFILE * fp )
531 {
532         int i;
533         int c;
534
535 #if 0 // don't use the standard fgets, because it will only handle the native line-ending style
536         if (fp->offset == 0) // This is not an archived file
537         {
538                 t = fgets(buf, n, fp->file);
539                 fp->raw_position = ftell(fp->file);
540                 return t;
541         }
542 #endif
543
544         for (i=0; i<n-1; i++ ) {
545                 do {
546                         if (fp->raw_position >= fp->size ) {
547                                 *buf = 0;
548                                 return NULL;
549                         }
550                         c = cfgetc(fp);
551                         if (c == 0 || c == 10)        // Unix line ending
552                                 break;
553                         if (c == 13) {      // Mac or DOS line ending
554                                 int c1;
555
556                                 c1 = cfgetc(fp);
557                                 if (c1 != EOF)  // The file could end with a Mac line ending
558                                         cfseek(fp, -1, SEEK_CUR);
559                                 if ( c1 == 10 ) // DOS line ending
560                                         continue;
561                                 else            // Mac line ending
562                                         break;
563                         }
564                 } while ( c == 13 );
565                 if ( c == 13 )  // because cr-lf is a bad thing on the mac
566                         c = '\n';   // and anyway -- 0xod is CR on mac, not 0x0a
567                 if ( c=='\n' ) break;
568                 *buf++ = c;
569         }
570         *buf++ = 0;
571         return  buf;
572 }
573
574 size_t cfread( void * buf, size_t elsize, size_t nelem, CFILE * fp ) 
575 {
576         unsigned int i, size;
577
578         size = elsize * nelem;
579         if ( size < 1 ) return 0;
580
581         i = fread ( buf, 1, size, fp->file );
582         fp->raw_position += i;
583         return i/elsize;
584 }
585
586
587 int cftell( CFILE *fp ) 
588 {
589         return fp->raw_position;
590 }
591
592 int cfseek( CFILE *fp, long int offset, int where )
593 {
594         int c, goal_position;
595
596         switch( where ) {
597         case SEEK_SET:
598                 goal_position = offset;
599                 break;
600         case SEEK_CUR:
601                 goal_position = fp->raw_position+offset;
602                 break;
603         case SEEK_END:
604                 goal_position = fp->size+offset;
605                 break;
606         default:
607                 return 1;
608         }       
609         c = fseek( fp->file, fp->offset + goal_position, SEEK_SET );
610         fp->raw_position = ftell(fp->file)-fp->offset;
611         return c;
612 }
613
614 int cfclose(CFILE *fp)
615 {
616         int result;
617
618         result = fclose(fp->file);
619         d_free(fp);
620
621         return result;
622 }
623
624 // routines to read basic data types from CFILE's.  Put here to
625 // simplify mac/pc reading from cfiles.
626
627 int cfile_read_int(CFILE *file)
628 {
629         int32_t i;
630
631         if (cfread( &i, sizeof(i), 1, file) != 1)
632                 Error( "Error reading int in cfile_read_int()" );
633
634         i = INTEL_INT(i);
635         return i;
636 }
637
638 short cfile_read_short(CFILE *file)
639 {
640         int16_t s;
641
642         if (cfread( &s, sizeof(s), 1, file) != 1)
643                 Error( "Error reading short in cfile_read_short()" );
644
645         s = INTEL_SHORT(s);
646         return s;
647 }
648
649 sbyte cfile_read_byte(CFILE *file)
650 {
651         sbyte b;
652
653         if (cfread( &b, sizeof(b), 1, file) != 1)
654                 Error( "Error reading byte in cfile_read_byte()" );
655
656         return b;
657 }
658
659 fix cfile_read_fix(CFILE *file)
660 {
661         fix f;
662
663         if (cfread( &f, sizeof(f), 1, file) != 1)
664                 Error( "Error reading fix in cfile_read_fix()" );
665
666         f = (fix)INTEL_INT((int)f);
667         return f;
668 }
669
670 fixang cfile_read_fixang(CFILE *file)
671 {
672         fixang f;
673
674         if (cfread(&f, 2, 1, file) != 1)
675                 Error("Error reading fixang in cfile_read_fixang()");
676
677         f = (fixang) INTEL_SHORT((int) f);
678         return f;
679 }
680
681 void cfile_read_vector(vms_vector *v, CFILE *file)
682 {
683         v->x = cfile_read_fix(file);
684         v->y = cfile_read_fix(file);
685         v->z = cfile_read_fix(file);
686 }
687
688 void cfile_read_angvec(vms_angvec *v, CFILE *file)
689 {
690         v->p = cfile_read_fixang(file);
691         v->b = cfile_read_fixang(file);
692         v->h = cfile_read_fixang(file);
693 }
694
695 void cfile_read_matrix(vms_matrix *m,CFILE *file)
696 {
697         cfile_read_vector(&m->rvec,file);
698         cfile_read_vector(&m->uvec,file);
699         cfile_read_vector(&m->fvec,file);
700 }
701
702
703 void cfile_read_string(char *buf, int n, CFILE *file)
704 {
705         char c;
706
707         do {
708                 c = (char)cfile_read_byte(file);
709                 if (n > 0)
710                 {
711                         *buf++ = c;
712                         n--;
713                 }
714         } while (c != 0);
715 }
716
717
718 // equivalent write functions of above read functions follow
719
720 int cfile_write_int(int i, CFILE *file)
721 {
722         i = INTEL_INT(i);
723         return cfwrite(&i, sizeof(i), 1, file);
724 }
725
726
727 int cfile_write_short(short s, CFILE *file)
728 {
729         s = INTEL_SHORT(s);
730         return cfwrite(&s, sizeof(s), 1, file);
731 }
732
733
734 int cfile_write_byte(sbyte b, CFILE *file)
735 {
736         return cfwrite(&b, sizeof(b), 1, file);
737 }
738
739
740 int cfile_write_string(char *buf, CFILE *file)
741 {
742         int len;
743
744         if ((!buf) || (buf && !buf[0]))
745                 return cfile_write_byte(0, file);
746
747         len = strlen(buf);
748         if (!cfwrite(buf, len, 1, file))
749                 return 0;
750
751         return cfile_write_byte(0, file);   // write out NULL termination
752 }