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