1 /*
2 * quakefs.c -- Hexen II filesystem
3 * $Id: quakefs.c 5787 2017-01-03 22:22:34Z sezero $
4 *
5 * Copyright (C) 1996-1997 Id Software, Inc.
6 * Copyright (C) 2005-2012 O.Sezer <sezero@users.sourceforge.net>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or (at
11 * your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 *
17 * See the GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 */
23
24 #include "quakedef.h"
25 #include "pakfile.h"
26 #include <errno.h>
27 #ifdef PLATFORM_WINDOWS
28 #include <io.h>
29 #endif
30 #include "filenames.h"
31
32 typedef struct
33 {
34 char name[MAX_QPATH];
35 int filepos, filelen;
36 } pakfiles_t;
37
38 typedef struct pack_s
39 {
40 char filename[MAX_OSPATH];
41 FILE *handle;
42 int numfiles;
43 pakfiles_t *files;
44 } pack_t;
45
46 typedef struct searchpath_s
47 {
48 unsigned int path_id; /* identifier assigned to the game directory
49 * Note that <install_dir>/game1 and
50 * <userdir>/game1 have the same id. */
51 char filename[MAX_OSPATH];
52 struct pack_s *pack; /* only one of filename / pack will be used */
53 struct searchpath_s *next;
54 } searchpath_t;
55
56 static searchpath_t *fs_searchpaths;
57 static searchpath_t *fs_base_searchpaths; /* without gamedirs */
58
59 static const char *fs_basedir;
60 static char fs_gamedir[MAX_OSPATH];
61 static char fs_userdir[MAX_OSPATH];
62 char fs_gamedir_nopath[MAX_QPATH];
63
64 unsigned int gameflags;
65
66 cvar_t oem = {"oem", "0", CVAR_ROM};
67 cvar_t registered = {"registered", "0", CVAR_ROM};
68
69 typedef struct
70 {
71 int numfiles;
72 unsigned int crc;
73 long size;
74 const char *dirname;
75 } pakdata_t;
76
77 #define MAX_PAKDATA 5 /* pak0...4 */
78 static pakdata_t pakdata[MAX_PAKDATA] =
79 {
80 { 696, 34289, 22704056, "data1" }, /* pak0.pak, registered, up-to-date, v1.11
81 * MD5: c9675191e75dd25a3b9ed81ee7e05eff */
82 { 523, 2995 , 75601170, "data1" }, /* pak1.pak, registered, up-to-date, v1.11
83 * MD5: c2ac5b0640773eed9ebe1cda2eca2ad0 */
84 { 183, 4807 , 17742721, "data1" }, /* pak2.pak, oem (Matrox m3D bundle) v1.10
85 * MD5: 99e0054861e94f66fc8e0e29416859c9 */
86 { 245, 1478 , 49089114, "portals" }, /* pak3.pak, Portal of Praevus expansion pack
87 * MD5: 77ae298dd0dcd16ab12f4a68067ff2c3 */
88 { 102, 41062, 10780245, "hw" } /* pak4.pak, hexenworld, versions 0.14 - 0.15
89 * MD5: 88109ee385d9723ac5f1015e034a44dd */
90 };
91
92 static pakdata_t demo_pakdata[] =
93 {
94 { 797, 22780, 27750257, "data1" } /* pak0.pak, demo v1.11 from Nov. 1997
95 * MD5: 8e598d82bf53436ed7a0e133aa4b9f09 */
96 };
97
98 static pakdata_t oem0_pakdata[] = /* Continent of Blackmarsh */
99 {
100 { 697, 9787 , 22720659, "data1" } /* pak0.pak, oem (Matrox m3D bundle) v1.10
101 * MD5: 8c9c6118117baca7b9349d477403fcc0 */
102 };
103
104 static pakdata_t old_pakdata[] =
105 {
106 { 697, 53062, 21714275, "data1" }, /* pak0.pak, original cdrom (1.03) version
107 * MD5: b53c9391d16134cb3baddc1085f18683 */
108 { 525, 47762, 76958474, "data1" }, /* pak1.pak, original cdrom (1.03) version
109 * MD5: 9a2010aafb9c0fe71c37d01292030270 */
110 { 701, 20870, 23537707, "data1" }, /* pak0.pak, original demo v0.42 from Aug. 1997
111 * (h2.exe -> console -> version says 1.07!)
112 * MD5: 208643a09193dafbca4b851762479438 */
113 /* !!! FIXME: I don't have the original v1.08 of Continent of Blackmarsh. I only know the file sizes. */
114 { -1, 0, 22719295, "data1" }, /* pak0.pak, original oem (Matrox m3D) v1.08
115 * MD5: ???????????????????????????????? */
116 { -1, 0, 17739969, "data1" }, /* pak2.pak, original oem (Matrox m3D) v1.08
117 * MD5: ???????????????????????????????? */
118 { 98, 25864, 10678369, "hw" }, /* pak4.pak, Hexen2World v0.11 (ugh..)
119 * MD5: c311a30ac8ee1f112019723b4fe42268 */
120 { 40, 48258, 3357888, "hw" }, /* pak4.pak, Hexen2World v0.09 (ugh ugh ugh!)
121 * MD5: 7708da4323f668cf9c71f99315704baa */
122 };
123
124 /* this graphic needs to be in the pak file to use registered features */
125 static const unsigned short pop[] =
126 {
127 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
128 0x0000, 0x0000, 0x6600, 0x0000, 0x0000, 0x0000, 0x6600, 0x0000,
129 0x0000, 0x0066, 0x0000, 0x0000, 0x0000, 0x0000, 0x0067, 0x0000,
130 0x0000, 0x6665, 0x0000, 0x0000, 0x0000, 0x0000, 0x0065, 0x6600,
131 0x0063, 0x6561, 0x0000, 0x0000, 0x0000, 0x0000, 0x0061, 0x6563,
132 0x0064, 0x6561, 0x0000, 0x0000, 0x0000, 0x0000, 0x0061, 0x6564,
133 0x0064, 0x6564, 0x0000, 0x6469, 0x6969, 0x6400, 0x0064, 0x6564,
134 0x0063, 0x6568, 0x6200, 0x0064, 0x6864, 0x0000, 0x6268, 0x6563,
135 0x0000, 0x6567, 0x6963, 0x0064, 0x6764, 0x0063, 0x6967, 0x6500,
136 0x0000, 0x6266, 0x6769, 0x6a68, 0x6768, 0x6a69, 0x6766, 0x6200,
137 0x0000, 0x0062, 0x6566, 0x6666, 0x6666, 0x6666, 0x6562, 0x0000,
138 0x0000, 0x0000, 0x0062, 0x6364, 0x6664, 0x6362, 0x0000, 0x0000,
139 0x0000, 0x0000, 0x0000, 0x0062, 0x6662, 0x0000, 0x0000, 0x0000,
140 0x0000, 0x0000, 0x0000, 0x0061, 0x6661, 0x0000, 0x0000, 0x0000,
141 0x0000, 0x0000, 0x0000, 0x0000, 0x6500, 0x0000, 0x0000, 0x0000,
142 0x0000, 0x0000, 0x0000, 0x0000, 0x6400, 0x0000, 0x0000, 0x0000
143 };
144
145 static char *FSERR_MakePath_BUF (const char *caller, int linenum, int base,
146 char *buf, size_t siz, const char *path);
147 static char *FSERR_MakePath_VABUF (const char *caller, int linenum, int base,
148 char *buf, size_t siz, const char *format, ...)
149 FUNC_PRINTF(6,7);
150 static char *do_MakePath_VA (int base, int *error, char *buf, size_t siz,
151 const char *format, va_list args)
152 FUNC_PRINTF(5,0);
153
154 /*
155 All of Quake's data access is through a hierchal file system, but the contents
156 of the file system can be transparently merged from several sources.
157
158 The "base directory" is the path to the directory holding the exe and all game
159 directories. The sys_* files pass this to host_init in quakeparms_t->basedir.
160 This can be overridden with the "-basedir" command line parm to allow code
161 debugging in a different directory. The base directory is only used during
162 filesystem initialization.
163
164 The "game directory" is the first tree on the search path and directory that
165 all generated files (savegames, screenshots, demos, config files) will be saved
166 to. This can be overridden with the "-game" command line parameter. The game
167 directory can never be changed while quake is executing. This is a precacution
168 against having a malicious server instruct clients to write files over areas
169 they shouldn't.
170 */
171
172
check_known_paks(int paknum,int numfiles,unsigned short crc)173 static unsigned int check_known_paks (int paknum, int numfiles, unsigned short crc)
174 {
175 if (paknum >= MAX_PAKDATA)
176 return GAME_MODIFIED;
177
178 if (strcmp(fs_gamedir_nopath, pakdata[paknum].dirname) != 0)
179 return GAME_MODIFIED; /* Raven didn't ship like that */
180
181 if (numfiles != pakdata[paknum].numfiles)
182 {
183 switch (paknum)
184 {
185 case 0: /* demo ?? */
186 if (numfiles == demo_pakdata[0].numfiles &&
187 crc == demo_pakdata[0].crc)
188 return GAME_DEMO;
189 /* oem ?? */
190 if (numfiles == oem0_pakdata[0].numfiles &&
191 crc == oem0_pakdata[0].crc)
192 return GAME_OEM0;
193 /* old version of demo ?? */
194 if (numfiles == old_pakdata[2].numfiles &&
195 crc == old_pakdata[2].crc)
196 return GAME_OLD_DEMO;
197 /* old cdrom version ?? */
198 if (numfiles == old_pakdata[0].numfiles &&
199 crc == old_pakdata[0].crc)
200 return GAME_OLD_CDROM0;
201 /* old oem version ?? */
202 if (numfiles == old_pakdata[3].numfiles &&
203 crc == old_pakdata[3].crc)
204 return GAME_OLD_OEM0;
205 /* not original: */
206 return GAME_MODIFIED;
207 case 1: /* old cdrom version ?? */
208 if (numfiles == old_pakdata[1].numfiles &&
209 crc == old_pakdata[1].crc)
210 return GAME_OLD_CDROM1;
211 /* not original: */
212 return GAME_MODIFIED;
213 case 2: /* old oem version ?? */
214 if (numfiles == old_pakdata[4].numfiles &&
215 crc == old_pakdata[4].crc)
216 return GAME_OLD_OEM2;
217 /* not original: */
218 return GAME_MODIFIED;
219 case 4: /* old HW version ?? */
220 if (numfiles == old_pakdata[5].numfiles &&
221 crc == old_pakdata[5].crc)
222 return GAME_HEXENWORLD;
223 /* not original: */
224 return GAME_MODIFIED;
225 default:/* not original */
226 return GAME_MODIFIED;
227 }
228 }
229
230 if (crc != pakdata[paknum].crc)
231 return GAME_MODIFIED; /* not original */
232
233 /* both crc and numfiles are good, we are still original */
234 switch (paknum)
235 {
236 case 0: /* pak0 of full version 1.11 */
237 return GAME_REGISTERED0;
238 case 1: /* pak1 of full version 1.11 */
239 return GAME_REGISTERED1;
240 case 2: /* bundle version */
241 return GAME_OEM2;
242 case 3: /* mission pack */
243 return GAME_PORTALS;
244 case 4: /* hexenworld */
245 return GAME_HEXENWORLD;
246 }
247
248 return GAME_MODIFIED; /* we shouldn't reach here */
249 }
250
251 /*
252 =================
253 FS_LoadPackFile
254
255 Takes a path to a pak file. Loads the header and directory.
256 =================
257 */
FS_LoadPackFile(const char * packfile,int paknum,qboolean base_fs)258 static pack_t *FS_LoadPackFile (const char *packfile, int paknum, qboolean base_fs)
259 {
260 dpackheader_t header;
261 int i, numpackfiles;
262 pakfiles_t *newfiles;
263 pack_t *pack;
264 FILE *packhandle;
265 dpackfile_t info[MAX_FILES_IN_PACK];
266 unsigned short crc;
267
268 packhandle = fopen (packfile, "rb");
269 if (!packhandle)
270 return NULL;
271
272 fread (&header, 1, sizeof(header), packhandle);
273 if (header.id[0] != 'P' || header.id[1] != 'A' ||
274 header.id[2] != 'C' || header.id[3] != 'K')
275 {
276 Sys_Printf ("WARNING: %s is not a packfile, ignored\n", packfile);
277 goto pak_error;
278 }
279
280 header.dirofs = LittleLong (header.dirofs);
281 header.dirlen = LittleLong (header.dirlen);
282
283 numpackfiles = header.dirlen / sizeof(dpackfile_t);
284
285 if (header.dirlen < 0 || header.dirofs < 0)
286 {
287 Sys_Error ("Invalid packfile %s (dirlen: %i, dirofs: %i)",
288 packfile, header.dirlen, header.dirofs);
289 }
290 if (!numpackfiles)
291 {
292 Sys_Printf ("WARNING: %s has no files, ignored\n", packfile);
293 goto pak_error;
294 }
295 if (numpackfiles > MAX_FILES_IN_PACK)
296 {
297 Sys_Printf ("WARNING: %s has %i files (max. allowed is %i), ignored\n",
298 packfile, numpackfiles, MAX_FILES_IN_PACK);
299 goto pak_error;
300 }
301
302 newfiles = (pakfiles_t *) Z_Malloc (numpackfiles * sizeof(pakfiles_t), Z_MAINZONE);
303
304 fseek (packhandle, header.dirofs, SEEK_SET);
305 fread (info, 1, header.dirlen, packhandle);
306
307 /* crc the directory */
308 CRC_Init (&crc);
309 for (i = 0; i < header.dirlen; i++)
310 CRC_ProcessByte (&crc, ((byte *)info)[i]);
311
312 /* check for modifications */
313 if (base_fs)
314 gameflags |= check_known_paks (paknum, numpackfiles, crc);
315 else gameflags |= GAME_MODIFIED;
316
317 /* parse the directory */
318 for (i = 0; i < numpackfiles; i++)
319 {
320 qerr_strlcpy(__thisfunc__, __LINE__, newfiles[i].name, info[i].name, MAX_QPATH);
321 newfiles[i].filepos = LittleLong(info[i].filepos);
322 newfiles[i].filelen = LittleLong(info[i].filelen);
323 }
324
325 pack = (pack_t *) Z_Malloc (sizeof(pack_t), Z_MAINZONE);
326 qerr_strlcpy(__thisfunc__, __LINE__, pack->filename, packfile, MAX_OSPATH);
327 pack->handle = packhandle;
328 pack->numfiles = numpackfiles;
329 pack->files = newfiles;
330
331 Sys_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);
332 return pack;
333 pak_error:
334 fclose (packhandle);
335 return NULL;
336 }
337
338
339 /*
340 ================
341 FS_AddGameDirectory
342
343 Sets fs_gamedir, fs_userdir and fs_gamedir_nopath. adds the directory
344 to the head of the path, then loads and adds pak1.pak pak2.pak ...
345 ================
346 */
FS_AddGameDirectory(const char * dir,qboolean base_fs)347 static void FS_AddGameDirectory (const char *dir, qboolean base_fs)
348 {
349 unsigned int path_id;
350 searchpath_t *search;
351 pack_t *pak;
352 char pakfile[MAX_OSPATH];
353 qboolean do_userdir = false;
354 int i;
355
356 qerr_strlcpy(__thisfunc__, __LINE__, fs_gamedir_nopath, dir,
357 sizeof(fs_gamedir_nopath));
358 FSERR_MakePath_BUF (__thisfunc__, __LINE__, FS_BASEDIR,
359 fs_gamedir, sizeof(fs_gamedir), dir);
360 FSERR_MakePath_BUF (__thisfunc__, __LINE__, FS_USERBASE,
361 fs_userdir, sizeof(fs_userdir), dir);
362
363 /* assign a path_id to this game directory */
364 if (fs_searchpaths)
365 path_id = fs_searchpaths->path_id << 1;
366 else path_id = 1U;
367
368 #if DO_USERDIRS
369 add_pakfile:
370 #endif
371 /* add any pak files in the format pak0.pak pak1.pak, ...
372 * unlike Quake, Hexen II can't stop at first unavailable
373 * pak: the mission pack has only pak3, hw has only pak4.
374 */
375 for (i = 0; i < 10; i++)
376 {
377 FSERR_MakePath_VABUF (__thisfunc__, __LINE__,
378 (do_userdir) ? FS_USERDIR : FS_GAMEDIR,
379 pakfile, sizeof(pakfile), "pak%i.pak", i);
380 pak = FS_LoadPackFile (pakfile, i, base_fs);
381 if (!pak) continue;
382 search = (searchpath_t *) Z_Malloc (sizeof(searchpath_t), Z_MAINZONE);
383 search->path_id = path_id;
384 search->pack = pak;
385 search->next = fs_searchpaths;
386 fs_searchpaths = search;
387 }
388
389 /* add the directory itself to the search path. unlike Quake,
390 * Hexen II does this ~after~ adding the pakfiles in this dir,
391 * so that the dir itself will be placed above the pakfiles in
392 * the search order which, in turn, will allow override files.
393 */
394 search = (searchpath_t *) Z_Malloc (sizeof(searchpath_t), Z_MAINZONE);
395 if (do_userdir)
396 qerr_strlcpy(__thisfunc__, __LINE__, search->filename, fs_userdir, MAX_OSPATH);
397 else qerr_strlcpy(__thisfunc__, __LINE__, search->filename, fs_gamedir, MAX_OSPATH);
398 search->path_id = path_id;
399 search->next = fs_searchpaths;
400 fs_searchpaths = search;
401
402 if (do_userdir)
403 return;
404 do_userdir = true;
405
406 #if DO_USERDIRS
407 /* add user's directory to the search path and
408 * add any pak files in the user's directory.
409 */
410 if (strcmp(fs_gamedir, fs_userdir))
411 {
412 Sys_mkdir (fs_userdir, true);
413 goto add_pakfile;
414 }
415 #endif
416 }
417
418 /*
419 ================
420 FS_Gamedir
421
422 Sets the gamedir and path to a different directory.
423
424 Hexen II uses this for setting the game directory from a -game
425 command line argument.
426
427 HexenWorld uses this to set the gamedir on both server and client
428 sides while the game is running: The client calls this upon every
429 map change from within CL_ParseServerData(), and the Server calls
430 this upon a gamedir command from within SV_Gamedir_f().
431 ================
432 */
433 #ifdef H2W
set_hw_dir(void)434 static inline void set_hw_dir (void) { /* helper for FS_Gamedir () */
435 qerr_strlcpy(__thisfunc__, __LINE__, fs_gamedir_nopath, "hw",
436 sizeof(fs_gamedir_nopath));
437 FSERR_MakePath_BUF (__thisfunc__, __LINE__, FS_BASEDIR,
438 fs_gamedir, sizeof(fs_gamedir), "hw");
439 FSERR_MakePath_BUF (__thisfunc__, __LINE__, FS_USERBASE,
440 fs_userdir, sizeof(fs_userdir), "hw");
441 #ifdef SERVERONLY
442 /* change the *gamedir serverinfo properly */
443 Info_SetValueForStarKey (svs.info, "*gamedir", "hw", MAX_SERVERINFO_STRING);
444 #endif
445 }
446 #endif
447
FS_Gamedir(const char * dir)448 void FS_Gamedir (const char *dir)
449 {
450 searchpath_t *next;
451
452 if (!*dir || !strcmp(dir, ".") || strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\") || strstr(dir, ":"))
453 {
454 if (!host_initialized)
455 Sys_Error ("gamedir should be a single directory name, not a path\n");
456 else {
457 Con_Printf("gamedir should be a single directory name, not a path\n");
458 return;
459 }
460 }
461
462 if (!q_strcasecmp(fs_gamedir_nopath, dir))
463 return; /* still the same */
464
465 /* free up any current game dir info: our top searchpath dir will be hw
466 * and any gamedirs set before by this very procedure will be removed.
467 * since hexen2 doesn't use this during game execution there will be no
468 * changes for it: it has portals or data1 at the top.
469 */
470 while (fs_searchpaths != fs_base_searchpaths)
471 {
472 if (fs_searchpaths->pack)
473 {
474 fclose (fs_searchpaths->pack->handle);
475 Z_Free (fs_searchpaths->pack->files);
476 Z_Free (fs_searchpaths->pack);
477 }
478 next = fs_searchpaths->next;
479 Z_Free (fs_searchpaths);
480 fs_searchpaths = next;
481 }
482
483 /* flush all data, so it will be forced to reload */
484 #if !defined(SERVERONLY)
485 Cache_Flush ();
486 #endif /* SERVERONLY */
487
488 /* check for reserved gamedirs */
489 if (!q_strcasecmp(dir, "hw"))
490 {
491 #if defined(H2W)
492 /* that we reached here means the hw server decided to abandon
493 * whatever the previous mod it was running and went back to
494 * pure hw. weird.. do as he wishes anyway and adjust our variables. */
495 set_hw_dir ();
496 #else /* hexen2 case: */
497 /* hw is reserved for hexenworld only. hexen2 shouldn't use it */
498 Con_Printf ("WARNING: Gamedir not set to hw :\n"
499 "It is reserved for HexenWorld.\n");
500 #endif /* H2W */
501 return;
502 }
503
504 if (!q_strcasecmp(dir, "portals"))
505 {
506 /* no hw server is supposed to set gamedir to portals
507 * and hw must be above portals in hierarchy. this is
508 * actually a hypothetical case.
509 * as for hexen2, it cannot reach here. */
510 #ifdef H2W
511 set_hw_dir ();
512 #endif
513 return;
514 }
515
516 if (!q_strcasecmp(dir, "data1"))
517 {
518 /* another hypothetical case: no hw mod is supposed to
519 * do this and hw must stay above data1 in hierarchy.
520 * as for hexen2, it can only reach here by a silly
521 * command line argument like -game data1, ignore it. */
522 #ifdef H2W
523 set_hw_dir ();
524 #endif
525 return;
526 }
527
528 /* a new gamedir: let's set it here. */
529 FS_AddGameDirectory(dir, false);
530 #if defined(H2W) && defined(SERVERONLY)
531 /* change the *gamedir serverinfo properly */
532 Info_SetValueForStarKey (svs.info, "*gamedir", dir, MAX_SERVERINFO_STRING);
533 #endif
534 }
535
536
537 /*
538 ==============================================================================
539
540 FILE I/O within QFS
541
542 ==============================================================================
543 */
544
545 size_t fs_filesize; /* size of the last file opened through QFS */
546 int file_from_pak; /* ZOID: global indicating that file came from a pak */
547
548
549 /*
550 ===========
551 FS_CopyFile
552
553 Copies the FROMPATH file as TOPATH file, creating any dirs needed.
554 Used for saving the game. Returns 0 on success, non-zero on error.
555 ===========
556 */
FS_CopyFile(const char * frompath,const char * topath)557 int FS_CopyFile (const char *frompath, const char *topath)
558 {
559 char *tmp;
560 int err;
561
562 if (!frompath || !topath)
563 {
564 Con_Printf ("%s: null input\n", __thisfunc__);
565 return 1;
566 }
567 /* create directories up to the dest file */
568 tmp = Z_Strdup (topath);
569 err = FS_CreatePath(tmp);
570 Z_Free (tmp);
571 if (err != 0)
572 {
573 Con_Printf ("%s: unable to create directory\n", __thisfunc__);
574 return err;
575 }
576
577 err = Sys_CopyFile (frompath, topath);
578 return err;
579 }
580
581 #define COPY_READ_BUFSIZE 8192 /* BUFSIZ */
FS_WriteFileFromHandle(FILE * fromfile,const char * topath,size_t size)582 int FS_WriteFileFromHandle (FILE *fromfile, const char *topath, size_t size)
583 {
584 char buf[COPY_READ_BUFSIZE];
585 FILE *out;
586 /* off_t remaining, count;*/
587 size_t remaining, count;
588 char *tmp;
589 int err;
590
591 if (!fromfile || !topath)
592 {
593 Con_Printf ("%s: null input\n", __thisfunc__);
594 return 1;
595 }
596
597 /* create directories up to the dest file */
598 tmp = Z_Strdup (topath);
599 err = FS_CreatePath(tmp);
600 Z_Free (tmp);
601 if (err != 0)
602 {
603 Con_Printf ("%s: unable to create directory\n", __thisfunc__);
604 return err;
605 }
606
607 out = fopen (topath, "wb");
608 if (!out)
609 {
610 Con_Printf ("%s: unable to create %s\n", topath, __thisfunc__);
611 return 1;
612 }
613
614 remaining = size;
615 while (remaining)
616 {
617 if (remaining < sizeof(buf))
618 count = remaining;
619 else count = sizeof(buf);
620
621 if (fread(buf, 1, count, fromfile) != count)
622 break;
623 if (fwrite(buf, 1, count, out) != count)
624 break;
625
626 remaining -= count;
627 }
628
629 fclose (out);
630
631 return (remaining == 0)? 0 : 1;
632 }
633
634 /*
635 ============
636 FS_WriteFile
637
638 The filename will be prefixed by the current game directory
639 Returns 0 on success, 1 on error.
640 ============
641 */
FS_WriteFile(const char * filename,const void * data,size_t len)642 int FS_WriteFile (const char *filename, const void *data, size_t len)
643 {
644 FILE *f;
645 char name[MAX_OSPATH];
646 size_t size;
647 int err;
648
649 FS_MakePath_BUF (FS_USERDIR, &err, name, sizeof(name), filename);
650 if (err)
651 {
652 Host_Error("%s: %d: string buffer overflow!", __thisfunc__, __LINE__);
653 return 1;
654 }
655
656 f = fopen (name, "wb");
657 if (!f)
658 {
659 Con_Printf ("Error opening %s\n", filename);
660 return 1;
661 }
662
663 Sys_Printf ("%s: %s\n", __thisfunc__, name);
664 size = fwrite (data, 1, len, f);
665 fclose (f);
666 if (size != len)
667 {
668 Con_Printf ("Error in writing %s\n", filename);
669 return 1;
670 }
671 return 0;
672 }
673
674
675 /*
676 ============
677 FS_CreatePath
678
679 Creates directory under user's path, making parent directories
680 as needed. The path must either be a path to a file, or, if the
681 full path is meant to be created, it must have the trailing path
682 seperator. Returns 0 on success, non-zero on error.
683 ============
684 */
FS_CreatePath(char * path)685 int FS_CreatePath (char *path)
686 {
687 char *ofs, c;
688 int err = 0;
689 size_t offset;
690
691 if (!path || !*path)
692 {
693 Con_Printf ("%s: no path!\n", __thisfunc__);
694 return 1;
695 }
696
697 if (strstr(path, ".."))
698 {
699 Con_Printf ("Relative pathnames are not allowed.\n");
700 return 1;
701 }
702
703 offset = strlen(host_parms->userdir);
704 if (offset && strstr(path, host_parms->userdir) != path)
705 {
706 Sys_Error ("Attempted to create a directory out of user's path");
707 return 1;
708 }
709
710 ofs = path + offset;
711 if (*ofs == '\0') /* not necessarily an error. */
712 return 0;
713 /* check for the path separator after the userdir. */
714 if (IS_DIR_SEPARATOR(*ofs))
715 ofs++;
716 else if (offset)
717 {
718 /* if the userdir itself has no trailing DIRSEP
719 * either, then it is a bad path: */
720 if (!IS_DIR_SEPARATOR(host_parms->userdir[offset - 1]))
721 {
722 Con_Printf ("%s: bad path\n", __thisfunc__);
723 return 1;
724 }
725 }
726
727 for ( ; *ofs; ofs++)
728 {
729 c = *ofs;
730 if (IS_DIR_SEPARATOR(c))
731 { /* create the directory */
732 *ofs = 0;
733 err = Sys_mkdir (path, false);
734 *ofs = c;
735 if (err)
736 break;
737 }
738 }
739
740 return err;
741 }
742
743
744 /*
745 ===========
746 FS_OpenFile
747
748 Finds the file in the search path, returns fs_filesize.
749 ===========
750 */
FS_OpenFile(const char * filename,FILE ** file,unsigned int * path_id)751 size_t FS_OpenFile (const char *filename, FILE **file, unsigned int *path_id)
752 {
753 searchpath_t *search;
754 pack_t *pak;
755 char ospath[MAX_OSPATH];
756 int i;
757
758 file_from_pak = 0;
759
760 /* search through the path, one element at a time */
761 for (search = fs_searchpaths ; search ; search = search->next)
762 {
763 if (search->pack) /* look through all the pak file elements */
764 {
765 pak = search->pack;
766 for (i = 0; i < pak->numfiles; i++)
767 {
768 if (strcmp(pak->files[i].name, filename) != 0)
769 continue;
770 /* found it! */
771 fs_filesize = (size_t) pak->files[i].filelen;
772 file_from_pak = 1;
773 if (path_id)
774 *path_id = search->path_id;
775 if (!file) /* for FS_FileExists() */
776 return fs_filesize;
777 /* open a new file on the pakfile */
778 *file = fopen (pak->filename, "rb");
779 if (!*file)
780 Sys_Error ("Couldn't reopen %s", pak->filename);
781 fseek (*file, pak->files[i].filepos, SEEK_SET);
782 return fs_filesize;
783 }
784 }
785 else /* check a file in the directory tree */
786 {
787 q_snprintf (ospath, sizeof(ospath), "%s/%s",search->filename, filename);
788 fs_filesize = (size_t) Sys_filesize (ospath);
789 if (fs_filesize == (size_t)-1)
790 continue;
791 if (path_id)
792 *path_id = search->path_id;
793 if (!file) /* for FS_FileExists() */
794 return fs_filesize;
795 *file = fopen (ospath, "rb");
796 if (!*file)
797 Sys_Error ("Couldn't reopen %s", ospath);
798 return fs_filesize;
799 }
800 }
801
802 Sys_DPrintf ("%s: can't find %s\n", __thisfunc__, filename);
803
804 if (file) *file = NULL;
805 fs_filesize = (size_t)-1;
806 return fs_filesize;
807 }
808
809 /*
810 ===========
811 FS_FileExists
812
813 Returns whether the file is found in the hexen2 filesystem.
814 ===========
815 */
FS_FileExists(const char * filename,unsigned int * path_id)816 qboolean FS_FileExists (const char *filename, unsigned int *path_id)
817 {
818 size_t ret = FS_OpenFile (filename, NULL, path_id);
819 return (ret == (size_t)-1) ? false : true;
820 }
821
822 /*
823 ============
824 FS_FileInGamedir
825
826 Reports the existance of a file with read permissions in
827 fs_gamedir or fs_userdir. *NOT* for files in pakfiles!
828 ============
829 */
FS_FileInGamedir(const char * filename)830 qboolean FS_FileInGamedir (const char *filename)
831 {
832 int ret;
833
834 ret = Sys_FileType(FS_MakePath(FS_USERDIR, NULL, filename));
835 if (ret & FS_ENT_FILE)
836 return true;
837 ret = Sys_FileType(FS_MakePath(FS_GAMEDIR, NULL, filename));
838 if (ret & FS_ENT_FILE)
839 return true;
840
841 return false;
842 }
843
844 /*
845 ============
846 FS_LoadFile
847
848 Filename are reletive to the quake directory.
849 Allways appends a 0 byte to the loaded data.
850 ============
851 */
852 #define LOADFILE_ZONE 0
853 #define LOADFILE_HUNK 1
854 #define LOADFILE_TEMPHUNK 2
855 #define LOADFILE_CACHE 3
856 #define LOADFILE_STACK 4
857 #define LOADFILE_MALLOC 5
858
859 static byte *loadbuf;
860 #if !defined(SERVERONLY)
861 static cache_user_t *loadcache;
862 #endif /* SERVERONLY */
863 static size_t loadsize;
864 static int zone_num;
865
866 #if defined (SERVERONLY)
867 #define Draw_BeginDisc()
868 #define Draw_EndDisc()
869 #endif /* SERVERONLY */
870
FS_LoadFile(const char * path,int usehunk,unsigned int * path_id)871 static byte *FS_LoadFile (const char *path, int usehunk, unsigned int *path_id)
872 {
873 FILE *h;
874 byte *buf;
875 char base[32];
876 size_t len;
877
878 /* look for it in the filesystem or pack files */
879 len = FS_OpenFile (path, &h, path_id);
880 if (!h)
881 return NULL;
882
883 /* extract the file's base name for hunk tag */
884 COM_FileBase (path, base, sizeof(base));
885 buf = NULL; /* quiet compiler warning */
886
887 switch (usehunk)
888 {
889 case LOADFILE_HUNK:
890 buf = (byte *) Hunk_AllocName (len+1, base);
891 break;
892 case LOADFILE_TEMPHUNK:
893 buf = (byte *) Hunk_TempAlloc (len+1);
894 break;
895 case LOADFILE_ZONE:
896 buf = (byte *) Z_Malloc (len+1, zone_num);
897 break;
898 #if !defined(SERVERONLY)
899 case LOADFILE_CACHE:
900 buf = (byte *) Cache_Alloc (loadcache, len+1, base);
901 break;
902 #endif /* SERVERONLY */
903 case LOADFILE_STACK:
904 if (len < loadsize)
905 buf = loadbuf;
906 else
907 buf = (byte *) Hunk_TempAlloc (len+1);
908 break;
909 case LOADFILE_MALLOC:
910 buf = (byte *) malloc (len+1);
911 break;
912 default:
913 Sys_Error ("%s: bad usehunk", __thisfunc__);
914 }
915
916 if (!buf)
917 Sys_Error ("%s: not enough space for %s", __thisfunc__, path);
918
919 ((byte *)buf)[len] = 0;
920
921 Draw_BeginDisc ();
922 fread (buf, 1, len, h);
923 fclose (h);
924 Draw_EndDisc ();
925
926 return buf;
927 }
928
FS_LoadHunkFile(const char * path,unsigned int * path_id)929 byte *FS_LoadHunkFile (const char *path, unsigned int *path_id)
930 {
931 return FS_LoadFile (path, LOADFILE_HUNK, path_id);
932 }
933
FS_LoadZoneFile(const char * path,int zone_id,unsigned int * path_id)934 byte *FS_LoadZoneFile (const char *path, int zone_id, unsigned int *path_id)
935 {
936 zone_num = zone_id;
937 return FS_LoadFile (path, LOADFILE_ZONE, path_id);
938 }
939
FS_LoadTempFile(const char * path,unsigned int * path_id)940 byte *FS_LoadTempFile (const char *path, unsigned int *path_id)
941 {
942 return FS_LoadFile (path, LOADFILE_TEMPHUNK, path_id);
943 }
944
945 #if !defined(SERVERONLY)
FS_LoadCacheFile(const char * path,struct cache_user_s * cu,unsigned int * path_id)946 void FS_LoadCacheFile (const char *path, struct cache_user_s *cu, unsigned int *path_id)
947 {
948 loadcache = cu;
949 FS_LoadFile (path, LOADFILE_CACHE, path_id);
950 }
951 #endif /* SERVERONLY */
952
953 /* uses temp hunk if larger than bufsize */
FS_LoadStackFile(const char * path,void * buffer,size_t bufsize,unsigned int * path_id)954 byte *FS_LoadStackFile (const char *path, void *buffer, size_t bufsize, unsigned int *path_id)
955 {
956 byte *buf;
957
958 loadbuf = (byte *)buffer;
959 loadsize = bufsize;
960 buf = FS_LoadFile (path, LOADFILE_STACK, path_id);
961
962 return buf;
963 }
964
965 /* returns malloc'd memory */
FS_LoadMallocFile(const char * path,unsigned int * path_id)966 byte *FS_LoadMallocFile (const char *path, unsigned int *path_id)
967 {
968 return FS_LoadFile (path, LOADFILE_MALLOC, path_id);
969 }
970
971
972 /*
973 ==============================================================================
974
975 MISC CONSOLE COMMANDS
976
977 ==============================================================================
978 */
979
980 /*
981 ============
982 FS_Path_f
983 Prints the search path to the console
984 ============
985 */
FS_Path_f(void)986 static void FS_Path_f (void)
987 {
988 searchpath_t *s;
989
990 Con_Printf ("Current search path:\n");
991 for (s = fs_searchpaths ; s ; s = s->next)
992 {
993 if (s == fs_base_searchpaths)
994 Con_Printf ("----------\n");
995 if (s->pack)
996 Con_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
997 else
998 Con_Printf ("%s\n", s->filename);
999 }
1000 }
1001
1002 /*
1003 ===========
1004 processMapname: Callback for FS_Maplist_f.
1005 Returns 0 if a name is skipped, the current
1006 number of names added to the list if the name
1007 is added, or -1 upon failures.
1008 ===========
1009 */
1010 #if !defined(SERVERONLY) /* dedicated servers dont need this command */
1011
1012 #define MAX_NUMMAPS 256 /* max number of maps to list */
1013 static int map_count = 0;
1014 static char *maplist[MAX_NUMMAPS];
1015
processMapname(const char * mapname,const char * partial,size_t len_partial)1016 static int processMapname (const char *mapname, const char *partial, size_t len_partial)
1017 {
1018 char cur_name[MAX_QPATH];
1019 int j, len;
1020
1021 if (map_count >= MAX_NUMMAPS)
1022 {
1023 Con_Printf ("WARNING: reached maximum number of maps to list\n");
1024 return -1;
1025 }
1026
1027 if (len_partial)
1028 {
1029 if (q_strncasecmp(partial, mapname, len_partial) != 0)
1030 return 0; /* doesn't match the prefix. skip. */
1031 }
1032
1033 len = q_strlcpy (cur_name, mapname, sizeof(cur_name)) - 4; /* -4 to kill ".bsp" */
1034 if (len <= 0)
1035 return 0;
1036 if (q_strcasecmp(&cur_name[len], ".bsp") != 0)
1037 return 0;
1038
1039 cur_name[len] = 0;
1040
1041 for (j = 0; j < map_count; j++)
1042 {
1043 if (! q_strcasecmp(maplist[j], cur_name))
1044 return 0; /* duplicated name. skip. */
1045 }
1046
1047 /* add to the maplist */
1048 maplist[map_count] = Z_Strdup (cur_name);
1049 return (++map_count);
1050 }
1051
1052 /*
1053 ===========
1054 FS_Maplist_f
1055 Prints map filenames to the console
1056 ===========
1057 */
FS_Maplist_f(void)1058 static void FS_Maplist_f (void)
1059 {
1060 searchpath_t *search;
1061 const char *prefix;
1062 size_t preLen;
1063
1064 if (Cmd_Argc() > 1)
1065 {
1066 prefix = Cmd_Argv(1);
1067 preLen = strlen(prefix);
1068 }
1069 else
1070 {
1071 preLen = 0;
1072 prefix = NULL;
1073 }
1074
1075 for (search = fs_searchpaths; search; search = search->next)
1076 {
1077 if (search->pack)
1078 {
1079 int i = 0;
1080 for (; i < search->pack->numfiles; ++i)
1081 {
1082 if (strncmp("maps/", search->pack->files[i].name, 5) != 0)
1083 continue;
1084 if (processMapname(search->pack->files[i].name + 5, prefix, preLen) < 0)
1085 goto done;
1086 }
1087 }
1088 else
1089 {
1090 const char *findname = Sys_FindFirstFile(va("%s/maps",search->filename), "*.bsp");
1091 while (findname)
1092 {
1093 if (processMapname(findname, prefix, preLen) < 0)
1094 {
1095 Sys_FindClose ();
1096 goto done;
1097 }
1098 findname = Sys_FindNextFile ();
1099 }
1100 Sys_FindClose ();
1101 }
1102 }
1103
1104 done:
1105 if (!map_count)
1106 {
1107 Con_Printf ("No maps found.\n\n");
1108 return;
1109 }
1110
1111 Con_Printf ("Found %d maps:\n\n", map_count);
1112 /* sort the list */
1113 if (map_count > 1)
1114 qsort (maplist, map_count, sizeof(char *), COM_StrCompare);
1115 Con_ShowList (map_count, (const char**)maplist);
1116 Con_Printf ("\n");
1117
1118 /* free the memory and zero map_count */
1119 while (map_count)
1120 Z_Free (maplist[--map_count]);
1121 }
1122 #endif /* SERVERONLY */
1123
1124
1125 /*
1126 ==============================================================================
1127
1128 INIT
1129
1130 ==============================================================================
1131 */
1132
1133 /*
1134 ================
1135 CheckRegistered
1136
1137 Looks for the pop.txt file and verifies it.
1138 Sets the registered flag.
1139 ================
1140 */
CheckRegistered(void)1141 static int CheckRegistered (void)
1142 {
1143 FILE *h;
1144 unsigned short check[128];
1145 int i;
1146
1147 FS_OpenFile("gfx/pop.lmp", &h, NULL);
1148 if (!h)
1149 return -1;
1150
1151 fread (check, 1, sizeof(check), h);
1152 fclose (h);
1153
1154 for (i = 0; i < 128; i++)
1155 {
1156 if (pop[i] != (unsigned short)BigShort(check[i]))
1157 {
1158 Sys_Printf ("Corrupted data file\n");
1159 return -1;
1160 }
1161 }
1162
1163 return 0;
1164 }
1165
1166 /*
1167 ================
1168 FS_Init
1169 ================
1170 */
FS_Init(void)1171 void FS_Init (void)
1172 {
1173 qboolean check_portals = false;
1174 int i;
1175
1176 Cvar_RegisterVariable (&oem);
1177 Cvar_RegisterVariable (®istered);
1178
1179 Cmd_AddCommand ("path", FS_Path_f);
1180 #if !defined(SERVERONLY)
1181 Cmd_AddCommand ("maplist", FS_Maplist_f);
1182 #endif /* SERVERONLY */
1183
1184 /* -basedir <path> overrides the system supplied base directory */
1185 i = COM_CheckParm ("-basedir");
1186 if (i && i < com_argc-1)
1187 {
1188 fs_basedir = com_argv[i+1];
1189 if (!*fs_basedir) Sys_Error("Bad argument to -basedir");
1190 #if !DO_USERDIRS
1191 host_parms->userdir = com_argv[i+1];
1192 #endif
1193 Sys_Printf ("%s: basedir changed to: %s\n", __thisfunc__, fs_basedir);
1194 }
1195 else
1196 {
1197 fs_basedir = host_parms->basedir;
1198 }
1199
1200 /* step 1: start up with data1 by default */
1201 FS_AddGameDirectory ("data1", true);
1202
1203 if (gameflags & GAME_REGISTERED0 && gameflags & GAME_REGISTERED1)
1204 gameflags |= GAME_REGISTERED;
1205 if (gameflags & GAME_OEM0 && gameflags & GAME_OEM2)
1206 gameflags |= GAME_OEM;
1207 if (gameflags & GAME_OLD_CDROM0 && gameflags & GAME_OLD_CDROM1)
1208 gameflags |= GAME_REGISTERED_OLD;
1209 if (gameflags & GAME_OLD_OEM0 && gameflags & GAME_OLD_OEM2)
1210 gameflags |= GAME_OLD_OEM;
1211 /* check for bad installations (mix'n'match data): */
1212 if ((gameflags & GAME_REGISTERED0 && gameflags & GAME_OLD_CDROM1) ||
1213 (gameflags & GAME_REGISTERED1 && gameflags & GAME_OLD_CDROM0) ||
1214 (gameflags & (GAME_OEM2|GAME_OLD_OEM2) && gameflags & (GAME_REGISTERED|GAME_REGISTERED_OLD|GAME_DEMO|GAME_OLD_DEMO)) ||
1215 (gameflags & (GAME_REGISTERED1|GAME_OLD_CDROM1) &&
1216 gameflags & (GAME_DEMO|GAME_OLD_DEMO|GAME_OEM0|GAME_OLD_OEM0|GAME_OEM2|GAME_OLD_OEM2)))
1217 {
1218 Sys_Error ("Bad Hexen II installation: mixed data from incompatible versions");
1219 }
1220 #if !ENABLE_OLD_DEMO
1221 if (gameflags & GAME_OLD_DEMO)
1222 Sys_Error ("Old version of Hexen II demo isn't supported");
1223 #endif /* OLD_DEMO */
1224 #if !ENABLE_OLD_RETAIL
1225 /* check if we have 1.11 versions of pak0.pak and pak1.pak */
1226 if (gameflags & (GAME_OLD_CDROM0|GAME_OLD_CDROM1|GAME_OLD_OEM0|GAME_OLD_OEM2))
1227 Sys_Error ("You must patch your installation with Raven's 1.11 update");
1228 #endif /* OLD_RETAIL */
1229
1230 /* finish the base filesystem setup */
1231 if (gameflags & (GAME_REGISTERED|GAME_REGISTERED_OLD))
1232 {
1233 Cvar_SetROM ("registered", "1");
1234 Sys_Printf ("Playing the registered version.\n");
1235 }
1236 else if (gameflags & GAME_OEM)
1237 {
1238 Cvar_SetROM ("oem", "1");
1239 Sys_Printf ("Playing the oem (Matrox m3D bundle) version \"Continent of Blackmarsh\"\n");
1240 }
1241 else if (gameflags & (GAME_DEMO|GAME_OLD_DEMO))
1242 {
1243 Sys_Printf ("Playing the demo version.\n");
1244 }
1245 else
1246 {
1247 /* no proper Raven data: it's best to error out here */
1248 Sys_Error ("Unable to find a proper Hexen II installation");
1249 }
1250 if (gameflags & (GAME_OLD_DEMO|GAME_REGISTERED_OLD|GAME_OLD_OEM))
1251 Sys_Printf ("Using old/unsupported, pre-1.11 version pak files.\n");
1252 if (gameflags & (GAME_REGISTERED|GAME_REGISTERED_OLD))
1253 {
1254 if (CheckRegistered() != 0)
1255 Sys_Error ("Unable to verify retail version data.");
1256 }
1257 #if !(defined(H2W) && defined(SERVERONLY))
1258 if (gameflags & GAME_MODIFIED && !(gameflags & (GAME_REGISTERED|GAME_REGISTERED_OLD)))
1259 Sys_Error ("You must have the full version of Hexen II to play modified games");
1260 #endif
1261
1262 /* step 2: portals directory (mission pack) */
1263 #if defined(H2MP)
1264 if (! COM_CheckParm ("-noportals"))
1265 check_portals = true;
1266 if (check_portals && !(gameflags & (GAME_REGISTERED|GAME_REGISTERED_OLD)))
1267 Sys_Error ("Portal of Praevus requires registered version of Hexen II");
1268 #elif defined(H2W)
1269 if (! COM_CheckParm ("-noportals") && gameflags & (GAME_REGISTERED|GAME_REGISTERED_OLD))
1270 check_portals = true;
1271 #else
1272 /* see if the user wants mission pack support */
1273 check_portals = (COM_CheckParm ("-portals")) ||
1274 (COM_CheckParm ("-missionpack")) ||
1275 (COM_CheckParm ("-h2mp"));
1276 i = COM_CheckParm ("-game");
1277 if (i && i < com_argc-1)
1278 {
1279 if (!q_strcasecmp(com_argv[i+1], "portals"))
1280 check_portals = true;
1281 }
1282 if (check_portals && !(gameflags & (GAME_REGISTERED|GAME_REGISTERED_OLD)))
1283 Sys_Error ("Portal of Praevus requires registered version of Hexen II");
1284 #endif
1285 #if !defined(H2W)
1286 if (sv_protocol == PROTOCOL_RAVEN_111 && check_portals)
1287 Sys_Error ("Old protocol request not compatible with the Mission Pack");
1288 #endif
1289
1290 if (check_portals)
1291 {
1292 #if defined(H2W)
1293 searchpath_t *mark = fs_searchpaths;
1294 #endif
1295 FS_AddGameDirectory ("portals", true);
1296 if (! (gameflags & GAME_PORTALS))
1297 {
1298 #if !defined(H2W)
1299 Sys_Error ("Missing or invalid mission pack installation\n");
1300 #else
1301 /* back out searchpaths from invalid mission pack
1302 * installations because the portals directory is
1303 * reserved for the mission pack */
1304 searchpath_t *next;
1305 Sys_Printf ("Missing or invalid mission pack installation\n");
1306 while (fs_searchpaths != mark)
1307 {
1308 if (fs_searchpaths->pack)
1309 {
1310 fclose (fs_searchpaths->pack->handle);
1311 Z_Free (fs_searchpaths->pack->files);
1312 Z_Free (fs_searchpaths->pack);
1313 Sys_Printf ("Removed packfile %s\n", fs_searchpaths->pack->filename);
1314 }
1315 else
1316 {
1317 Sys_Printf ("Removed path %s\n", fs_searchpaths->filename);
1318 }
1319 next = fs_searchpaths->next;
1320 Z_Free (fs_searchpaths);
1321 fs_searchpaths = next;
1322 }
1323 fs_searchpaths = mark;
1324 /* back to data1 */
1325 FS_MakePath_BUF (FS_BASEDIR, NULL, fs_gamedir, sizeof(fs_gamedir), "data1");
1326 FS_MakePath_BUF (FS_USERBASE,NULL, fs_userdir, sizeof(fs_userdir), "data1");
1327 #endif /* H2W */
1328 }
1329 }
1330
1331 /* step 3: hw directory (hexenworld) */
1332 #if defined(H2W)
1333 FS_AddGameDirectory ("hw", true);
1334 /* error out if GAME_HEXENWORLD isn't set */
1335 if (!(gameflags & GAME_HEXENWORLD))
1336 Sys_Error ("You must have the HexenWorld data installed");
1337 #endif /* H2W */
1338
1339 /* this is the end of our base searchpath:
1340 * any set gamedirs, such as those from -game commandline
1341 * arguments, from exec'ed configs or the ones dictated by
1342 * the server, will be freed up to here upon a new gamedir
1343 * command */
1344 fs_base_searchpaths = fs_searchpaths;
1345
1346 i = COM_CheckParm ("-game");
1347 if (i != 0)
1348 {
1349 /* only registered versions can do -game */
1350 if (! (gameflags & (GAME_REGISTERED|GAME_REGISTERED_OLD)))
1351 Sys_Error ("You must have the full version of Hexen II to play modified games");
1352 /* add basedir/gamedir as an override game */
1353 if (i < com_argc - 1)
1354 FS_Gamedir (com_argv[i+1]);
1355 }
1356 }
1357
1358 #define FS_NUM_BUFFS 4
1359 #define FS_BUFFERLEN 1024
1360
get_fs_buffer(void)1361 static char *get_fs_buffer(void)
1362 {
1363 static char fs_buffers[FS_NUM_BUFFS][FS_BUFFERLEN];
1364 static int buffer_idx = 0;
1365 buffer_idx = (buffer_idx + 1) & (FS_NUM_BUFFS - 1);
1366 return fs_buffers[buffer_idx];
1367 }
1368
init_MakePath(int base,char * buf,size_t siz)1369 static int init_MakePath (int base, char *buf, size_t siz)
1370 {
1371 int len;
1372
1373 switch (base)
1374 {
1375 case FS_USERDIR:
1376 len = q_strlcpy(buf, fs_userdir, siz);
1377 break;
1378 case FS_GAMEDIR:
1379 len = q_strlcpy(buf, fs_gamedir, siz);
1380 break;
1381 case FS_USERBASE:
1382 len = q_strlcpy(buf, host_parms->userdir, siz);
1383 break;
1384 case FS_BASEDIR:
1385 len = q_strlcpy(buf, fs_basedir, siz);
1386 break;
1387 default:
1388 Sys_Error ("%s: Bad FS_BASE", __thisfunc__);
1389 return -1;
1390 }
1391
1392 if (len >= (int)siz - 1)
1393 return -1;
1394 if (len && !IS_DIR_SEPARATOR(buf[len - 1]))
1395 buf[len++] = DIR_SEPARATOR_CHAR;
1396 buf[len] = '\0';
1397 return len;
1398 }
1399
do_MakePath(int base,int * error,char * buf,size_t siz,const char * path)1400 static char *do_MakePath (int base, int *error, char *buf, size_t siz, const char *path)
1401 {
1402 int len;
1403
1404 len = init_MakePath(base, buf, siz);
1405 if (len < 0) goto _bad;
1406
1407 len = q_strlcat(buf, path, siz);
1408 if (len < (int)siz) {
1409 if (error) *error = 0;
1410 } else {
1411 _bad:
1412 if (error) *error = 1;
1413 Con_DPrintf("%s: overflow (string truncated)\n", __thisfunc__);
1414 }
1415
1416 return buf;
1417 }
1418
do_MakePath_VA(int base,int * error,char * buf,size_t siz,const char * format,va_list args)1419 static char *do_MakePath_VA (int base, int *error, char *buf, size_t siz,
1420 const char *format, va_list args)
1421 {
1422 int len, ret;
1423
1424 len = init_MakePath(base, buf, siz);
1425 if (len < 0) goto _bad;
1426
1427 ret = q_vsnprintf(&buf[len], siz - len, format, args);
1428 if (ret < (int)siz - len) {
1429 if (error) *error = 0;
1430 } else {
1431 _bad:
1432 if (error) *error = 1;
1433 Con_DPrintf("%s: overflow (string truncated)\n", __thisfunc__);
1434 }
1435
1436 return buf;
1437 }
1438
FSERR_MakePath_BUF(const char * caller,int linenum,int base,char * buf,size_t siz,const char * path)1439 static char *FSERR_MakePath_BUF (const char *caller, int linenum, int base,
1440 char *buf, size_t siz, const char *path)
1441 {
1442 int err;
1443 char *p;
1444
1445 p = do_MakePath(base, &err, buf, siz, path);
1446
1447 if (err) Sys_Error("%s: %d: string buffer overflow!", caller, linenum);
1448 return p;
1449 }
1450
FSERR_MakePath_VABUF(const char * caller,int linenum,int base,char * buf,size_t siz,const char * format,...)1451 static char *FSERR_MakePath_VABUF (const char *caller, int linenum, int base,
1452 char *buf, size_t siz, const char *format, ...)
1453 {
1454 va_list argptr;
1455 int err;
1456 char *p;
1457
1458 va_start (argptr, format);
1459 p = do_MakePath_VA(base, &err, buf, siz, format, argptr);
1460 va_end (argptr);
1461
1462 if (err) Sys_Error("%s: %d: string buffer overflow!", caller, linenum);
1463 return p;
1464 }
1465
FS_MakePath(int base,int * error,const char * path)1466 char *FS_MakePath (int base, int *error, const char *path)
1467 {
1468 return do_MakePath(base, error, get_fs_buffer(), FS_BUFFERLEN, path);
1469 }
1470
FS_MakePath_BUF(int base,int * error,char * buf,size_t siz,const char * path)1471 char *FS_MakePath_BUF (int base, int *error, char *buf, size_t siz, const char *path)
1472 {
1473 return do_MakePath(base, error, buf, siz, path);
1474 }
1475
FS_MakePath_VA(int base,int * error,const char * format,...)1476 char *FS_MakePath_VA (int base, int *error, const char *format, ...)
1477 {
1478 va_list argptr;
1479 char *p;
1480
1481 va_start (argptr, format);
1482 p = do_MakePath_VA(base, error, get_fs_buffer(), FS_BUFFERLEN, format, argptr);
1483 va_end (argptr);
1484
1485 return p;
1486 }
1487
FS_MakePath_VABUF(int base,int * error,char * buf,size_t siz,const char * format,...)1488 char *FS_MakePath_VABUF (int base, int *error, char *buf, size_t siz, const char *format, ...)
1489 {
1490 va_list argptr;
1491 char *p;
1492
1493 va_start (argptr, format);
1494 p = do_MakePath_VA(base, error, buf, siz, format, argptr);
1495 va_end (argptr);
1496
1497 return p;
1498 }
1499
FS_GetBasedir(void)1500 const char *FS_GetBasedir (void)
1501 {
1502 return fs_basedir;
1503 }
1504
FS_GetUserbase(void)1505 const char *FS_GetUserbase (void)
1506 {
1507 return host_parms->userdir;
1508 }
1509
FS_GetGamedir(void)1510 const char *FS_GetGamedir (void)
1511 {
1512 return fs_gamedir;
1513 }
1514
FS_GetUserdir(void)1515 const char *FS_GetUserdir (void)
1516 {
1517 return fs_userdir;
1518 }
1519
1520 /* The following FS_*() stdio replacements are necessary if one is
1521 * to perform non-sequential reads on files reopened on pak files
1522 * because we need the bookkeeping about file start/end positions.
1523 * Allocating and filling in the fshandle_t structure is the users'
1524 * responsibility when the file is initially opened. */
1525
FS_fread(void * ptr,size_t size,size_t nmemb,fshandle_t * fh)1526 size_t FS_fread(void *ptr, size_t size, size_t nmemb, fshandle_t *fh)
1527 {
1528 long byte_size;
1529 long bytes_read;
1530 size_t nmemb_read;
1531
1532 if (!fh) {
1533 errno = EBADF;
1534 return 0;
1535 }
1536 if (!ptr) {
1537 errno = EFAULT;
1538 return 0;
1539 }
1540 if (!size || !nmemb) { /* no error, just zero bytes wanted */
1541 errno = 0;
1542 return 0;
1543 }
1544
1545 byte_size = nmemb * size;
1546 if (byte_size > fh->length - fh->pos) /* just read to end */
1547 byte_size = fh->length - fh->pos;
1548 bytes_read = fread(ptr, 1, byte_size, fh->file);
1549 fh->pos += bytes_read;
1550
1551 /* fread() must return the number of elements read,
1552 * not the total number of bytes. */
1553 nmemb_read = bytes_read / size;
1554 /* even if the last member is only read partially
1555 * it is counted as a whole in the return value. */
1556 if (bytes_read % size)
1557 nmemb_read++;
1558
1559 return nmemb_read;
1560 }
1561
FS_fseek(fshandle_t * fh,long offset,int whence)1562 int FS_fseek(fshandle_t *fh, long offset, int whence)
1563 {
1564 /* I don't care about 64 bit off_t or fseeko() here.
1565 * the quake/hexen2 file system is 32 bits, anyway. */
1566 int ret;
1567
1568 if (!fh) {
1569 errno = EBADF;
1570 return -1;
1571 }
1572
1573 /* the relative file position shouldn't be smaller
1574 * than zero or bigger than the filesize. */
1575 switch (whence)
1576 {
1577 case SEEK_SET:
1578 break;
1579 case SEEK_CUR:
1580 offset += fh->pos;
1581 break;
1582 case SEEK_END:
1583 offset = fh->length + offset;
1584 break;
1585 default:
1586 errno = EINVAL;
1587 return -1;
1588 }
1589
1590 if (offset < 0) {
1591 errno = EINVAL;
1592 return -1;
1593 }
1594
1595 if (offset > fh->length) /* just seek to end */
1596 offset = fh->length;
1597
1598 ret = fseek(fh->file, fh->start + offset, SEEK_SET);
1599 if (ret < 0)
1600 return ret;
1601
1602 fh->pos = offset;
1603 return 0;
1604 }
1605
FS_fclose(fshandle_t * fh)1606 int FS_fclose(fshandle_t *fh)
1607 {
1608 if (!fh) {
1609 errno = EBADF;
1610 return -1;
1611 }
1612 return fclose(fh->file);
1613 }
1614
FS_ftell(fshandle_t * fh)1615 long FS_ftell(fshandle_t *fh)
1616 {
1617 if (!fh) {
1618 errno = EBADF;
1619 return -1;
1620 }
1621 return fh->pos;
1622 }
1623
FS_rewind(fshandle_t * fh)1624 void FS_rewind(fshandle_t *fh)
1625 {
1626 if (!fh) return;
1627 clearerr(fh->file);
1628 fseek(fh->file, fh->start, SEEK_SET);
1629 fh->pos = 0;
1630 }
1631
FS_feof(fshandle_t * fh)1632 int FS_feof(fshandle_t *fh)
1633 {
1634 if (!fh) {
1635 errno = EBADF;
1636 return -1;
1637 }
1638 if (fh->pos >= fh->length)
1639 return -1;
1640 return 0;
1641 }
1642
FS_ferror(fshandle_t * fh)1643 int FS_ferror(fshandle_t *fh)
1644 {
1645 if (!fh) {
1646 errno = EBADF;
1647 return -1;
1648 }
1649 return ferror(fh->file);
1650 }
1651
FS_fgetc(fshandle_t * fh)1652 int FS_fgetc(fshandle_t *fh)
1653 {
1654 if (!fh) {
1655 errno = EBADF;
1656 return EOF;
1657 }
1658 if (fh->pos >= fh->length)
1659 return EOF;
1660 fh->pos += 1;
1661 return fgetc(fh->file);
1662 }
1663
FS_fgets(char * s,int size,fshandle_t * fh)1664 char *FS_fgets(char *s, int size, fshandle_t *fh)
1665 {
1666 char *ret;
1667
1668 if (FS_feof(fh))
1669 return NULL;
1670
1671 if (size > (fh->length - fh->pos) + 1)
1672 size = (fh->length - fh->pos) + 1;
1673
1674 ret = fgets(s, size, fh->file);
1675 fh->pos = ftell(fh->file) - fh->start;
1676
1677 return ret;
1678 }
1679
FS_filelength(fshandle_t * fh)1680 long FS_filelength (fshandle_t *fh)
1681 {
1682 if (!fh) {
1683 errno = EBADF;
1684 return -1;
1685 }
1686 return fh->length;
1687 }
1688
1689