]> git.sur5r.net Git - cc65/blob - samples/gunzip65.c
Return after errors, move left bracket consumption down
[cc65] / samples / gunzip65.c
1 /*
2 ** gunzip65 - a gunzip utility for 6502-based machines.
3 **
4 ** Piotr Fusik <fox@scene.pl>
5 **
6 ** This should be considered as a test of my zlib-compatible library
7 ** rather than a real application.
8 ** It's not user-friendly, fault-tolerant, whatever.
9 ** However, it really works for real GZIP files, provided they are small
10 ** enough to fit in buffer[] (after decompression!).
11 */
12
13 #include <stdio.h>
14 #include <string.h>
15 #include <zlib.h>
16
17 #ifdef __CC65__
18 #include <stdlib.h>
19 #include <cc65.h>
20 #endif
21
22 #ifndef __CC65__
23 /*
24 ** Emulate inflatemem() if using original zlib.
25 ** As you can see, this program is quite portable.
26 */
27 unsigned inflatemem(char* dest, const char* source)
28 {
29         z_stream stream;
30
31         stream.next_in = (Bytef*) source;
32         stream.avail_in = 65535;
33
34         stream.next_out = dest;
35         stream.avail_out = 65535;
36
37         stream.zalloc = (alloc_func) 0;
38         stream.zfree = (free_func) 0;
39
40         inflateInit2(&stream, -MAX_WBITS);
41         inflate(&stream, Z_FINISH);
42         inflateEnd(&stream);
43
44         return stream.total_out;
45 }
46 #endif /* __CC65__ */
47
48 /*
49 ** Structure of a GZIP file:
50 **
51 ** 1. GZIP header:
52 **    Offset 0: Signature (2 bytes: 0x1f, 0x8b)
53 **    Offset 2: Compression method (1 byte: 8 == "deflate")
54 **    Offset 3: Flags (1 byte: see below)
55 **    Offset 4: File date and time (4 bytes)
56 **    Offset 8: Extra flags (1 byte)
57 **    Offset 9: Target OS (1 byte: DOS, Amiga, Unix, etc.)
58 **    if (flags & FEXTRA) { 2 bytes of length, then length bytes }
59 **    if (flags & FNAME) { ASCIIZ filename }
60 **    if (flags & FCOMMENT) { ASCIIZ comment }
61 **    if (flags & FHCRC) { 2 bytes of CRC }
62 **
63 ** 2. Deflate compressed data.
64 **
65 ** 3. GZIP trailer:
66 **    Offset 0: CRC-32 (4 bytes)
67 **    Offset 4: uncompressed file length (4 bytes)
68 */
69
70 /* Flags in the GZIP header. */
71 #define FTEXT     1     /* Extra text */
72 #define FHCRC     2     /* Header CRC */
73 #define FEXTRA    4     /* Extra field */
74 #define FNAME     8     /* File name */
75 #define FCOMMENT 16     /* File comment */
76
77 /*
78 ** We read whole GZIP file into this buffer.
79 ** Then we use this buffer for the decompressed data.
80 */
81 static unsigned char buffer[26000];
82
83 /*
84 ** Get a 16-bit little-endian unsigned number, using unsigned char* p.
85 ** On many machines this could be (*(unsigned short*) p),
86 ** but I really like portability. :-)
87 */
88 #define GET_WORD(p) (*(p) + ((unsigned) (p)[1] << 8))
89
90 /* Likewise, for a 32-bit number. */
91 #define GET_LONG(p) (GET_WORD(p) + ((unsigned long) GET_WORD(p + 2) << 16))
92
93 /*
94 ** Uncompress a GZIP file.
95 ** On entry, buffer[] should contain the whole GZIP file contents,
96 ** and the argument complen should be equal to the length of the GZIP file.
97 ** On return, buffer[] contains the uncompressed data, and the returned
98 ** value is the length of the uncompressed data.
99 */
100 unsigned uncompress_buffer(unsigned complen)
101 {
102         unsigned char* ptr;
103         unsigned long crc;
104         unsigned long unclen;
105         void* ptr2;
106         unsigned unclen2;
107
108         /* check GZIP signature */
109         if (buffer[0] != 0x1f || buffer[1] != 0x8b) {
110                 puts("Not GZIP format");
111                 return 0;
112         }
113
114         /* check compression method (it is always (?) "deflate") */
115         if (buffer[2] != 8) {
116                 puts("Unsupported compression method");
117                 return 0;
118         }
119
120         /* get CRC from GZIP trailer */
121         crc = GET_LONG(buffer + complen - 8);
122
123         /* get uncompressed length from GZIP trailer */
124         unclen = GET_LONG(buffer + complen - 4);
125         if (unclen > sizeof(buffer)) {
126                 puts("Uncompressed size too big");
127                 return 0;
128         }
129
130         /* skip extra field, file name, comment and crc */
131         ptr = buffer + 10;
132         if (buffer[3] & FEXTRA)
133                 ptr = buffer + 12 + GET_WORD(buffer + 10);
134         if (buffer[3] & FNAME)
135                 while (*ptr++ != 0);
136         if (buffer[3] & FCOMMENT)
137                 while (*ptr++ != 0);
138         if (buffer[3] & FHCRC)
139                 ptr += 2;
140
141         /*
142         ** calculate length of raw "deflate" data
143         ** (without the GZIP header and 8-byte trailer)
144         */
145         complen -= (ptr - buffer) + 8;
146
147         /*
148         ** We will move the compressed data to the end of buffer[].
149         ** Thus the compressed data and the decompressed data (written from
150         ** the beginning of buffer[]) may overlap, as long as the decompressed
151         ** data doesn't go further than unread compressed data.
152         ** ptr2 points to the beginning of compressed data at the end
153         ** of buffer[].
154         */
155         ptr2 = buffer + sizeof(buffer) - complen;
156         /* move the compressed data to end of buffer[] */
157         memmove(ptr2, ptr, complen);
158
159         /* uncompress */
160         puts("Inflating...");
161         unclen2 = inflatemem(buffer, ptr2);
162
163         /* verify uncompressed length */
164         if (unclen2 != (unsigned) unclen) {
165                 puts("Uncompressed size does not match");
166                 return 0;
167         }
168
169         /* verify CRC */
170         puts("Calculating CRC...");
171         if (crc32(crc32(0L, Z_NULL, 0), buffer, unclen2) != crc) {
172                 puts("CRC mismatch");
173                 return 0;
174         }
175
176         /* return number of uncompressed bytes */
177         return unclen2;
178 }
179
180 /*
181 ** Get a filename from standard input.
182 */
183 char* get_fname(void)
184 {
185         static char filename[100];
186         unsigned len;
187         fgets(filename, sizeof(filename), stdin);
188         len = strlen(filename);
189         if (len >= 1 && filename[len - 1] == '\n')
190                 filename[len - 1] = '\0';
191         return filename;
192 }
193
194 int main(void)
195 {
196         FILE* fp;
197         unsigned length;
198
199 #ifdef __CC65__
200         /* allow user to read exit messages */
201         if (doesclrscrafterexit()) {
202                 atexit((void (*)) getchar);
203         }
204 #endif /* __CC65__ */
205
206         /* read GZIP file */
207         puts("GZIP file name:");
208         fp = fopen(get_fname(), "rb");
209         if (!fp) {
210                 puts("Can't open GZIP file");
211                 return 1;
212         }
213         length = fread(buffer, 1, sizeof(buffer), fp);
214         fclose(fp);
215         if (length == sizeof(buffer)) {
216                 puts("File is too long");
217                 return 1;
218         }
219
220         /* decompress */
221         length = uncompress_buffer(length);
222         if (length == 0)
223                 return 1;
224
225         /* write uncompressed file */
226         puts("Uncompressed file name:");
227         fp = fopen(get_fname(), "wb");
228         if (!fp) {
229                 puts("Can't create output file");
230                 return 1;
231         }
232         if (fwrite(buffer, 1, length, fp) != length) {
233                 puts("Error while writing output file");
234                 return 1;
235         }
236         fclose(fp);
237
238         puts("Ok.");
239         return 0;
240 }