/* * Wad compressor/decompressor by Graue * Written January 23, 2007 * * This file is in the public domain; use it without any restrictions. */ #include #include #ifndef _MSC_VER #include #else typedef unsigned int uint32_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; #endif #include #include #include "err.h" #include "lzf.h" #include "xm.h" /* Note: Define WORDS_BIGENDIAN if that is the case on the target CPU. */ #define MINCOMPRESS 1024 // Don't bother compressing lumps smaller than this. #define SWAP16(n) ((((n)&0xff)<<8) | ((n)>>8)) #define SWAP32(n) \ ( \ ((((n)&0xff) <<24) \ | ((((n)>>8)&0xff)<<16) \ | ((((n)>>16)&0xff)<<8) \ | ((n)>>24) \ ) #ifdef WORDS_BIGENDIAN #define conv16le SWAP16 #define conv32le SWAP32 #else #define conv16le(n) (n) #define conv32le(n) (n) #endif #define READFUNC(name, type, conv) \ static type name(FILE *fp) \ { \ type val; \ if (fread(&val, sizeof val, 1, fp) < 1) \ { \ if (ferror(fp)) \ err(1, "error reading input file"); \ errx(1, "premature end of input file"); \ } \ val = conv(val); \ return val; \ } READFUNC(read32le, uint32_t, conv32le) #define WRITEFUNC(name, type, conv) \ static void name(FILE *fp, type val) \ { \ val = conv(val); \ if (fwrite(&val, sizeof (type), 1, fp) < 1) \ err(1, "error writing output file"); \ } WRITEFUNC(write32le, uint32_t, conv32le) #define ID_IWAD 0x44415749 #define ID_PWAD 0x44415750 #define ID_ZWAD 0x4441575a // 'ZWAD' for lzf-compressed wad typedef struct { char name[8]; uint32_t len; void *data; } lump_t; typedef struct { uint32_t id; uint32_t numlumps; lump_t *lumps; } wad_t; extern char *__progname; static void usage(void) { fprintf(stderr, "usage: %s [cd] infile outfile\n", __progname); exit(EXIT_FAILURE); } #define fileoffset_t long #define myseek fseek #define mytell ftell static void readlumpdata(lump_t *lump, uint32_t csize, int compressed, FILE *fp) { uint8_t *cbuf; uint8_t *ubuf; uint32_t usize; unsigned int retval; cbuf = xm(1, csize); if (fread(cbuf, csize, 1, fp) < 1) err(1, "cannot read lump from input file"); if (!compressed) { lump->len = csize; lump->data = cbuf; return; } usize = conv32le(*(uint32_t *)cbuf); if (usize == 0) { lump->len = csize - 4; lump->data = cbuf + 4; // XXX cannot be freed later return; } ubuf = xm(1, usize); retval = lzf_decompress(cbuf + 4, csize - 4, ubuf, usize); if (retval == 0) { if (errno == E2BIG) errx(1, "decompressed data bigger than advertised"); if (errno == EINVAL) errx(1, "invalid compressed (lzf) data"); else err(1, "unknown error from lzf"); } else if (retval < usize) errx(1, "decompressed data smaller than advertised"); lump->len = usize; lump->data = ubuf; free(cbuf); return; } static wad_t *readwad(const char *fname) { wad_t *wad; FILE *fp; int inputcompressed; fileoffset_t dirstart; uint32_t ix; fp = fopen(fname, "rb"); if (fp == NULL) err(1, "%s", fname); wad = xm(sizeof *wad, 1); // Read wad id. If it is ZWAD, prepare to read compressed lumps. wad->id = read32le(fp); inputcompressed = wad->id == ID_ZWAD; // Read number of lumps, and prepare space for that many. wad->numlumps = read32le(fp); wad->lumps = xm(sizeof wad->lumps[0], wad->numlumps); // Read offset to directory. dirstart = (fileoffset_t)read32le(fp); for (ix = 0; ix < wad->numlumps; ix++) { fileoffset_t lumpofs; uint32_t lumpsize; // The compressed size, if it is compressed. if (myseek(fp, dirstart + (16*ix), SEEK_SET) == -1) { err(1, "cannot seek input file to dir entry %lu", (unsigned long)ix); } lumpofs = (fileoffset_t)read32le(fp); lumpsize = read32le(fp); if (fread(wad->lumps[ix].name, 1, 8, fp) < 8) err(1, "cannot read lump %lu name", (unsigned long)ix); // Don't try to seek to zero-length lumps. // Their offset is meaningless. if (lumpsize == 0) { wad->lumps[ix].len = 0; continue; } if (myseek(fp, lumpofs, SEEK_SET) == -1) err(1, "cannot seek to lump %lu", (unsigned long)ix); readlumpdata(&wad->lumps[ix], lumpsize, inputcompressed, fp); } if (fclose(fp) == EOF) warn("error closing input file %s", fname); return wad; } void writewad(const wad_t *wad, const char *fname, int compress) { uint32_t *lumpofs; // Each lump's offset in the file. uint32_t *disksize; // Each lump's disk size (compressed if applicable). uint32_t dirstart; uint32_t ix; FILE *fp; lumpofs = xm(sizeof lumpofs[0], wad->numlumps); disksize = xm(sizeof disksize[0], wad->numlumps); fp = fopen(fname, "wb"); if (fp == NULL) err(1, "%s", fname); // Write header. write32le(fp, compress ? ID_ZWAD : ID_PWAD); write32le(fp, wad->numlumps); write32le(fp, 0); // Directory table comes later. for (ix = 0; ix < wad->numlumps; ix++) { uint8_t *cbuf; uint32_t csize; lumpofs[ix] = (uint32_t)mytell(fp); if (!compress) { cbuf = wad->lumps[ix].data; csize = wad->lumps[ix].len; } else { unsigned int retval; csize = wad->lumps[ix].len + 4; cbuf = xm(1, csize); if (wad->lumps[ix].len < MINCOMPRESS || (retval = lzf_compress(wad->lumps[ix].data, wad->lumps[ix].len, cbuf + 4, csize - 5)) == 0) { // Store uncompressed. memcpy(cbuf + 4, wad->lumps[ix].data, wad->lumps[ix].len); *(uint32_t *)cbuf = 0; } else { // Compression succeeded. // Store uncompressed size, and set compressed // size properly. *(uint32_t *)cbuf = conv32le(wad->lumps[ix].len); csize = retval + 4; } } if (!csize) ; // inu: 0 length markers aren't to be written else if (fwrite(cbuf, csize, 1, fp) < 1) { err(1, "cannot write lump %lu to %s", (unsigned long)ix, fname); } if (compress) free(cbuf); disksize[ix] = csize; } dirstart = (uint32_t)mytell(fp); if (myseek(fp, 8L, SEEK_SET) == -1) err(1, "cannot reseek %s to write directory start", fname); write32le(fp, dirstart); if (myseek(fp, dirstart, SEEK_SET) == -1) err(1, "cannot reseek %s to write actual directory", fname); for (ix = 0; ix < wad->numlumps; ix++) { // Write the lump's directory entry. write32le(fp, lumpofs[ix]); write32le(fp, disksize[ix]); if (fwrite(wad->lumps[ix].name, 1, 8, fp) < 8) { err(1, "cannot write lump %lu's name", (unsigned long)ix); } } if (fclose(fp) == EOF) warn("error closing output file %s", fname); } int main(int argc, char *argv[]) { const char *infile, *outfile; wad_t *wad; int compress; if (argc != 4 || strlen(argv[1]) != 1) usage(); /* c to compress, d to decompress */ if (argv[1][0] == 'c') compress = 1; else if (argv[1][0] == 'd') compress = 0; else usage(); infile = argv[2]; outfile = argv[3]; wad = readwad(infile); writewad(wad, outfile, compress); return 0; }