1 /*
2 	Relay -- a tool to record and play Quake2 demos
3 	Copyright (C) 2000 Conor Davis
4 
5 	This program is free software; you can redistribute it and/or
6 	modify it under the terms of the GNU General Public License
7 	as published by the Free Software Foundation; either version 2
8 	of the License, or (at your option) any later version.
9 
10 	This program is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with this program; if not, write to the Free Software
17 	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 	Conor Davis
20 	cedavis@planetquake.com
21 */
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include "endian.h"
28 #include "mem.h"
29 #include "pak.h"
30 #include "q2defines.h"
31 #include "q2utils.h"
32 
33 #define TOC_SIZE	64
34 
35 typedef struct
36 {
37 	char	name[56];
38 	size_t	filepos, filelen;
39 } packfile_t;
40 
41 typedef struct pak_s
42 {
43 	char			*name;		// e.g. ./baseq2/pak0.pak
44 	packfile_t		*files;
45 	size_t			numfiles;	// size of files array
46 	struct pak_s	*next;
47 } pak_t;
48 
49 typedef struct searchdir_s
50 {
51 	char				*name;
52 	struct searchdir_s	*next;
53 } searchdir_t;
54 
55 static pak_t *pak_head = NULL;
56 static searchdir_t *searchdir_head = NULL;
57 
FreePackFile(pak_t * pak)58 static void FreePackFile(pak_t *pak)
59 {
60 	Z_Free(pak->name);
61 	Z_Free(pak->files);
62 	Z_Free(pak);
63 }
64 
RemovePackFile(const char * filename)65 void RemovePackFile(const char *filename)
66 {
67 	pak_t	*pak, *prev;
68 
69 	for (prev = NULL, pak = pak_head; pak; prev = pak, pak = pak->next)
70 	{
71 		if (!strcmp(filename, pak->name))
72 		{
73 			if (prev)
74 				prev->next = pak->next;
75 			else
76 				pak_head = pak->next;
77 
78 			FreePackFile(pak);
79 			return;
80 		}
81 	}
82 }
83 
RemoveAllPackFiles()84 void RemoveAllPackFiles()
85 {
86 	pak_t	*pak, *next;
87 
88 	for (pak = pak_head; pak; pak = next)
89 	{
90 		next = pak->next;
91 		FreePackFile(pak);
92 	}
93 
94 	pak_head = NULL;
95 }
96 
AddPackFile(const char * filename)97 int AddPackFile(const char *filename)
98 {
99 	FILE		*fd;
100 	size_t		dirofs, dirlen, i;
101 	byte		ident[4];
102 	pak_t		*pak;
103 	packfile_t	*entry;
104 
105 	fd = fopen(filename, "rb");
106 	if (!fd)
107 		return -1;
108 
109 	// make sure it is a real pak file
110 	if (!fread(ident, 4, 1, fd))
111 	{
112 		fclose(fd);
113 		return -1;
114 	}
115 	if (memcmp(ident, "PACK", 4))
116 	{
117 		fclose(fd);
118 		return -1;
119 	}
120 
121 	if (!fread(&dirofs, 4, 1, fd))
122 	{
123 		fclose(fd);
124 		return -1;
125 	}
126 	if (!fread(&dirlen, 4, 1, fd))
127 	{
128 		fclose(fd);
129 		return -1;
130 	}
131 	dirofs = LittleLong(dirofs);
132 	dirlen = LittleLong(dirlen);
133 
134 	if (dirlen % TOC_SIZE)
135 	{
136 		fclose(fd);
137 		return -1;
138 	}
139 
140 	if (fseek(fd, dirofs, SEEK_SET))
141 	{
142 		fclose(fd);
143 		return -1;
144 	}
145 
146 	pak = Z_Malloc(sizeof(pak_t));
147 	pak->name = Z_Strdup(filename);
148 	pak->numfiles = dirlen / TOC_SIZE;
149 	pak->files = Z_Malloc( pak->numfiles*sizeof(packfile_t) );
150 
151 	for (i = 0, entry = pak->files; i < pak->numfiles; i++, entry++)
152 	{
153 		if (!fread(entry->name, 56, 1, fd))
154 		{
155 			FreePackFile(pak);
156 			fclose(fd);
157 			return -1;
158 		}
159 		entry->name[sizeof(entry->name)-1] = 0;
160 		if (!fread(&entry->filepos, 4, 1, fd))
161 		{
162 			FreePackFile(pak);
163 			fclose(fd);
164 			return -1;
165 		}
166 		entry->filepos = LittleLong(entry->filepos);
167 		if (!fread(&entry->filelen, 4, 1, fd))
168 		{
169 			FreePackFile(pak);
170 			fclose(fd);
171 			return -1;
172 		}
173 		entry->filelen = LittleLong(entry->filelen);
174 	}
175 
176 	pak->next = pak_head;
177 	pak_head = pak;
178 
179 	return 0;
180 }
181 
FreePackDir(searchdir_t * searchdir)182 static void FreePackDir(searchdir_t *searchdir)
183 {
184 	Z_Free(searchdir->name);
185 	Z_Free(searchdir);
186 }
187 
RemovePackDir(const char * dir,int flags)188 void RemovePackDir(const char *dir, int flags)
189 {
190 	if (flags & PACK_FILES)
191 	{
192 		searchdir_t *searchdir, *prev;
193 
194 		for (prev = NULL, searchdir = searchdir_head; searchdir; prev = searchdir, searchdir = searchdir->next)
195 		{
196 			if (!strcmp(dir, searchdir->name))
197 			{
198 				if (prev)
199 					prev->next = searchdir->next;
200 				else
201 					searchdir_head = searchdir->next;
202 
203 				FreePackDir(searchdir);
204 			}
205 		}
206 	}
207 
208 	if (flags & PACK_PACKS)
209 	{
210 		pak_t		*pak, *prev, *next;
211 		char		path[MAX_OSPATH];
212 
213 		for (prev = NULL, pak = pak_head; pak; prev = pak, pak = next)
214 		{
215 			next = pak->next;
216 			COM_FileBase(pak->name, path);
217 			if (!strcmp(dir, path))
218 			{
219 				if (prev)
220 					prev->next = pak->next;
221 				else
222 					pak_head = pak->next;
223 
224 				FreePackFile(pak);
225 			}
226 		}
227 	}
228 }
229 
RemoveAllPackDirs()230 void RemoveAllPackDirs()
231 {
232 	searchdir_t *searchdir, *next;
233 
234 	for (searchdir = searchdir_head; searchdir; searchdir = next)
235 	{
236 		next = searchdir->next;
237 		FreePackDir(searchdir);
238 	}
239 	searchdir_head = NULL;
240 
241 	RemoveAllPackFiles();
242 }
243 
AddPackDir(const char * dir,int flags)244 void AddPackDir(const char *dir, int flags)
245 {
246 	searchdir_t *searchdir;
247 	char		path[MAX_OSPATH];
248 	int 		i;
249 
250 	if (flags & PACK_FILES)
251 	{
252 		searchdir = Z_Malloc(sizeof(searchdir_t));
253 		searchdir->name = Z_Strdup(dir);
254 
255 		searchdir->next = searchdir_head;
256 		searchdir_head = searchdir;
257 	}
258 
259 	if (flags & PACK_PACKS)
260 	{
261 		for (i = 0; i < 10; i++)
262 		{
263 			sprintf(path, "%s/pak%d.pak", dir, i);
264 			AddPackFile(path);
265 		}
266 	}
267 }
268 
pfopen(const char * filename,const char * mode)269 PFILE *pfopen(const char *filename, const char *mode)
270 {
271 	PFILE		*pfd;
272 	FILE		*fd;
273 	pak_t		*pak;
274 	packfile_t	*entry;
275 	searchdir_t *searchdir;
276 	char		path[MAX_OSPATH], buf[8], *c;
277 	size_t		pos, i;
278 	int 		flags;
279 	qboolean	use_packs, use_virtual;
280 
281 	if (!filename || !filename[0])
282 		return NULL;
283 
284 	if (!mode || !mode[0])
285 		return NULL;
286 
287 	flags = 0;
288 	use_packs = false;
289 	use_virtual = false;
290 
291 	while (*mode)
292 	{
293 		switch(*mode)
294 		{
295 		case 'a':
296 			flags &= ~(PF_READONLY|PF_READWRITE);
297 			flags |= PF_WRITEONLY|PF_APPEND;
298 			break;
299 		case 'r':
300 			flags &= ~(PF_WRITEONLY|PF_READWRITE|PF_APPEND);
301 			flags |= PF_READONLY;
302 			break;
303 		case 'w':
304 			flags &= ~(PF_READONLY|PF_READWRITE|PF_APPEND);
305 			flags |= PF_WRITEONLY;
306 			break;
307 		case 'p':
308 			use_packs = true;
309 			break;
310 		case 'v':
311 			use_virtual = true;
312 			break;
313 		case 't':
314 			flags |= PF_TEXT;
315 			break;
316 		case 'b':
317 			flags &= ~PF_TEXT;
318 			break;
319 		default:
320 			break;
321 		}
322 		mode++;
323 	}
324 
325 	if (flags & PF_READONLY)
326 	{
327 		if (use_packs)
328 		{
329 			for (pak = pak_head; pak; pak = pak->next)
330 			{
331 				for (i = 0, entry = pak->files; i < pak->numfiles; i++, entry++)
332 				{
333 					if (!strcmp(filename, entry->name))
334 					{
335 						fd = fopen(pak->name, "rb");
336 						if (fd)
337 						{
338 							if (fseek(fd, entry->filepos, SEEK_SET))
339 							{
340 								fclose(fd);
341 								return NULL;
342 							}
343 
344 							pfd = Z_Malloc(sizeof(PFILE));
345 							if (!pfd)
346 							{
347 								fclose(fd);
348 								return NULL;
349 							}
350 							pfd->fd = fd;
351 							pfd->filepos = entry->filepos;
352 							pfd->filelen = entry->filelen;
353 							pfd->flags = flags;
354 
355 							return pfd;
356 						}
357 					}
358 				}
359 			}
360 		}
361 
362 		if (use_virtual)
363 		{
364 			for (searchdir = searchdir_head; searchdir; searchdir = searchdir->next)
365 			{
366 				sprintf(path, "%s/%s", searchdir->name, filename);
367 				fd = fopen(path, "rb");
368 				if (fd)
369 				{
370 					if (fseek(fd, 0, SEEK_END))
371 					{
372 						fclose(fd);
373 						return NULL;
374 					}
375 					pos = ftell(fd);
376 					if (fseek(fd, 0, SEEK_SET))
377 					{
378 						fclose(fd);
379 						return NULL;
380 					}
381 
382 					pfd = Z_Malloc(sizeof(PFILE));
383 					pfd->fd = fd;
384 					pfd->filepos = 0;
385 					pfd->filelen = pos;
386 					pfd->flags = flags;
387 
388 					return pfd;
389 				}
390 			}
391 		}
392 
393 		c = buf;
394 		*c++ = 'r';
395 		if (flags & PF_TEXT)
396 			*c++ = 't';
397 		else
398 			*c++ = 'b';
399 		*c = 0;
400 
401 		fd = fopen(filename, buf);
402 		if (!fd)
403 			return NULL;
404 
405 		if (fseek(fd, 0, SEEK_END))
406 		{
407 			fclose(fd);
408 			return NULL;
409 		}
410 		pos = ftell(fd);
411 		if (fseek(fd, 0, SEEK_SET))
412 		{
413 			fclose(fd);
414 			return NULL;
415 		}
416 
417 		pfd = Z_Malloc(sizeof(PFILE));
418 		pfd->fd = fd;
419 		pfd->filepos = 0;
420 		pfd->filelen = pos;
421 		pfd->flags = flags;
422 
423 		return pfd;
424 	}
425 
426 	if (flags & PF_WRITEONLY)
427 	{
428 		c = buf;
429 		if (flags & PF_APPEND)
430 			*c++ = 'a';
431 		else
432 			*c++ = 'w';
433 		if (flags & PF_TEXT)
434 			*c++ = 't';
435 		else
436 			*c++ = 'b';
437 		*c = 0;
438 
439 		if (use_virtual)
440 		{
441 			if (!searchdir_head)
442 				return NULL;
443 
444 			sprintf(path, "%s/%s", searchdir_head->name, filename);
445 		}
446 		else
447 			strcpy(path, filename);
448 
449 		fd = fopen(path, buf);
450 		if (!fd)
451 			return NULL;
452 
453 		pfd = Z_Malloc(sizeof(PFILE));
454 		if (!pfd)
455 		{
456 			fclose(fd);
457 			return NULL;
458 		}
459 		pfd->fd = fd;
460 		pfd->filepos = 0;
461 		pfd->filelen = ftell(fd);
462 		pfd->flags = flags;
463 
464 		return pfd;
465 	}
466 
467 	return NULL;
468 }
469 
pfclose(PFILE * pfd)470 void pfclose(PFILE *pfd)
471 {
472 	fclose(pfd->fd);
473 	Z_Free(pfd);
474 }
475 
pfseek(PFILE * pfd,long offset,int origin)476 int pfseek(PFILE *pfd, long offset, int origin)
477 {
478 	if (pfd->flags & PF_READONLY)
479 	{
480 		switch (origin)
481 		{
482 		case SEEK_SET:
483 			if ((unsigned)offset > pfd->filelen)
484 				return 1;
485 
486 			return fseek(pfd->fd, pfd->filepos + (unsigned)offset, SEEK_SET);
487 		case SEEK_CUR:
488 			if ((unsigned)(ftell(pfd->fd) + offset) < pfd->filepos)
489 				return 1;
490 			if ((unsigned)(ftell(pfd->fd) + offset) > pfd->filepos + pfd->filelen)
491 				return 1;
492 
493 			return fseek(pfd->fd, offset, SEEK_CUR);
494 		case SEEK_END:
495 			if (offset > 0)
496 				return 1;
497 			if ((unsigned)-offset > pfd->filelen)
498 				return 1;
499 
500 			return fseek(pfd->fd, pfd->filepos + pfd->filelen + offset, SEEK_SET);
501 		default:
502 			return 1;
503 		}
504 	}
505 	else if (pfd->flags & PF_WRITEONLY)
506 	{
507 		return fseek(pfd->fd, offset, origin);
508 	}
509 
510 	return 1;
511 }
512 
pftell(PFILE * pfd)513 size_t pftell(PFILE *pfd)
514 {
515 	return ftell(pfd->fd) - pfd->filepos;
516 }
517 
pfread(void * buffer,size_t size,size_t count,PFILE * pfd)518 size_t pfread(void *buffer, size_t size, size_t count, PFILE *pfd)
519 {
520 	if (pfd->flags & PF_WRITEONLY)
521 		return 0;
522 
523 	if (ftell(pfd->fd) + size*count > pfd->filepos + pfd->filelen)
524 		return 0;
525 
526 	return fread(buffer, size, count, pfd->fd);
527 }
528 
pfwrite(void * buffer,size_t size,size_t count,PFILE * pfd)529 size_t pfwrite(void *buffer, size_t size, size_t count, PFILE *pfd)
530 {
531 	if (pfd->flags & PF_READONLY)
532 		return 0;
533 
534 	return fwrite(buffer, size, count, pfd->fd);
535 }