1 /* decrunch.c
2  *
3  * based on load.c from:
4  * Extended Module Player
5  *
6  * Copyright (C) 1996-1999 Claudio Matsuoka and Hipolito Carraro Jr
7  *
8  * CHANGES: (modified for uade by mld)
9  * removed all xmp related code)
10  * added "custom" labels of pp20 files
11  * added support for external unrar decruncher
12  * added support for the external XPK Lib for Unix (the xType usage *g*)
13  *
14  * TODO:
15  * real builtin support for XPK lib for Unix
16  *
17  * This file is part of the Extended Module Player and is distributed
18  * under the terms of the GNU General Public License. See doc/COPYING
19  * for more information.
20  */
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <stdint.h>
26 #include <libgen.h>
27 #include <errno.h>
28 #include <limits.h>
29 #include <unistd.h>
30 #include <assert.h>
31 
32 #include "decrunch.h"
33 #include "ppdepack.h"
34 #include "unsqsh.h"
35 #include "mmcmp.h"
36 #include "s404_dec.h"
37 #include "compat.h"
38 
39 
atomic_fread(void * dst,FILE * f,size_t count)40 size_t atomic_fread(void *dst, FILE *f, size_t count)
41 {
42     uint8_t *p = (uint8_t *) dst;
43     size_t left = count;
44 
45     while (left > 0) {
46 
47 	ssize_t nread = fread(p, 1, left, f);
48 
49 	if (nread <= 0) {
50 	    fprintf(stderr, "atomic_fread() failed: %s\n", strerror(errno));
51 	    return 0;
52 	}
53 
54 	left -= nread;
55 	p += nread;
56     }
57 
58     return count;
59 }
60 
61 
atomic_fwrite(FILE * f,void * src,size_t count)62 size_t atomic_fwrite(FILE *f, void *src, size_t count)
63 {
64     uint8_t *p = (uint8_t *) src;
65     size_t left = count;
66 
67     while (left > 0) {
68 	ssize_t nwrite = fwrite(p, 1, left, f);
69 	if (nwrite <= 0) {
70 	    fprintf(stderr, "atomic_fwrite() failed: %s\n", strerror(errno));
71 	    return 0;
72 	}
73 	left -= nwrite;
74 	p += nwrite;
75     }
76 
77     return count;
78 }
79 
80 
read_packed_data_to_memory(uint8_t ** buf,size_t * nbytes,uint8_t * header,FILE * in)81 static int read_packed_data_to_memory(uint8_t **buf, size_t *nbytes,
82 				      uint8_t *header, FILE *in)
83 {
84     if (in == stdin) {
85 	size_t bsize = 4096;
86 
87 	*buf = malloc(bsize);
88 	if (*buf == NULL)
89 	    return -1;
90 
91         /* header[] contains nbytes of data initially */
92 	memcpy(*buf, header, *nbytes);
93 
94 	while (1) {
95 	    size_t n;
96 
97 	    if (*nbytes == bsize) {
98 		uint8_t *newbuf;
99 
100 		bsize *= 2;
101 
102 		newbuf = realloc(*buf, bsize);
103 		if (newbuf == NULL)
104 		    return -1;
105 
106 		*buf = newbuf;
107 	    }
108 
109 	    n = fread(&(*buf)[*nbytes], 1, bsize - *nbytes, in);
110 
111 	    if (n <= 0)
112 		break;
113 
114 	    *nbytes += n;
115 	}
116 
117     } else {
118 
119 	fseek(in, 0, SEEK_END);
120 	*nbytes = ftell(in);
121 	fseek(in, 0, SEEK_SET);
122 
123 	*buf = malloc(*nbytes);
124 	if (*buf == NULL)
125 	    return -1;
126 
127 	if (atomic_fread(*buf, in, *nbytes) == 0)
128 	    return -1;
129     }
130 
131     return 0;
132 }
133 
134 
check_header(uint8_t b[16])135 static struct decruncher *check_header(uint8_t b[16])
136 {
137     struct decruncher *decruncher = NULL;
138 
139     if ((b[0] == 'P' && b[1] == 'X' && b[2] == '2' && b[3] == '0') ||
140 	(b[0] == 'P' && b[1] == 'P' && b[2] == '2' && b[3] == '0')) {
141 	decruncher = &decruncher_pp;
142 
143     } else if (b[0] == 'X' && b[1] == 'P' && b[2] == 'K' && b[3] == 'F' &&
144 	       b[8] == 'S' && b[9] == 'Q' && b[10] == 'S' && b[11] == 'H') {
145 	decruncher = &decruncher_sqsh;
146 
147     } else if (b[0] == 'z' && b[1] == 'i' && b[2] == 'R' && b[3] == 'C' &&
148 	       b[4] == 'O' && b[5] == 'N' && b[6] == 'i' && b[7] == 'a') {
149 	decruncher = &decruncher_mmcmp;
150 
151     } else if (b[0] == 'S' && b[1] == '4' && b[2] == '0' && b[3] == '4' &&
152 	       b[4] < 0x80 && b[8] < 0x80 && b[12] < 0x80) {
153 	decruncher = &decruncher_s404;
154     }
155 
156     return decruncher;
157 }
158 
159 
decrunch(FILE * out,const char * filename,int pretend)160 int decrunch(FILE *out, const char *filename, int pretend)
161 {
162     uint8_t b[16];
163     int res;
164     size_t nbytes;
165     FILE *in;
166     char tmpname[PATH_MAX] = "";
167     uint8_t *buf = NULL;
168     struct decruncher *decruncher;
169     int output_to_same_file = (out == NULL);
170 
171     if (filename) {
172 	in = fopen(filename, "rb");
173 	if (in == NULL) {
174 	    fprintf(stderr, "Unknown file %s\n", filename);
175 	    goto error;
176 	}
177     } else {
178 	in = stdin;
179     }
180 
181     nbytes = fread(b, 1, sizeof b, in);
182     if (nbytes < sizeof b)
183 	goto error;
184 
185     decruncher = check_header(b);
186     if (decruncher == NULL)
187 	goto error;
188 
189     if (filename)
190 	fprintf(stderr, "File %s is in %s format.\n", filename, decruncher->name);
191     else
192 	fprintf(stderr, "Stream is in %s format.\n", decruncher->name);
193 
194     if (pretend)
195       return 0;
196 
197     if (read_packed_data_to_memory(&buf, &nbytes, b, in))
198 	goto error;
199 
200     if (nbytes <= sizeof b)
201       goto error;
202 
203     if (output_to_same_file) {
204 	int fd;
205 
206 	snprintf(tmpname, sizeof tmpname, "%s.XXXXXX", filename);
207 
208 	fd = mkstemp(tmpname);
209 	if (fd < 0) {
210 	    tmpname[0] = 0;
211 	    fprintf(stderr, "Could not create a temporary file: %s (%s)\n", tmpname, strerror(errno));
212 	    goto error;
213 	}
214 
215 	out = fdopen(fd, "wb");
216 	if (out == NULL) {
217 	    fprintf(stderr, "Could not fdopen temporary file: %s\n", tmpname);
218 	    goto error;
219 	}
220     }
221 
222     res = decruncher->decrunch(buf, nbytes, out);
223 
224     if (res < 0)
225       goto error;
226 
227     free(buf);
228 
229     fclose(in);
230 
231     if (output_to_same_file) {
232 	fclose(out);
233 	out = NULL;
234 
235 	assert(tmpname[0] != 0);
236 
237 #ifdef RENAME_WORKAROUND
238 	/* Don't check return value of unlink(). This is for MinGW. */
239 	unlink(filename);
240 #endif
241 
242 	if (rename(tmpname, filename)) {
243 	    fprintf(stderr, "Rename error: %s -> %s (%s)\n", tmpname, filename, strerror(errno));
244 	    unlink(tmpname);
245 	}
246     }
247 
248     return 0;
249 
250  error:
251     fclose(in);
252 
253     if (tmpname[0]) {
254         if (out != NULL)
255             fclose(out);
256 
257 	unlink(tmpname);
258     }
259 
260     if (buf)
261 	free(buf);
262 
263     return -1;
264 }
265