quakeforge/libs/util/pakfile.c

284 lines
6.1 KiB
C

/*
packfile.c
pak file support
Copyright (C) 2003 #AUTHOR#
Author: #AUTHOR#
Date: #DATE#
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:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <errno.h>
#include "QF/pakfile.h"
#include "QF/qendian.h"
#include "QF/quakeio.h"
#include "QF/sys.h"
static const char *
pack_get_key (const void *p, void *unused)
{
return ((dpackfile_t *) p)->name;
}
VISIBLE pack_t *
pack_new (const char *name)
{
pack_t *pack = calloc (sizeof (*pack), 1);
if (!pack)
return 0;
pack->filename = strdup (name);
if (!pack->filename) {
free (pack);
return 0;
}
pack->file_hash = Hash_NewTable (1021, pack_get_key, 0, 0);
if (!pack->file_hash) {
free (pack->filename);
free (pack);
return 0;
}
return pack;
}
VISIBLE void
pack_del (pack_t *pack)
{
if (pack->files)
free (pack->files);
if (pack->handle)
Qclose (pack->handle);
if (pack->filename)
free (pack->filename);
if (pack->file_hash)
free (pack->file_hash);
free (pack);
}
VISIBLE void
pack_rehash (pack_t *pack)
{
int i;
for (i = 0; i < pack->numfiles; i++) {
Hash_Add (pack->file_hash, &pack->files[i]);
}
}
VISIBLE pack_t *
pack_open (const char *name)
{
pack_t *pack = pack_new (name);
int i;
if (!pack)
return 0;
pack->handle = Qopen (name, "rb");
if (!pack->handle) {
goto error;
}
if (Qread (pack->handle, &pack->header, sizeof (pack->header))
!= sizeof (pack->header)) {
fprintf (stderr, "%s: not a pack file\n", name);
errno = 0;
goto error;
}
if (strncmp (pack->header.id, "PACK", 4)) {
fprintf (stderr, "%s: not a pack file\n", name);
errno = 0;
goto error;
}
pack->header.dirofs = LittleLong (pack->header.dirofs);
pack->header.dirlen = LittleLong (pack->header.dirlen);
pack->numfiles = pack->header.dirlen / sizeof (dpackfile_t);
pack->old_numfiles = pack->files_size = pack->numfiles;
pack->files = malloc (pack->files_size * sizeof (dpackfile_t));
if (!pack->files) {
//fprintf (stderr, "out of memory\n");
goto error;
}
Qseek (pack->handle, pack->header.dirofs, SEEK_SET);
Qread (pack->handle, pack->files, pack->numfiles * sizeof (pack->files[0]));
for (i = 0; i < pack->numfiles; i++) {
pack->files[i].filepos = LittleLong (pack->files[i].filepos);
pack->files[i].filelen = LittleLong (pack->files[i].filelen);
Hash_Add (pack->file_hash, &pack->files[i]);
}
return pack;
error:
pack_del (pack);
return 0;
}
VISIBLE pack_t *
pack_create (const char *name)
{
pack_t *pack = pack_new (name);
if (!pack)
return 0;
pack->handle = Qopen (name, "wb");
if (!pack->handle) {
pack_del (pack);
return 0;
}
strncpy (pack->header.id, "PACK", sizeof (pack->header.id));
Qwrite (pack->handle, &pack->header, sizeof (pack->header));
return pack;
}
VISIBLE void
pack_close (pack_t *pack)
{
int i;
if (pack->modified) {
if (pack->numfiles > pack->old_numfiles) {
Qseek (pack->handle, 0, SEEK_END);
pack->header.dirofs = Qtell (pack->handle);
}
for (i = 0; i < pack->numfiles; i++) {
pack->files[i].filepos = LittleLong (pack->files[i].filepos);
pack->files[i].filelen = LittleLong (pack->files[i].filelen);
}
Qseek (pack->handle, pack->header.dirofs, SEEK_SET);
Qwrite (pack->handle, pack->files,
pack->numfiles * sizeof (pack->files[0]));
pack->header.dirlen = pack->numfiles * sizeof (pack->files[0]);
pack->header.dirofs = LittleLong (pack->header.dirofs);
pack->header.dirlen = LittleLong (pack->header.dirlen);
Qseek (pack->handle, 0, SEEK_SET);
Qwrite (pack->handle, &pack->header, sizeof (pack->header));
Qseek (pack->handle, 0, SEEK_END);
}
pack_del (pack);
}
VISIBLE int
pack_add (pack_t *pack, const char *filename)
{
dpackfile_t *pf;
QFile *file;
char buffer[16384];
int bytes;
pf = Hash_Find (pack->file_hash, filename);
if (pf)
return -1;
if (pack->numfiles == pack->files_size) {
dpackfile_t *f;
pack->files_size += 64;
f = realloc (pack->files, pack->files_size * sizeof (dpackfile_t));
if (!f)
return -1;
pack->files = f;
}
file = Qopen (filename, "rb");
if (!file)
return -1;
pack->modified = 1;
pf = &pack->files[pack->numfiles++];
if (filename[0] == '/') {
fprintf (stderr, "removing leading /");
filename++;
}
strncpy (pf->name, filename, sizeof (pf->name));
pf->name[sizeof (pf->name) - 1] = 0;
Qseek (pack->handle, 0, SEEK_END);
pf->filepos = Qtell (pack->handle);
pf->filelen = 0;
while ((bytes = Qread (file, buffer, sizeof (buffer)))) {
Qwrite (pack->handle, buffer, bytes);
pf->filelen += bytes;
}
Qclose (file);
if (pack->pad && pf->filelen & 3) {
static char buf[4];
Qwrite (pack->handle, buf, 4 - (pf->filelen & 3));
}
Hash_Add (pack->file_hash, pf);
return 0;
}
VISIBLE int
pack_extract (pack_t *pack, dpackfile_t *pf)
{
const char *name = pf->name;
size_t count;
int len;
QFile *file;
char buffer[16384];
if (Sys_CreatePath (name) == -1)
return -1;
if (!(file = Qopen (name, "wb")))
return -1;
Qseek (pack->handle, pf->filepos, SEEK_SET);
len = pf->filelen;
while (len) {
count = len;
if (count > sizeof (buffer))
count = sizeof (buffer);
count = Qread (pack->handle, buffer, count);
Qwrite (file, buffer, count);
len -= count;
}
Qclose (file);
return 0;
}
VISIBLE dpackfile_t *
pack_find_file (pack_t *pack, const char *filename)
{
return Hash_Find (pack->file_hash, filename);
}