/* Relay -- a tool to record and play Quake2 demos Copyright (C) 2000 Conor Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Conor Davis cedavis@planetquake.com */ #include #include #include #include "endian.h" #include "mem.h" #include "pak.h" #include "q2defines.h" #include "q2utils.h" #define TOC_SIZE 64 typedef struct { char name[56]; size_t filepos, filelen; } packfile_t; typedef struct pak_s { char *name; // e.g. ./baseq2/pak0.pak packfile_t *files; size_t numfiles; // size of files array struct pak_s *next; } pak_t; typedef struct searchdir_s { char *name; struct searchdir_s *next; } searchdir_t; static pak_t *pak_head = NULL; static searchdir_t *searchdir_head = NULL; static void FreePackFile(pak_t *pak) { Z_Free(pak->name); Z_Free(pak->files); Z_Free(pak); } void RemovePackFile(const char *filename) { pak_t *pak, *prev; for (prev = NULL, pak = pak_head; pak; prev = pak, pak = pak->next) { if (!strcmp(filename, pak->name)) { if (prev) prev->next = pak->next; else pak_head = pak->next; FreePackFile(pak); return; } } } void RemoveAllPackFiles() { pak_t *pak, *next; for (pak = pak_head; pak; pak = next) { next = pak->next; FreePackFile(pak); } pak_head = NULL; } int AddPackFile(const char *filename) { FILE *fd; size_t dirofs, dirlen, i; byte ident[4]; pak_t *pak; packfile_t *entry; fd = fopen(filename, "rb"); if (!fd) return -1; // make sure it is a real pak file if (!fread(ident, 4, 1, fd)) { fclose(fd); return -1; } if (memcmp(ident, "PACK", 4)) { fclose(fd); return -1; } if (!fread(&dirofs, 4, 1, fd)) { fclose(fd); return -1; } if (!fread(&dirlen, 4, 1, fd)) { fclose(fd); return -1; } dirofs = LittleLong(dirofs); dirlen = LittleLong(dirlen); if (dirlen % TOC_SIZE) { fclose(fd); return -1; } if (fseek(fd, dirofs, SEEK_SET)) { fclose(fd); return -1; } pak = Z_Malloc(sizeof(pak_t)); pak->name = Z_Strdup(filename); pak->numfiles = dirlen / TOC_SIZE; pak->files = Z_Malloc( pak->numfiles*sizeof(packfile_t) ); for (i = 0, entry = pak->files; i < pak->numfiles; i++, entry++) { if (!fread(entry->name, 56, 1, fd)) { FreePackFile(pak); fclose(fd); return -1; } entry->name[sizeof(entry->name)-1] = 0; if (!fread(&entry->filepos, 4, 1, fd)) { FreePackFile(pak); fclose(fd); return -1; } entry->filepos = LittleLong(entry->filepos); if (!fread(&entry->filelen, 4, 1, fd)) { FreePackFile(pak); fclose(fd); return -1; } entry->filelen = LittleLong(entry->filelen); } pak->next = pak_head; pak_head = pak; return 0; } static void FreePackDir(searchdir_t *searchdir) { Z_Free(searchdir->name); Z_Free(searchdir); } void RemovePackDir(const char *dir, int flags) { if (flags & PACK_FILES) { searchdir_t *searchdir, *prev; for (prev = NULL, searchdir = searchdir_head; searchdir; prev = searchdir, searchdir = searchdir->next) { if (!strcmp(dir, searchdir->name)) { if (prev) prev->next = searchdir->next; else searchdir_head = searchdir->next; FreePackDir(searchdir); } } } if (flags & PACK_PACKS) { pak_t *pak, *prev, *next; char path[MAX_OSPATH]; for (prev = NULL, pak = pak_head; pak; prev = pak, pak = next) { next = pak->next; COM_FileBase(pak->name, path); if (!strcmp(dir, path)) { if (prev) prev->next = pak->next; else pak_head = pak->next; FreePackFile(pak); } } } } void RemoveAllPackDirs() { searchdir_t *searchdir, *next; for (searchdir = searchdir_head; searchdir; searchdir = next) { next = searchdir->next; FreePackDir(searchdir); } searchdir_head = NULL; RemoveAllPackFiles(); } void AddPackDir(const char *dir, int flags) { searchdir_t *searchdir; char path[MAX_OSPATH]; int i; if (flags & PACK_FILES) { searchdir = Z_Malloc(sizeof(searchdir_t)); searchdir->name = Z_Strdup(dir); searchdir->next = searchdir_head; searchdir_head = searchdir; } if (flags & PACK_PACKS) { for (i = 0; i < 10; i++) { sprintf(path, "%s/pak%d.pak", dir, i); AddPackFile(path); } } } PFILE *pfopen(const char *filename, const char *mode) { PFILE *pfd; FILE *fd; pak_t *pak; packfile_t *entry; searchdir_t *searchdir; char path[MAX_OSPATH], buf[8], *c; size_t pos, i; int flags; qboolean use_packs, use_virtual; if (!filename || !filename[0]) return NULL; if (!mode || !mode[0]) return NULL; flags = 0; use_packs = false; use_virtual = false; while (*mode) { switch(*mode) { case 'a': flags &= ~(PF_READONLY|PF_READWRITE); flags |= PF_WRITEONLY|PF_APPEND; break; case 'r': flags &= ~(PF_WRITEONLY|PF_READWRITE|PF_APPEND); flags |= PF_READONLY; break; case 'w': flags &= ~(PF_READONLY|PF_READWRITE|PF_APPEND); flags |= PF_WRITEONLY; break; case 'p': use_packs = true; break; case 'v': use_virtual = true; break; case 't': flags |= PF_TEXT; break; case 'b': flags &= ~PF_TEXT; break; default: break; } mode++; } if (flags & PF_READONLY) { if (use_packs) { for (pak = pak_head; pak; pak = pak->next) { for (i = 0, entry = pak->files; i < pak->numfiles; i++, entry++) { if (!strcmp(filename, entry->name)) { fd = fopen(pak->name, "rb"); if (fd) { if (fseek(fd, entry->filepos, SEEK_SET)) { fclose(fd); return NULL; } pfd = Z_Malloc(sizeof(PFILE)); if (!pfd) { fclose(fd); return NULL; } pfd->fd = fd; pfd->filepos = entry->filepos; pfd->filelen = entry->filelen; pfd->flags = flags; return pfd; } } } } } if (use_virtual) { for (searchdir = searchdir_head; searchdir; searchdir = searchdir->next) { sprintf(path, "%s/%s", searchdir->name, filename); fd = fopen(path, "rb"); if (fd) { if (fseek(fd, 0, SEEK_END)) { fclose(fd); return NULL; } pos = ftell(fd); if (fseek(fd, 0, SEEK_SET)) { fclose(fd); return NULL; } pfd = Z_Malloc(sizeof(PFILE)); pfd->fd = fd; pfd->filepos = 0; pfd->filelen = pos; pfd->flags = flags; return pfd; } } } c = buf; *c++ = 'r'; if (flags & PF_TEXT) *c++ = 't'; else *c++ = 'b'; *c = 0; fd = fopen(filename, buf); if (!fd) return NULL; if (fseek(fd, 0, SEEK_END)) { fclose(fd); return NULL; } pos = ftell(fd); if (fseek(fd, 0, SEEK_SET)) { fclose(fd); return NULL; } pfd = Z_Malloc(sizeof(PFILE)); pfd->fd = fd; pfd->filepos = 0; pfd->filelen = pos; pfd->flags = flags; return pfd; } if (flags & PF_WRITEONLY) { c = buf; if (flags & PF_APPEND) *c++ = 'a'; else *c++ = 'w'; if (flags & PF_TEXT) *c++ = 't'; else *c++ = 'b'; *c = 0; if (use_virtual) { if (!searchdir_head) return NULL; sprintf(path, "%s/%s", searchdir_head->name, filename); } else strcpy(path, filename); fd = fopen(path, buf); if (!fd) return NULL; pfd = Z_Malloc(sizeof(PFILE)); if (!pfd) { fclose(fd); return NULL; } pfd->fd = fd; pfd->filepos = 0; pfd->filelen = ftell(fd); pfd->flags = flags; return pfd; } return NULL; } void pfclose(PFILE *pfd) { fclose(pfd->fd); Z_Free(pfd); } int pfseek(PFILE *pfd, long offset, int origin) { if (pfd->flags & PF_READONLY) { switch (origin) { case SEEK_SET: if ((unsigned)offset > pfd->filelen) return 1; return fseek(pfd->fd, pfd->filepos + (unsigned)offset, SEEK_SET); case SEEK_CUR: if ((unsigned)(ftell(pfd->fd) + offset) < pfd->filepos) return 1; if ((unsigned)(ftell(pfd->fd) + offset) > pfd->filepos + pfd->filelen) return 1; return fseek(pfd->fd, offset, SEEK_CUR); case SEEK_END: if (offset > 0) return 1; if ((unsigned)-offset > pfd->filelen) return 1; return fseek(pfd->fd, pfd->filepos + pfd->filelen + offset, SEEK_SET); default: return 1; } } else if (pfd->flags & PF_WRITEONLY) { return fseek(pfd->fd, offset, origin); } return 1; } size_t pftell(PFILE *pfd) { return ftell(pfd->fd) - pfd->filepos; } size_t pfread(void *buffer, size_t size, size_t count, PFILE *pfd) { if (pfd->flags & PF_WRITEONLY) return 0; if (ftell(pfd->fd) + size*count > pfd->filepos + pfd->filelen) return 0; return fread(buffer, size, count, pfd->fd); } size_t pfwrite(void *buffer, size_t size, size_t count, PFILE *pfd) { if (pfd->flags & PF_READONLY) return 0; return fwrite(buffer, size, count, pfd->fd); }