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