use standard fgets for non-archive files
[btb/d2x.git] / cfile / cfile.c
1 /* $Id: cfile.c,v 1.13 2003-06-22 23:06:00 btb 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  *
19  * Old Log:
20  * Revision 1.7  1995/10/27  15:18:20  allender
21  * get back to descent directory before trying to read a hog file
22  *
23  * Revision 1.6  1995/10/21  23:48:24  allender
24  * hogfile(s) are now in :Data: folder
25  *
26  * Revision 1.5  1995/08/14  09:27:31  allender
27  * added byteswap header
28  *
29  * Revision 1.4  1995/05/12  11:54:33  allender
30  * changed memory stuff again
31  *
32  * Revision 1.3  1995/05/04  20:03:38  allender
33  * added code that was missing...use NewPtr instead of malloc
34  *
35  * Revision 1.2  1995/04/03  09:59:49  allender
36  * *** empty log message ***
37  *
38  * Revision 1.1  1995/03/30  10:25:02  allender
39  * Initial revision
40  *
41  *
42  * --- PC RCS Information ---
43  * Revision 1.24  1995/03/15  14:20:27  john
44  * Added critical error checker.
45  *
46  * Revision 1.23  1995/03/13  15:16:53  john
47  * Added alternate directory stuff.
48  *
49  * Revision 1.22  1995/02/09  23:08:47  matt
50  * Increased the max number of files in hogfile to 250
51  *
52  * Revision 1.21  1995/02/01  20:56:47  john
53  * Added cfexist function
54  *
55  * Revision 1.20  1995/01/21  17:53:48  john
56  * Added alternate pig file thing.
57  *
58  * Revision 1.19  1994/12/29  15:10:02  john
59  * Increased hogfile max files to 200.
60  *
61  * Revision 1.18  1994/12/12  13:20:57  john
62  * Made cfile work with fiellentth.
63  *
64  * Revision 1.17  1994/12/12  13:14:25  john
65  * Made cfiles prefer non-hog files.
66  *
67  * Revision 1.16  1994/12/09  18:53:26  john
68  * *** empty log message ***
69  *
70  * Revision 1.15  1994/12/09  18:52:56  john
71  * Took out mem, error checking.
72  *
73  * Revision 1.14  1994/12/09  18:10:31  john
74  * Speed up cfgets, which was slowing down the reading of
75  * bitmaps.tbl, which was making POF loading look slow.
76  *
77  * Revision 1.13  1994/12/09  17:53:51  john
78  * Added error checking to number of hogfiles..
79  *
80  * Revision 1.12  1994/12/08  19:02:55  john
81  * Added cfgets.
82  *
83  * Revision 1.11  1994/12/07  21:57:48  john
84  * Took out data dir.
85  *
86  * Revision 1.10  1994/12/07  21:38:02  john
87  * Made cfile not return error..
88  *
89  * Revision 1.9  1994/12/07  21:35:34  john
90  * Made it read from data directory.
91  *
92  * Revision 1.8  1994/12/07  21:33:55  john
93  * Stripped out compression stuff...
94  *
95  * Revision 1.7  1994/04/13  23:44:59  matt
96  * When file cannot be opened, free up the buffer for that file.
97  *
98  * Revision 1.6  1994/02/18  12:38:20  john
99  * Optimized a bit
100  *
101  * Revision 1.5  1994/02/15  18:13:20  john
102  * Fixed more bugs.
103  *
104  * Revision 1.4  1994/02/15  13:27:58  john
105  * Works ok...
106  *
107  * Revision 1.3  1994/02/15  12:51:57  john
108  * Crappy inbetween version
109  *
110  * Revision 1.2  1994/02/14  20:12:29  john
111  * First version working with new cfile stuff.
112  *
113  * Revision 1.1  1994/02/14  15:51:33  john
114  * Initial revision
115  *
116  * Revision 1.1  1994/02/10  15:45:12  john
117  * Initial revision
118  *
119  *
120  */
121
122 #ifdef HAVE_CONFIG_H
123 #include <conf.h>
124 #endif
125
126 #include <stdio.h>
127 #include <string.h>
128 #include <sys/stat.h>
129
130 #include "pstypes.h"
131 #include "u_mem.h"
132 #include "strutil.h"
133 #include "d_io.h"
134 #include "error.h"
135 #include "cfile.h"
136 #include "byteswap.h"
137
138 struct CFILE {
139         FILE    *file;
140         int     size;
141         int     lib_offset;
142         int     raw_position;
143 };
144
145 typedef struct hogfile {
146         char    name[13];
147         int     offset;
148         int     length;
149 } hogfile;
150
151 #define MAX_HOGFILES 300
152
153 hogfile HogFiles[MAX_HOGFILES];
154 char Hogfile_initialized = 0;
155 int Num_hogfiles = 0;
156 char HogFilename[64];
157
158 hogfile D1HogFiles[MAX_HOGFILES];
159 char D1Hogfile_initialized = 0;
160 int D1Num_hogfiles = 0;
161 char D1HogFilename[64];
162
163 hogfile AltHogFiles[MAX_HOGFILES];
164 char AltHogfile_initialized = 0;
165 int AltNum_hogfiles = 0;
166 char AltHogFilename[64];
167
168 char AltHogDir[64];
169 char AltHogdir_initialized = 0;
170
171 // routine to take a DOS path and turn it into a macintosh
172 // pathname.  This routine is based on the fact that we should
173 // see a \ character in the dos path.  The sequence .\ a tthe
174 // beginning of a path is turned into a :
175
176 #ifdef MACINTOSH
177 void macify_dospath(char *dos_path, char *mac_path)
178 {
179         char *p;
180
181         if (!strncmp(dos_path, ".\\", 2)) {
182                 strcpy(mac_path, ":");
183                 strcat(mac_path, &(dos_path[2]) );
184         } else
185                 strcpy(mac_path, dos_path);
186
187         while ( (p = strchr(mac_path, '\\')) != NULL)
188                 *p = ':';
189
190 }
191 #endif
192
193 void cfile_use_alternate_hogdir( char * path )
194 {
195         if ( path )     {
196                 strcpy( AltHogDir, path );
197                 AltHogdir_initialized = 1;
198         } else {
199                 AltHogdir_initialized = 0;
200         }
201 }
202
203 //in case no one installs one
204 int default_error_counter=0;
205
206 //ptr to counter of how many critical errors
207 int *critical_error_counter_ptr=&default_error_counter;
208
209 //tell cfile about your critical error counter
210 void cfile_set_critical_error_counter_ptr(int *ptr)
211 {
212         critical_error_counter_ptr = ptr;
213
214 }
215
216
217 FILE * cfile_get_filehandle( char * filename, char * mode )
218 {
219         FILE * fp;
220         char temp[128];
221
222         *critical_error_counter_ptr = 0;
223         fp = fopen( filename, mode );
224         if ( fp && *critical_error_counter_ptr )        {
225                 fclose(fp);
226                 fp = NULL;
227         }
228         if ( (fp==NULL) && (AltHogdir_initialized) )    {
229                 strcpy( temp, AltHogDir );
230                 strcat( temp, "/");
231                 strcat( temp, filename );
232                 *critical_error_counter_ptr = 0;
233                 fp = fopen( temp, mode );
234                 if ( fp && *critical_error_counter_ptr )        {
235                         fclose(fp);
236                         fp = NULL;
237                 }
238         }
239         return fp;
240 }
241
242 //returns 1 if file loaded with no errors
243 int cfile_init_hogfile(char *fname, hogfile * hog_files, int * nfiles )
244 {
245         char id[4];
246         FILE * fp;
247         int i, len;
248
249         *nfiles = 0;
250
251         fp = cfile_get_filehandle( fname, "rb" );
252         if ( fp == NULL ) return 0;
253
254         fread( id, 3, 1, fp );
255         if ( strncmp( id, "DHF", 3 ) )  {
256                 fclose(fp);
257                 return 0;
258         }
259
260         while( 1 )
261         {
262                 if ( *nfiles >= MAX_HOGFILES ) {
263                         fclose(fp);
264                         Error( "HOGFILE is limited to %d files.\n",  MAX_HOGFILES );
265                 }
266                 i = fread( hog_files[*nfiles].name, 13, 1, fp );
267                 if ( i != 1 )   {               //eof here is ok
268                         fclose(fp);
269                         return 1;
270                 }
271                 i = fread( &len, 4, 1, fp );
272                 if ( i != 1 )   {
273                         fclose(fp);
274                         return 0;
275                 }
276                 hog_files[*nfiles].length = INTEL_INT(len);
277                 hog_files[*nfiles].offset = ftell( fp );
278                 *nfiles = (*nfiles) + 1;
279                 // Skip over
280                 i = fseek( fp, INTEL_INT(len), SEEK_CUR );
281         }
282 }
283
284 //Specify the name of the hogfile.  Returns 1 if hogfile found & had files
285 int cfile_init(char *hogname)
286 {
287         #ifdef MACINTOSH
288         char mac_path[255];
289
290         macify_dospath(hogname, mac_path);
291         #endif
292
293         Assert(Hogfile_initialized == 0);
294
295         #ifndef MACINTOSH
296         if (cfile_init_hogfile(hogname, HogFiles, &Num_hogfiles )) {
297                 strcpy( HogFilename, hogname );
298         #else
299         if (cfile_init_hogfile(mac_path, HogFiles, &Num_hogfiles )) {
300                 strcpy( HogFilename, mac_path );
301         #endif
302                 Hogfile_initialized = 1;
303                 return 1;
304         }
305         else
306                 return 0;       //not loaded!
307 }
308
309
310 int cfile_size(char *hogname)
311 {
312         CFILE *fp;
313         struct stat statbuf;
314
315         fp = cfopen(hogname, "rb");
316         if (fp == NULL)
317                 return -1;
318         fstat(fileno(fp->file), &statbuf);
319         cfclose(fp);
320         return statbuf.st_size;
321 }
322
323 /*
324  * return handle for file called "name", embedded in one of the hogfiles
325  */
326 FILE * cfile_find_libfile(char * name, int * length)
327 {
328         FILE * fp;
329         int i;
330
331         if ( AltHogfile_initialized )   {
332                 for (i=0; i<AltNum_hogfiles; i++ )      {
333                         if ( !stricmp( AltHogFiles[i].name, name ))     {
334                                 fp = cfile_get_filehandle( AltHogFilename, "rb" );
335                                 if ( fp == NULL ) return NULL;
336                                 fseek( fp,  AltHogFiles[i].offset, SEEK_SET );
337                                 *length = AltHogFiles[i].length;
338                                 return fp;
339                         }
340                 }
341         }
342
343         if ( !Hogfile_initialized )     {
344                 //@@cfile_init_hogfile( "DESCENT2.HOG", HogFiles, &Num_hogfiles );
345                 //@@Hogfile_initialized = 1;
346
347                 //Int3();       //hogfile ought to be initialized
348         }
349
350         for (i=0; i<Num_hogfiles; i++ ) {
351                 if ( !stricmp( HogFiles[i].name, name ))        {
352                         fp = cfile_get_filehandle( HogFilename, "rb" );
353                         if ( fp == NULL ) return NULL;
354                         fseek( fp,  HogFiles[i].offset, SEEK_SET );
355                         *length = HogFiles[i].length;
356                         return fp;
357                 }
358         }
359
360         if (D1Hogfile_initialized)      {
361                 for (i = 0; i < D1Num_hogfiles; i++) {
362                         if (!stricmp(D1HogFiles[i].name, name)) {
363                                 fp = cfile_get_filehandle(D1HogFilename, "rb");
364                                 if (fp == NULL) return NULL;
365                                 fseek(fp,  D1HogFiles[i].offset, SEEK_SET);
366                                 *length = D1HogFiles[i].length;
367                                 return fp;
368                         }
369                 }
370         }
371
372         return NULL;
373 }
374
375 int cfile_use_alternate_hogfile( char * name )
376 {
377         if ( name )     {
378                 #ifdef MACINTOSH
379                 char mac_path[255];
380
381                 macify_dospath(name, mac_path);
382                 strcpy( AltHogFilename, mac_path);
383                 #else
384                 strcpy( AltHogFilename, name );
385                 #endif
386                 cfile_init_hogfile( AltHogFilename, AltHogFiles, &AltNum_hogfiles );
387                 AltHogfile_initialized = 1;
388                 return (AltNum_hogfiles > 0);
389         } else {
390                 AltHogfile_initialized = 0;
391                 return 1;
392         }
393 }
394
395 int cfile_use_descent1_hogfile( char * name )
396 {
397         if (name)       {
398 #ifdef MACINTOSH
399                 char mac_path[255];
400
401                 macify_dospath(name, mac_path);
402                 strcpy(D1HogFilename, mac_path);
403 #else
404                 strcpy(D1HogFilename, name);
405 #endif
406                 cfile_init_hogfile(D1HogFilename, D1HogFiles, &D1Num_hogfiles);
407                 D1Hogfile_initialized = 1;
408                 return (D1Num_hogfiles > 0);
409         } else {
410                 D1Hogfile_initialized = 0;
411                 return 1;
412         }
413 }
414
415
416 // cfeof() Tests for end-of-file on a stream
417 //
418 // returns a nonzero value after the first read operation that attempts to read
419 // past the end of the file. It returns 0 if the current position is not end of file.
420 // There is no error return.
421
422 int cfeof(CFILE *cfile)
423 {
424         Assert(cfile != NULL);
425
426         Assert(cfile->file != NULL);
427
428     return (cfile->raw_position >= cfile->size);
429 }
430
431
432 int cferror(CFILE *cfile)
433 {
434         return ferror(cfile->file);
435 }
436
437
438 int cfexist( char * filename )
439 {
440         int length;
441         FILE *fp;
442
443
444         if (filename[0] != '\x01')
445                 fp = cfile_get_filehandle( filename, "rb" );            // Check for non-hog file first...
446         else {
447                 fp = NULL;              //don't look in dir, only in hogfile
448                 filename++;
449         }
450
451         if ( fp )       {
452                 fclose(fp);
453                 return 1;
454         }
455
456         fp = cfile_find_libfile(filename, &length );
457         if ( fp )       {
458                 fclose(fp);
459                 return 2;               // file found in hog
460         }
461
462         return 0;               // Couldn't find it.
463 }
464
465
466 // Deletes a file.
467 int cfile_delete(char *filename)
468 {
469         return remove(filename);
470 }
471
472
473 // Rename a file.
474 int cfile_rename(char *oldname, char *newname)
475 {
476         return rename(oldname, newname);
477 }
478
479
480 // Make a directory.
481 int cfile_mkdir(char *pathname)
482 {
483 #if defined(__WINDOWS__) || defined(__MINGW32__)
484         return mkdir(pathname);
485 #else
486         return mkdir(pathname, 0755);
487 #endif
488 }
489
490
491 CFILE * cfopen(char * filename, char * mode )
492 {
493         int length;
494         FILE * fp;
495         CFILE *cfile;
496
497         if (filename[0] != '\x01') {
498                 #ifdef MACINTOSH
499                 char mac_path[255];
500
501                 macify_dospath(filename, mac_path);
502                 fp = cfile_get_filehandle( mac_path, mode);
503                 #else
504                 fp = cfile_get_filehandle( filename, mode );            // Check for non-hog file first...
505                 #endif
506         } else {
507                 fp = NULL;              //don't look in dir, only in hogfile
508                 filename++;
509         }
510
511         if ( !fp ) {
512                 fp = cfile_find_libfile(filename, &length );
513                 if ( !fp )
514                         return NULL;            // No file found
515                 if (stricmp(mode, "rb"))
516                         Error("mode must be rb for files in hog.\n");
517                 cfile = d_malloc ( sizeof(CFILE) );
518                 if ( cfile == NULL ) {
519                         fclose(fp);
520                         return NULL;
521                 }
522                 cfile->file = fp;
523                 cfile->size = length;
524                 cfile->lib_offset = ftell( fp );
525                 cfile->raw_position = 0;
526                 return cfile;
527         } else {
528                 cfile = d_malloc ( sizeof(CFILE) );
529                 if ( cfile == NULL ) {
530                         fclose(fp);
531                         return NULL;
532                 }
533                 cfile->file = fp;
534                 cfile->size = filelength( fileno(fp) );
535                 cfile->lib_offset = 0;
536                 cfile->raw_position = 0;
537                 return cfile;
538         }
539 }
540
541 int cfilelength( CFILE *fp )
542 {
543         return fp->size;
544 }
545
546
547 // cfwrite() writes to the file
548 //
549 // returns:   number of full elements actually written
550 //
551 //
552 int cfwrite(void *buf, int elsize, int nelem, CFILE *cfile)
553 {
554         Assert(cfile != NULL);
555         Assert(buf != NULL);
556         Assert(elsize > 0);
557
558         Assert(cfile->file != NULL);
559         Assert(cfile->lib_offset == 0);
560
561         return fwrite(buf, elsize, nelem, cfile->file);
562 }
563
564
565 // cfputc() writes a character to a file
566 //
567 // returns:   success ==> returns character written
568 //            error   ==> EOF
569 //
570 int cfputc(int c, CFILE *cfile)
571 {
572         Assert(cfile != NULL);
573
574         Assert(cfile->file != NULL);
575         Assert(cfile->lib_offset == 0);
576
577         return fputc(c, cfile->file);
578 }
579
580
581 int cfgetc( CFILE * fp )
582 {
583         int c;
584
585         if (fp->raw_position >= fp->size ) return EOF;
586
587         c = getc( fp->file );
588         if (c!=EOF)
589                 fp->raw_position++;
590
591 //      Assert( fp->raw_position==(ftell(fp->file)-fp->lib_offset) );
592
593         return c;
594 }
595
596
597 // cfputs() writes a string to a file
598 //
599 // returns:   success ==> non-negative value
600 //            error   ==> EOF
601 //
602 int cfputs(char *str, CFILE *cfile)
603 {
604         Assert(cfile != NULL);
605         Assert(str != NULL);
606
607         Assert(cfile->file != NULL);
608
609         return fputs(str, cfile->file);
610 }
611
612
613 char * cfgets( char * buf, size_t n, CFILE * fp )
614 {
615         char * t = buf;
616         int i;
617         int c;
618
619         if (fp->lib_offset == 0) // This is not an archived file
620         {
621                 t = fgets(buf, n, fp->file);
622                 fp->raw_position = ftell(fp->file);
623                 return t;
624         }
625
626         for (i=0; i<n-1; i++ ) {
627                 do {
628                         if (fp->raw_position >= fp->size ) {
629                                 *buf = 0;
630                                 return NULL;
631                         }
632                         c = fgetc( fp->file );
633                         fp->raw_position++;
634                         if (c == 0 || c == 10)        // Unix line ending
635                                 break;
636                         if (c == 13) {      // Mac or DOS line ending
637                                 int c1;
638
639                                 c1 = fgetc( fp->file );
640                                 fseek( fp->file, -1, SEEK_CUR);
641                                 if ( c1 == 10 ) // DOS line ending
642                                         continue;
643                                 else            // Mac line ending
644                                         break;
645                         }
646                 } while ( c == 13 );
647                 if ( c == 13 )  // because cr-lf is a bad thing on the mac
648                         c = '\n';   // and anyway -- 0xod is CR on mac, not 0x0a
649                 *buf++ = c;
650                 if ( c=='\n' ) break;
651         }
652         *buf++ = 0;
653         return  t;
654 }
655
656 size_t cfread( void * buf, size_t elsize, size_t nelem, CFILE * fp ) 
657 {
658         unsigned int i, size;
659
660         size = elsize * nelem;
661         if ( size < 1 ) return 0;
662
663         i = fread ( buf, 1, size, fp->file );
664         fp->raw_position += i;
665         return i/elsize;
666 }
667
668
669 int cftell( CFILE *fp ) 
670 {
671         return fp->raw_position;
672 }
673
674 int cfseek( CFILE *fp, long int offset, int where )
675 {
676         int c, goal_position;
677
678         switch( where ) {
679         case SEEK_SET:
680                 goal_position = offset;
681                 break;
682         case SEEK_CUR:
683                 goal_position = fp->raw_position+offset;
684                 break;
685         case SEEK_END:
686                 goal_position = fp->size+offset;
687                 break;
688         default:
689                 return 1;
690         }       
691         c = fseek( fp->file, fp->lib_offset + goal_position, SEEK_SET );
692         fp->raw_position = ftell(fp->file)-fp->lib_offset;
693         return c;
694 }
695
696 int cfclose(CFILE *fp)
697 {
698         int result;
699
700         result = fclose(fp->file);
701         d_free(fp);
702
703         return result;
704 }
705
706 // routines to read basic data types from CFILE's.  Put here to
707 // simplify mac/pc reading from cfiles.
708
709 int cfile_read_int(CFILE *file)
710 {
711         int32_t i;
712
713         if (cfread( &i, sizeof(i), 1, file) != 1)
714                 Error( "Error reading int in cfile_read_int()" );
715
716         i = INTEL_INT(i);
717         return i;
718 }
719
720 short cfile_read_short(CFILE *file)
721 {
722         int16_t s;
723
724         if (cfread( &s, sizeof(s), 1, file) != 1)
725                 Error( "Error reading short in cfile_read_short()" );
726
727         s = INTEL_SHORT(s);
728         return s;
729 }
730
731 byte cfile_read_byte(CFILE *file)
732 {
733         byte b;
734
735         if (cfread( &b, sizeof(b), 1, file) != 1)
736                 Error( "Error reading byte in cfile_read_byte()" );
737
738         return b;
739 }
740
741 fix cfile_read_fix(CFILE *file)
742 {
743         fix f;
744
745         if (cfread( &f, sizeof(f), 1, file) != 1)
746                 Error( "Error reading fix in cfile_read_fix()" );
747
748         f = (fix)INTEL_INT((int)f);
749         return f;
750 }
751
752 fixang cfile_read_fixang(CFILE *file)
753 {
754         fixang f;
755
756         if (cfread(&f, 2, 1, file) != 1)
757                 Error("Error reading fixang in cfile_read_fixang()");
758
759         f = (fixang) INTEL_SHORT((int) f);
760         return f;
761 }
762
763 void cfile_read_vector(vms_vector *v, CFILE *file)
764 {
765         v->x = cfile_read_fix(file);
766         v->y = cfile_read_fix(file);
767         v->z = cfile_read_fix(file);
768 }
769
770 void cfile_read_angvec(vms_angvec *v, CFILE *file)
771 {
772         v->p = cfile_read_fixang(file);
773         v->b = cfile_read_fixang(file);
774         v->h = cfile_read_fixang(file);
775 }
776
777 void cfile_read_matrix(vms_matrix *m,CFILE *file)
778 {
779         cfile_read_vector(&m->rvec,file);
780         cfile_read_vector(&m->uvec,file);
781         cfile_read_vector(&m->fvec,file);
782 }
783
784
785 void cfile_read_string(char *buf, int n, CFILE *file)
786 {
787         char c;
788
789         do {
790                 c = (char)cfile_read_byte(file);
791                 if (n > 0)
792                 {
793                         *buf++ = c;
794                         n--;
795                 }
796         } while (c != 0);
797 }
798
799
800 // equivalent write functions of above read functions follow
801
802 int cfile_write_int(int i, CFILE *file)
803 {
804         i = INTEL_INT(i);
805         return cfwrite(&i, sizeof(i), 1, file);
806 }
807
808
809 int cfile_write_short(short s, CFILE *file)
810 {
811         s = INTEL_SHORT(s);
812         return cfwrite(&s, sizeof(s), 1, file);
813 }
814
815
816 int cfile_write_byte(byte b, CFILE *file)
817 {
818         return cfwrite(&b, sizeof(b), 1, file);
819 }
820
821
822 int cfile_write_string(char *buf, CFILE *file)
823 {
824         int len;
825
826         if ((!buf) || (buf && !buf[0]))
827                 return cfile_write_byte(0, file);
828
829         len = strlen(buf);
830         if (!cfwrite(buf, len, 1, file))
831                 return 0;
832
833         return cfile_write_byte(0, file);   // write out NULL termination
834 }