Kart-Public/tools/libwad/src/wad.c
2014-03-15 13:11:35 -04:00

412 lines
8.5 KiB
C

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// libwad: Doom WAD format interface library.
// Copyright (C) 2011 by Callum Dickinson.
//
// 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.
//-----------------------------------------------------------------------------
/// \file wad.c
/// \brief WAD interface functionality.
#include "wad_static.h"
//
// Offset for the first lump's data is always 12 bytes.
//
#define LUMPOFFSET 12
//
// Maximum of wad files used at the same time.
// (There is a max of simultaneous open files anyway, and this should be plenty.)
//
#define MAX_WADFILES 48
wad_t *wads[MAX_WADFILES];
wad_uint32_t wad_numwads;
wad_uint32_t wad_oldnumwads;
wad_uint32_t wad_removednum;
static inline void WAD_ChangeWADNum(wad_int32_t i, wad_t *wad)
{
wads[i] = wad;
}
static inline void WAD_CacheWAD(wad_t *wad)
{
WAD_ChangeWADNum(wad_numwads, wad);
wad_oldnumwads = wad_numwads;
wad_numwads++;
}
static void WAD_UncacheWAD(wad_t *wad)
{
wad_uint32_t i;
for (i = 0; i < wad_numwads; i++)
if (WAD_WADByNum(i) == wad)
break;
WAD_ChangeWADNum(i, NULL);
wad_removednum = i;
for (; i < wad_numwads; i++)
{
if (WAD_WADByNum(i+1))
{
WAD_ChangeWADNum(i, WAD_WADByNum(i+1));
WAD_ChangeWADNum(i+1, NULL);
}
}
wad_oldnumwads = wad_numwads;
wad_numwads--;
}
wad_uint32_t WAD_WADLoopAdvance(wad_uint32_t i)
{
wad_uint32_t o;
o = wad_oldnumwads;
wad_oldnumwads = wad_numwads;
return (o > wad_numwads && i <= wad_removednum) ? i : i + 1;
}
wad_t *WAD_WADByNum(wad_uint16_t num)
{
return (wads[num]) ? (wad_t *)wads[num] : NULL;
}
wad_t *WAD_WADByFileName(const char *filename)
{
wad_uint32_t i;
wad_t *w;
for (i = 0; i < wad_numwads; i = WAD_WADLoopAdvance(i))
{
w = WAD_WADByNum(i);
if (!strcmp(filename, w->filename))
return w;
}
return NULL;
}
//
// Allocate a wadfile, setup the lumpinfo (directory) and
// lumpcache, add the wadfile to the current active wadfiles
//
// 1: Reached the maximum WAD number.
// 2: Cannot open the file.
// 3: Cannot read the WAD file's header.
// 4: Invalid WAD ID.
// 5: WAD's lump directory is corrupt.
// 6: Corrupt compressed WAD.
//
wad_t *WAD_OpenWAD(const char *filename)
{
size_t i;
// Pointer to the WAD.
wad_t *wad = NULL;
// WAD file structure information.
// wad->filename is the function's argument.
FILE *file; // wad->file
wadheader_t *header; // wad->header
lump_t *l, *lumps = NULL; // wad->lumps
// Temporary lump information.
wad_uint64_t position;
lumpdir_t *lumpdir;
lump_t *ln = NULL;
//
// Check if the limit of maximum WAD files has been
// reached or not.
//
if (wad_numwads >= MAX_WADFILES)
return NULL;
//
// Open the WAD file.
//
if (!(file = fopen(filename, "rb")))
return NULL;
//
// Read the WAD file's header.
//
header = malloc(sizeof(*header));
if (fread(header, 1, sizeof(*header), file) < sizeof(*header))
return NULL;
// Check if the ID is valid.
if (memcmp(header->id, "IWAD", 4) && memcmp(header->id, "PWAD", 4) && memcmp(header->id, "SDLL", 4))
{
WAD_FreeMemory(file, header, NULL, NULL, NULL);
return NULL;
}
// Check the rest of the header for type.
header->numlumps = WAD_INT32_Endianness(header->numlumps);
header->lumpdir = WAD_INT32_Endianness(header->lumpdir);
//
// Read the WAD file's lump directory.
//
i = header->numlumps * sizeof(*lumpdir);
lumpdir = malloc(i);
if (fseek(file, header->lumpdir, SEEK_SET) < 0 || fread(lumpdir, i, 1, file) < 1)
{
WAD_FreeMemory(file, header, lumpdir, NULL, NULL);
return NULL;
}
//
// Fill lump information structure.
//
for (i = 0; i < header->numlumps; i++, lumpdir++)
{
if (!(l = malloc(sizeof(*l))))
{
WAD_FreeMemory(file, header, lumpdir, lumps, NULL);
return NULL;
}
position = WAD_INT32_Endianness(lumpdir->position);
strncpy(l->name, lumpdir->name, 8);
l->size = l->disksize = WAD_INT32_Endianness(lumpdir->size);
l->compressed = false; // TODO: Compressed WAD support.
if (l->disksize)
{
fseek(file, position, SEEK_SET);
if (!(l->data = malloc(l->disksize)) || fread(l->data, l->disksize, 1, file) < 1)
{
WAD_FreeMemory(file, header, lumpdir, lumps, NULL);
if (!l->data)
return NULL;
else
return NULL;
}
}
l->num = i;
l->next = NULL;
if (!lumps)
{
l->prev = NULL;
ln = lumps = l;
}
else
{
l->prev = ln;
ln = ln->next = l;
}
}
//free(lumpdir);
//
// Allocate and fill WAD entry.
//
if (!(wad = malloc(sizeof(*wad))))
{
WAD_FreeMemory(file, header, NULL, lumps, NULL);
return NULL;
}
wad->filename = (char *)filename;
wad->header = header;
wad->compressed = false;
wad->lumps = lumps;
for (l = wad->lumps; l; l = l->next)
l->wad = wad;
fclose(file);
//
// Add the WAD to the wadfiles buffer and wad_numwads number.
//
WAD_CacheWAD(wad);
//
// Done, loaded the WAD file.
//
return wad;
}
// 1: Invalid wad number.
// 2: Unable to open file for writing.
// 3: Unable to write wad ID to the header.
// 4: Unable to write number of lumps to the header.
// 5: Unable to write temporary data to the header.
// 6: Unable to write lump data.
// 7: Unable to write position of lump data.
// 8: Unable to Write size of lump data.
// 9: Unable to write lump name.
// 9: Unable to write directory position.
wad_int32_t WAD_SaveWAD(wad_t *wad)
{
lump_t *l;
FILE *file;
char *newfilename;
wad_int64_t diroffset, lumpoffset;
if (!wad)
return 1;
newfilename = WAD_VA("%s%s", wad->filename, ".bak");
remove(newfilename);
rename(wad->filename, newfilename);
if (!(file = fopen(wad->filename, "wb")))
return 2;
//
// Write four-character WAD ID.
//
if(fwrite(wad->header->id, 4, 1, file) < 1)
return 3;
//
// Write number of lumps.
//
if (fwrite(&wad->header->numlumps, 4, 1, file) < 1)
return 4;
//
// Offset of directory is not known yet. For now, write number of lumps
// again, just to fill the space.
//
if (fwrite(&wad->header->numlumps, 4, 1, file) < 1)
return 5;
//
// Loop through lump list, writing lump data.
//
for (l = wad->lumps; l; l = l->next)
{
// Don't write anything for the head of the lump list or for lumps of
// zero length.
if (!l->data)
continue;
// Write the data.
if (fwrite(l->data, l->disksize, 1, file) < 1)
return 6;
}
//
// Current position is where directory will start.
//
diroffset = ftell(file);
//
// Pointer to the offset for the first lump's data.
//
lumpoffset = LUMPOFFSET;
//
// Loop through lump list again, this time writing directory entries.
//
for (l = wad->lumps; l; l = l->next)
{
// Write position of lump data.
if (fwrite(&lumpoffset, 4, 1, file) < 1)
return 7;
// Write size of lump data.
if (fwrite(&(l->disksize), 4, 1, file) < 1)
return 8;
// Write lump name.
if (fwrite(&(l->name), 8, 1, file) < 1)
return 9;
// Increment lumpoffset variable as appropriate.
lumpoffset += l->disksize;
}
//
// Go back to header and write the proper directory position.
//
fseek(file, 8, SEEK_SET);
if (fwrite(&diroffset, 4, 1, file) < 1)
return 10;
//
// Done, saved the WAD file.
//
return 0;
}
inline wad_int32_t WAD_SaveWADByNum(wad_uint16_t num)
{
return WAD_SaveWAD(WAD_WADByNum(num));
}
inline wad_int32_t WAD_SaveWADByFileName(const char *filename)
{
return WAD_SaveWAD(WAD_WADByFileName(filename));
}
inline wad_int32_t WAD_SaveCloseWAD(wad_t *wad)
{
wad_int32_t ret;
if (!(ret = WAD_SaveWAD(wad)))
return ret;
WAD_CloseWAD(wad);
return ret;
}
inline wad_int32_t WAD_SaveCloseWADByNum(wad_uint16_t num)
{
return WAD_SaveCloseWAD(WAD_WADByNum(num));
}
inline wad_int32_t WAD_SaveCloseWADByFileName(const char *filename)
{
return WAD_SaveCloseWAD(WAD_WADByFileName(filename));
}
void WAD_CloseWAD(wad_t *wad)
{
if (!wad)
return;
//
// Free memory used by the WAD.
//
WAD_FreeMemory(NULL, wad->header, NULL, wad->lumps, wad);
//
// Remove references to the WAD.
//
WAD_UncacheWAD(wad);
}
inline void WAD_CloseWADByNum(wad_uint16_t num)
{
WAD_CloseWAD(WAD_WADByNum(num));
}
inline void WAD_CloseWADByFileName(const char *name)
{
WAD_CloseWAD(WAD_WADByFileName(name));
}