1 #define _BSD_SOURCE
2
3 #include <assert.h>
4 #include <unistd.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <time.h>
9 #include <ctype.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <fcntl.h>
13 #include <dirent.h>
14 #include <fnmatch.h>
15
16 #include "fixer.h"
17 #include "files.h"
18
19 static char *local_dir;
20 static char *global_dir;
21
22 /*
23 Sets the local and global directories used by the other functions.
24 Local = ~/.dir, where config and user-installed files are kept.
25 Global = installdir, where installed data is stored.
26 */
SetGameDirectories(const char * local,const char * global)27 int SetGameDirectories(const char *local, const char *global)
28 {
29 struct stat buf;
30
31 local_dir = strdup(local);
32 global_dir = strdup(global);
33
34 if (stat(local_dir, &buf) == -1) {
35 printf("Creating local directory %s...\n", local_dir);
36
37 mkdir(local_dir, S_IRWXU);
38 }
39
40 return 0;
41 }
42
43 #define DIR_SEPARATOR "/"
44
FixFilename(const char * filename,const char * prefix,int force)45 static char *FixFilename(const char *filename, const char *prefix, int force)
46 {
47 char *f, *ptr;
48 int flen;
49 int plen;
50
51 plen = strlen(prefix) + 1;
52 flen = strlen(filename) + plen + 1;
53
54 f = (char *)malloc(flen);
55 strcpy(f, prefix);
56 strcat(f, DIR_SEPARATOR);
57 strcat(f, filename);
58
59 /* only the filename part needs to be modified */
60 ptr = &f[plen+1];
61
62 while (*ptr) {
63 if ((*ptr == '/') || (*ptr == '\\') || (*ptr == ':')) {
64 *ptr = DIR_SEPARATOR[0];
65 } else if (*ptr == '\r' || *ptr == '\n') {
66 *ptr = 0;
67 break;
68 } else {
69 if (force) {
70 *ptr = tolower(*ptr);
71 }
72 }
73 ptr++;
74 }
75
76 return f;
77 }
78
79 /*
80 Open a file of type type, with mode mode.
81
82 Mode can be:
83 #define FILEMODE_READONLY 0x01
84 #define FILEMODE_WRITEONLY 0x02
85 #define FILEMODE_READWRITE 0x04
86 #define FILEMODE_APPEND 0x08
87 Type is (mode = ReadOnly):
88 #define FILETYPE_PERM 0x08 // try the global dir only
89 #define FILETYPE_OPTIONAL 0x10 // try the global dir first, then try the local dir
90 #define FILETYPE_CONFIG 0x20 // try the local dir only
91
92 Type is (mode = WriteOnly or ReadWrite):
93 FILETYPE_PERM: error
94 FILETYPE_OPTIONAL: error
95 FILETYPE_CONFIG: try the local dir only
96 */
OpenGameFile(const char * filename,int mode,int type)97 FILE *OpenGameFile(const char *filename, int mode, int type)
98 {
99 char *rfilename;
100 char *openmode;
101 FILE *fp;
102
103 if ((type != FILETYPE_CONFIG) && (mode != FILEMODE_READONLY))
104 return NULL;
105
106 switch(mode) {
107 case FILEMODE_READONLY:
108 openmode = "rb";
109 break;
110 case FILEMODE_WRITEONLY:
111 openmode = "wb";
112 break;
113 case FILEMODE_READWRITE:
114 openmode = "w+";
115 break;
116 case FILEMODE_APPEND:
117 openmode = "ab";
118 break;
119 default:
120 return NULL;
121 }
122
123 if (type != FILETYPE_CONFIG) {
124 rfilename = FixFilename(filename, global_dir, 0);
125
126 fp = fopen(rfilename, openmode);
127
128 free(rfilename);
129
130 if (fp != NULL) {
131 return fp;
132 }
133
134 rfilename = FixFilename(filename, global_dir, 1);
135
136 fp = fopen(rfilename, openmode);
137
138 free(rfilename);
139
140 if (fp != NULL) {
141 return fp;
142 }
143 }
144
145 if (type != FILETYPE_PERM) {
146 rfilename = FixFilename(filename, local_dir, 0);
147
148 fp = fopen(rfilename, openmode);
149
150 free(rfilename);
151
152 if (fp != NULL) {
153 return fp;
154 }
155
156 rfilename = FixFilename(filename, local_dir, 1);
157
158 fp = fopen(rfilename, openmode);
159
160 free(rfilename);
161
162 return fp;
163 }
164
165 return NULL;
166 }
167
168 /*
169 Close a fd returned from OpenGameFile
170
171 Currently this just uses stdio, so CloseGameFile is redundant.
172 */
CloseGameFile(FILE * pfd)173 int CloseGameFile(FILE *pfd)
174 {
175 return fclose(pfd);
176 }
177
178
179 /*
180 Get the filesystem attributes of a file
181
182 #define FILEATTR_DIRECTORY 0x0100
183 #define FILEATTR_READABLE 0x0200
184 #define FILEATTR_WRITABLE 0x0400
185
186 Error or can't access it: return value of 0 (What is the game going to do about it anyway?)
187 */
GetFA(const char * filename)188 static int GetFA(const char *filename)
189 {
190 struct stat buf;
191 int attr;
192
193 attr = 0;
194 if (stat(filename, &buf) == 0) {
195 if (S_ISDIR(buf.st_mode)) {
196 attr |= FILEATTR_DIRECTORY;
197 }
198
199 if (access(filename, R_OK) == 0) {
200 attr |= FILEATTR_READABLE;
201 }
202
203 if (access(filename, W_OK) == 0) {
204 attr |= FILEATTR_WRITABLE;
205 }
206 }
207
208 return attr;
209 }
210
GetTS(const char * filename)211 static time_t GetTS(const char *filename)
212 {
213 struct stat buf;
214
215 if (stat(filename, &buf) == 0) {
216 return buf.st_mtime;
217 }
218
219 return 0;
220 }
221
GetGameFileAttributes(const char * filename,int type)222 int GetGameFileAttributes(const char *filename, int type)
223 {
224 struct stat buf;
225 char *rfilename;
226 int attr;
227
228 attr = 0;
229 if (type != FILETYPE_CONFIG) {
230 rfilename = FixFilename(filename, global_dir, 0);
231
232 if (stat(rfilename, &buf) == 0) {
233 if (S_ISDIR(buf.st_mode)) {
234 attr |= FILEATTR_DIRECTORY;
235 }
236
237 if (access(rfilename, R_OK) == 0) {
238 attr |= FILEATTR_READABLE;
239 }
240
241 if (access(rfilename, W_OK) == 0) {
242 attr |= FILEATTR_WRITABLE;
243 }
244
245 free(rfilename);
246
247 return attr;
248 }
249
250 free(rfilename);
251
252 rfilename = FixFilename(filename, global_dir, 1);
253
254 if (stat(rfilename, &buf) == 0) {
255 if (S_ISDIR(buf.st_mode)) {
256 attr |= FILEATTR_DIRECTORY;
257 }
258
259 if (access(rfilename, R_OK) == 0) {
260 attr |= FILEATTR_READABLE;
261 }
262
263 if (access(rfilename, W_OK) == 0) {
264 attr |= FILEATTR_WRITABLE;
265 }
266
267 free(rfilename);
268
269 return attr;
270 }
271
272 free(rfilename);
273 }
274
275 if (type != FILETYPE_PERM) {
276 rfilename = FixFilename(filename, local_dir, 0);
277
278 if (stat(rfilename, &buf) == 0) {
279 if (S_ISDIR(buf.st_mode)) {
280 attr |= FILEATTR_DIRECTORY;
281 }
282
283 if (access(rfilename, R_OK) == 0) {
284 attr |= FILEATTR_READABLE;
285 }
286
287 if (access(rfilename, W_OK) == 0) {
288 attr |= FILEATTR_WRITABLE;
289 }
290
291 free(rfilename);
292
293 return attr;
294 }
295
296 free(rfilename);
297
298 rfilename = FixFilename(filename, local_dir, 1);
299
300 if (stat(rfilename, &buf) == 0) {
301 if (S_ISDIR(buf.st_mode)) {
302 attr |= FILEATTR_DIRECTORY;
303 }
304
305 if (access(rfilename, R_OK) == 0) {
306 attr |= FILEATTR_READABLE;
307 }
308
309 if (access(rfilename, W_OK) == 0) {
310 attr |= FILEATTR_WRITABLE;
311 }
312
313 free(rfilename);
314
315 return attr;
316 }
317
318 free(rfilename);
319
320 }
321
322 return 0;
323 }
324
325 /*
326 Delete a file: local dir only
327 */
DeleteGameFile(const char * filename)328 int DeleteGameFile(const char *filename)
329 {
330 char *rfilename;
331 int ret;
332
333 rfilename = FixFilename(filename, local_dir, 0);
334 ret = unlink(rfilename);
335 free(rfilename);
336
337 if (ret == -1) {
338 rfilename = FixFilename(filename, local_dir, 1);
339 ret = unlink(rfilename);
340 free(rfilename);
341 }
342
343 return ret;
344 }
345
346 /*
347 Create a directory: local dir only
348
349 TODO: maybe also mkdir parent directories, if they do not exist?
350 */
CreateGameDirectory(const char * dirname)351 int CreateGameDirectory(const char *dirname)
352 {
353 char *rfilename;
354 int ret;
355
356 rfilename = FixFilename(dirname, local_dir, 0);
357 ret = mkdir(rfilename, S_IRWXU);
358 free(rfilename);
359
360 if (ret == -1) {
361 rfilename = FixFilename(dirname, local_dir, 1);
362 ret = mkdir(rfilename, S_IRWXU);
363 free(rfilename);
364 }
365
366 return ret;
367 }
368
369 /* This struct is private. */
370 typedef struct GameDirectory
371 {
372 DIR *localdir; /* directory opened with opendir */
373 DIR *globaldir;
374
375 char *localdirname;
376 char *globaldirname;
377
378 char *pat; /* pattern to match */
379
380 GameDirectoryFile tmp; /* Temp space */
381 } GameDirectory;
382
383 /*
384 "Open" a directory dirname, with type type
385 Returns a pointer to a directory datatype
386
387 Pattern is the pattern to match
388 */
OpenGameDirectory(const char * dirname,const char * pattern,int type)389 void *OpenGameDirectory(const char *dirname, const char *pattern, int type)
390 {
391 char *localdirname, *globaldirname;
392 DIR *localdir, *globaldir;
393 GameDirectory *gd;
394
395 globaldir = NULL;
396 globaldirname = NULL;
397 if (type != FILETYPE_CONFIG) {
398 globaldirname = FixFilename(dirname, global_dir, 0);
399
400 globaldir = opendir(globaldirname);
401
402 if (globaldir == NULL) {
403 free(globaldirname);
404
405 globaldirname = FixFilename(dirname, global_dir, 1);
406
407 globaldir = opendir(globaldirname);
408
409 if (globaldir == NULL)
410 free(globaldirname);
411 }
412 }
413
414 localdir = NULL;
415 localdirname = NULL;
416 if (type != FILETYPE_PERM) {
417 localdirname = FixFilename(dirname, local_dir, 0);
418
419 localdir = opendir(localdirname);
420
421 if (localdir == NULL) {
422 free(localdirname);
423
424 localdirname = FixFilename(dirname, local_dir, 1);
425
426 localdir = opendir(localdirname);
427
428 if (localdir == NULL)
429 free(localdirname);
430 }
431 }
432
433 if (localdir == NULL && globaldir == NULL)
434 return NULL;
435
436 gd = (GameDirectory *)malloc(sizeof(GameDirectory));
437
438 gd->localdir = localdir;
439 gd->globaldir = globaldir;
440
441 gd->localdirname = localdirname;
442 gd->globaldirname = globaldirname;
443
444 gd->pat = strdup(pattern);
445
446 return gd;
447 }
448
449 /*
450 This struct is public.
451
452 typedef struct GameDirectoryFile
453 {
454 char *filename;
455 int attr;
456 } GameDirectoryFile;
457 */
458
459 /*
460 Returns the next match of pattern with the contents of dir
461
462 f is the current file
463 */
ScanGameDirectory(void * dir)464 GameDirectoryFile *ScanGameDirectory(void *dir)
465 {
466 char *ptr;
467 struct dirent *file;
468 GameDirectory *directory;
469
470 directory = (GameDirectory *)dir;
471
472 if (directory->globaldir) {
473 while ((file = readdir(directory->globaldir)) != NULL) {
474 if (fnmatch(directory->pat, file->d_name, FNM_PATHNAME) == 0) {
475 ptr = FixFilename(file->d_name, directory->globaldirname, 0);
476 directory->tmp.attr = GetFA(ptr);
477 free(ptr);
478
479 directory->tmp.filename = file->d_name;
480
481 return &directory->tmp;
482 }
483 }
484 closedir(directory->globaldir);
485 free(directory->globaldirname);
486
487 directory->globaldir = NULL;
488 directory->globaldirname = NULL;
489 }
490
491 if (directory->localdir) {
492 while ((file = readdir(directory->localdir)) != NULL) {
493 if (fnmatch(directory->pat, file->d_name, FNM_PATHNAME) == 0) {
494 ptr = FixFilename(file->d_name, directory->localdirname, 0);
495 directory->tmp.attr = GetFA(ptr);
496 directory->tmp.timestamp = GetTS(ptr);
497 free(ptr);
498
499 directory->tmp.filename = file->d_name;
500
501 return &directory->tmp;
502 }
503 }
504 closedir(directory->localdir);
505 free(directory->localdirname);
506
507 directory->localdir = NULL;
508 directory->localdirname = NULL;
509 }
510
511 return NULL;
512 }
513
514 /*
515 Close directory
516 */
CloseGameDirectory(void * dir)517 int CloseGameDirectory(void *dir)
518 {
519 GameDirectory *directory = (GameDirectory *)dir;
520
521 if (directory) {
522 free(directory->pat);
523
524 if (directory->localdirname)
525 free(directory->localdirname);
526 if (directory->globaldirname)
527 free(directory->globaldirname);
528 if (directory->localdir)
529 closedir(directory->localdir);
530 if (directory->globaldir)
531 closedir(directory->globaldir);
532
533 return 0;
534 }
535 return -1;
536 }
537
538 /*
539 Game-specific helper function.
540 */
try_game_directory(char * dir,char * file)541 static int try_game_directory(char *dir, char *file)
542 {
543 char tmppath[PATH_MAX];
544
545 strncpy(tmppath, dir, PATH_MAX-32);
546 tmppath[PATH_MAX-32] = 0;
547 strcat(tmppath, file);
548
549 return access(tmppath, R_OK) == 0;
550 }
551
552 /*
553 Game-specific helper function.
554 */
check_game_directory(char * dir)555 static int check_game_directory(char *dir)
556 {
557 if (!dir || !*dir) {
558 return 0;
559 }
560
561 if (!try_game_directory(dir, "/avp_huds")) {
562 return 0;
563 }
564
565 if (!try_game_directory(dir, "/avp_huds/alien.rif")) {
566 return 0;
567 }
568
569 if (!try_game_directory(dir, "/avp_rifs")) {
570 return 0;
571 }
572
573 if (!try_game_directory(dir, "/avp_rifs/temple.rif")) {
574 return 0;
575 }
576
577 if (!try_game_directory(dir, "/fastfile")) {
578 return 0;
579 }
580
581 if (!try_game_directory(dir, "/fastfile/ffinfo.txt")) {
582 return 0;
583 }
584
585 return 1;
586 }
587
588 /*
589 Game-specific initialization
590 */
InitGameDirectories(char * argv0)591 void InitGameDirectories(char *argv0)
592 {
593 extern char *SecondTex_Directory;
594 extern char *SecondSoundDir;
595
596 char tmppath[PATH_MAX];
597 char *homedir, *gamedir, *localdir, *tmp;
598 char *path;
599 size_t len, copylen;
600
601 SecondTex_Directory = "graphics/";
602 SecondSoundDir = "sound/";
603
604 homedir = getenv("HOME");
605 if (homedir == NULL)
606 homedir = ".";
607 localdir = (char *)malloc(strlen(homedir)+10);
608 strcpy(localdir, homedir);
609 strcat(localdir, "/");
610 strcat(localdir, ".avp");
611
612 tmp = NULL;
613
614 /*
615 1. $AVP_DATA overrides all
616 2. executable path from argv[0]
617 3. realpath of executable path from argv[0]
618 4. $PATH
619 5. current directory
620 */
621
622 /* 1. $AVP_DATA */
623 gamedir = getenv("AVP_DATA");
624
625 /* $AVP_DATA overrides all, so no check */
626
627 if (gamedir == NULL) {
628 /* 2. executable path from argv[0] */
629 tmp = strdup(argv0);
630
631 if (tmp == NULL) {
632 /* ... */
633 fprintf(stderr, "InitGameDirectories failure\n");
634 exit(EXIT_FAILURE);
635 }
636
637 gamedir = strrchr(tmp, '/');
638
639 if (gamedir) {
640 *gamedir = 0;
641 gamedir = tmp;
642
643 if (!check_game_directory(gamedir)) {
644 gamedir = NULL;
645 }
646 }
647 }
648
649 if (gamedir == NULL) {
650 /* 3. realpath of executable path from argv[0] */
651
652 assert(tmp != NULL);
653
654 gamedir = realpath(tmp, tmppath);
655
656 if (!check_game_directory(gamedir)) {
657 gamedir = NULL;
658 }
659 }
660
661 if (gamedir == NULL) {
662 /* 4. $PATH */
663 path = getenv("PATH");
664 if (path) {
665 while (*path) {
666 len = strcspn(path, ":");
667
668 copylen = min(len, PATH_MAX-1);
669
670 strncpy(tmppath, path, copylen);
671 tmppath[copylen] = 0;
672
673 if (check_game_directory(tmppath)) {
674 gamedir = tmppath;
675 break;
676 }
677
678 path += len;
679 path += strspn(path, ":");
680 }
681 }
682 }
683
684 if (gamedir == NULL) {
685 /* 5. current directory */
686 gamedir = ".";
687 }
688
689 assert(gamedir != NULL);
690
691 /* last chance sanity check */
692 if (!check_game_directory(gamedir)) {
693 fprintf(stderr, "Unable to find the AvP gamedata.\n");
694 fprintf(stderr, "The directory last examined was: %s\n", gamedir);
695 fprintf(stderr, "Has the game been installed and\n");
696 fprintf(stderr, "are all game files lowercase?\n");
697 exit(EXIT_FAILURE);
698 }
699
700 SetGameDirectories(localdir, gamedir);
701
702 free(localdir);
703 if (tmp) {
704 free(tmp);
705 }
706
707 /* delete some log files */
708 DeleteGameFile("dx_error.log");
709 }
710
711 #ifdef FILES_DRIVER
main(int argc,char * argv[])712 int main(int argc, char *argv[])
713 {
714 FILE *fp;
715 char buf[64];
716 void *dir;
717
718 SetGameDirectories("tmp1", "tmp2");
719
720 fp = OpenGameFile("tester", FILEMODE_WRITEONLY, FILETYPE_CONFIG);
721
722 fputs("test\n", fp);
723
724 CloseGameFile(fp);
725
726 CreateGameDirectory("yaya");
727 CreateGameDirectory("tester2");
728 CreateGameDirectory("tester2/blah");
729
730 fp = OpenGameFile("tester", FILEMODE_READONLY, FILETYPE_OPTIONAL);
731 printf("Read: %s", fgets(buf, 60, fp));
732 CloseGameFile(fp);
733
734 fp = OpenGameFile("tester", FILEMODE_READONLY, FILETYPE_CONFIG);
735 printf("Read: %s", fgets(buf, 60, fp));
736 CloseGameFile(fp);
737
738 dir = OpenGameDirectory(".", "*", FILETYPE_OPTIONAL);
739 if (dir != NULL) {
740 GameDirectoryFile *gd;
741
742 while ((gd = ScanGameDirectory(dir)) != NULL) {
743 printf("Name: %s, Attr: %08X\n", gd->filename, gd->attr);
744 }
745
746 CloseGameDirectory(dir);
747 } else {
748 printf("Could not open the directory...\n");
749 }
750
751 DeleteGameFile("tester");
752
753 return 0;
754 }
755 #endif
756