1 /*
2 * Wad compressor/decompressor by Graue <graue@oceanbase.org>
3 * Written January 23, 2007
4 *
5 * This file is in the public domain; use it without any restrictions.
6 */
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #ifndef _MSC_VER
11 #include <stdint.h>
12 #else
13 typedef unsigned int uint32_t;
14 typedef unsigned char uint8_t;
15 typedef unsigned short uint16_t;
16 #endif
17 #include <errno.h>
18 #include <string.h>
19 #include "err.h"
20 #include "lzf.h"
21 #include "xm.h"
22
23 /* Note: Define WORDS_BIGENDIAN if that is the case on the target CPU. */
24
25 #define MINCOMPRESS 1024 // Don't bother compressing lumps smaller than this.
26
27 #define SWAP16(n) ((((n)&0xff)<<8) | ((n)>>8))
28 #define SWAP32(n) \
29 ( \
30 ((((n)&0xff) <<24) \
31 | ((((n)>>8)&0xff)<<16) \
32 | ((((n)>>16)&0xff)<<8) \
33 | ((n)>>24) \
34 )
35 #ifdef WORDS_BIGENDIAN
36 #define conv16le SWAP16
37 #define conv32le SWAP32
38 #else
39 #define conv16le(n) (n)
40 #define conv32le(n) (n)
41 #endif
42 #define READFUNC(name, type, conv) \
43 static type name(FILE *fp) \
44 { \
45 type val; \
46 if (fread(&val, sizeof val, 1, fp) < 1) \
47 { \
48 if (ferror(fp)) \
49 err(1, "error reading input file"); \
50 errx(1, "premature end of input file"); \
51 } \
52 val = conv(val); \
53 return val; \
54 }
55 READFUNC(read32le, uint32_t, conv32le)
56
57 #define WRITEFUNC(name, type, conv) \
58 static void name(FILE *fp, type val) \
59 { \
60 val = conv(val); \
61 if (fwrite(&val, sizeof (type), 1, fp) < 1) \
62 err(1, "error writing output file"); \
63 }
64 WRITEFUNC(write32le, uint32_t, conv32le)
65
66 #define ID_IWAD 0x44415749
67 #define ID_PWAD 0x44415750
68 #define ID_ZWAD 0x4441575a // 'ZWAD' for lzf-compressed wad
69
70 typedef struct
71 {
72 char name[8];
73 uint32_t len;
74 void *data;
75 } lump_t;
76
77 typedef struct
78 {
79 uint32_t id;
80 uint32_t numlumps;
81 lump_t *lumps;
82 } wad_t;
83
84 extern char *__progname;
85
usage(void)86 static void usage(void)
87 {
88 fprintf(stderr, "usage: %s [cd] infile outfile\n", __progname);
89 exit(EXIT_FAILURE);
90 }
91
92 #define fileoffset_t long
93 #define myseek fseek
94 #define mytell ftell
95
readlumpdata(lump_t * lump,uint32_t csize,int compressed,FILE * fp)96 static void readlumpdata(lump_t *lump, uint32_t csize, int compressed, FILE *fp)
97 {
98 uint8_t *cbuf;
99 uint8_t *ubuf;
100 uint32_t usize;
101 unsigned int retval;
102
103 cbuf = xm(1, csize);
104 if (fread(cbuf, csize, 1, fp) < 1)
105 err(1, "cannot read lump from input file");
106
107 if (!compressed)
108 {
109 lump->len = csize;
110 lump->data = cbuf;
111 return;
112 }
113
114 usize = conv32le(*(uint32_t *)cbuf);
115 if (usize == 0)
116 {
117 lump->len = csize - 4;
118 lump->data = cbuf + 4; // XXX cannot be freed later
119 return;
120 }
121
122 ubuf = xm(1, usize);
123 retval = lzf_decompress(cbuf + 4, csize - 4, ubuf, usize);
124 if (retval == 0)
125 {
126 if (errno == E2BIG)
127 errx(1, "decompressed data bigger than advertised");
128 if (errno == EINVAL)
129 errx(1, "invalid compressed (lzf) data");
130 else
131 err(1, "unknown error from lzf");
132 }
133 else if (retval < usize)
134 errx(1, "decompressed data smaller than advertised");
135 lump->len = usize;
136 lump->data = ubuf;
137 free(cbuf);
138 return;
139 }
140
readwad(const char * fname)141 static wad_t *readwad(const char *fname)
142 {
143 wad_t *wad;
144 FILE *fp;
145 int inputcompressed;
146 fileoffset_t dirstart;
147 uint32_t ix;
148
149 fp = fopen(fname, "rb");
150 if (fp == NULL) err(1, "%s", fname);
151
152 wad = xm(sizeof *wad, 1);
153
154 // Read wad id. If it is ZWAD, prepare to read compressed lumps.
155 wad->id = read32le(fp);
156 inputcompressed = wad->id == ID_ZWAD;
157
158 // Read number of lumps, and prepare space for that many.
159 wad->numlumps = read32le(fp);
160 wad->lumps = xm(sizeof wad->lumps[0], wad->numlumps);
161
162 // Read offset to directory.
163 dirstart = (fileoffset_t)read32le(fp);
164
165 for (ix = 0; ix < wad->numlumps; ix++)
166 {
167 fileoffset_t lumpofs;
168 uint32_t lumpsize; // The compressed size, if it is compressed.
169
170 if (myseek(fp, dirstart + (16*ix), SEEK_SET) == -1)
171 {
172 err(1, "cannot seek input file to dir entry %lu",
173 (unsigned long)ix);
174 }
175 lumpofs = (fileoffset_t)read32le(fp);
176 lumpsize = read32le(fp);
177 if (fread(wad->lumps[ix].name, 1, 8, fp) < 8)
178 err(1, "cannot read lump %lu name", (unsigned long)ix);
179
180 // Don't try to seek to zero-length lumps.
181 // Their offset is meaningless.
182 if (lumpsize == 0)
183 {
184 wad->lumps[ix].len = 0;
185 continue;
186 }
187
188 if (myseek(fp, lumpofs, SEEK_SET) == -1)
189 err(1, "cannot seek to lump %lu", (unsigned long)ix);
190 readlumpdata(&wad->lumps[ix], lumpsize, inputcompressed, fp);
191 }
192
193 if (fclose(fp) == EOF)
194 warn("error closing input file %s", fname);
195 return wad;
196 }
197
writewad(const wad_t * wad,const char * fname,int compress)198 void writewad(const wad_t *wad, const char *fname, int compress)
199 {
200 uint32_t *lumpofs; // Each lump's offset in the file.
201 uint32_t *disksize; // Each lump's disk size (compressed if applicable).
202 uint32_t dirstart;
203 uint32_t ix;
204 FILE *fp;
205
206 lumpofs = xm(sizeof lumpofs[0], wad->numlumps);
207 disksize = xm(sizeof disksize[0], wad->numlumps);
208
209 fp = fopen(fname, "wb");
210 if (fp == NULL) err(1, "%s", fname);
211
212 // Write header.
213 write32le(fp, compress ? ID_ZWAD : ID_PWAD);
214 write32le(fp, wad->numlumps);
215 write32le(fp, 0); // Directory table comes later.
216
217 for (ix = 0; ix < wad->numlumps; ix++)
218 {
219 uint8_t *cbuf;
220 uint32_t csize;
221
222 lumpofs[ix] = (uint32_t)mytell(fp);
223 if (!compress)
224 {
225 cbuf = wad->lumps[ix].data;
226 csize = wad->lumps[ix].len;
227 }
228 else
229 {
230 unsigned int retval;
231
232 csize = wad->lumps[ix].len + 4;
233 cbuf = xm(1, csize);
234 if (wad->lumps[ix].len < MINCOMPRESS
235 || (retval = lzf_compress(wad->lumps[ix].data,
236 wad->lumps[ix].len, cbuf + 4,
237 csize - 5)) == 0)
238 {
239 // Store uncompressed.
240 memcpy(cbuf + 4, wad->lumps[ix].data,
241 wad->lumps[ix].len);
242 *(uint32_t *)cbuf = 0;
243 }
244 else
245 {
246 // Compression succeeded.
247 // Store uncompressed size, and set compressed
248 // size properly.
249 *(uint32_t *)cbuf =
250 conv32le(wad->lumps[ix].len);
251 csize = retval + 4;
252 }
253 }
254
255 if (!csize)
256 ; // inu: 0 length markers aren't to be written
257 else if (fwrite(cbuf, csize, 1, fp) < 1)
258 {
259 err(1, "cannot write lump %lu to %s", (unsigned long)ix,
260 fname);
261 }
262
263 if (compress)
264 free(cbuf);
265 disksize[ix] = csize;
266 }
267
268 dirstart = (uint32_t)mytell(fp);
269 if (myseek(fp, 8L, SEEK_SET) == -1)
270 err(1, "cannot reseek %s to write directory start", fname);
271 write32le(fp, dirstart);
272 if (myseek(fp, dirstart, SEEK_SET) == -1)
273 err(1, "cannot reseek %s to write actual directory", fname);
274
275 for (ix = 0; ix < wad->numlumps; ix++)
276 {
277 // Write the lump's directory entry.
278 write32le(fp, lumpofs[ix]);
279 write32le(fp, disksize[ix]);
280 if (fwrite(wad->lumps[ix].name, 1, 8, fp) < 8)
281 {
282 err(1, "cannot write lump %lu's name",
283 (unsigned long)ix);
284 }
285 }
286
287 if (fclose(fp) == EOF)
288 warn("error closing output file %s", fname);
289 }
290
main(int argc,char * argv[])291 int main(int argc, char *argv[])
292 {
293 const char *infile, *outfile;
294 wad_t *wad;
295 int compress;
296
297 if (argc != 4 || strlen(argv[1]) != 1)
298 usage();
299
300 /* c to compress, d to decompress */
301 if (argv[1][0] == 'c') compress = 1;
302 else if (argv[1][0] == 'd') compress = 0;
303 else usage();
304
305 infile = argv[2];
306 outfile = argv[3];
307
308 wad = readwad(infile);
309 writewad(wad, outfile, compress);
310 return 0;
311 }
312