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 }