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