Subversion Repositories ipe_artfile_utils

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
/**
2
 * ART file unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
3
 * Supports:
4
 * - Blown Away - The Interactive Game by Imagination Pilots
5
 * - Panic in the Park - The Interactive Game by Imagination Pilots
6
 * - Where's Waldo? At the Circus (Waldo1)
7
 * Revision: 2018-02-15
8
 **/
9
 
10
#include <stdio.h>
11
#include <stdlib.h>
12
#include <assert.h>
13
#include <string.h>
14
#include <getopt.h>
15
 
16
#include "ipe16_bmpexport.h"
17
#include "ipe16_artfile.h"
18
#include "ipe16_lzw_decoder.h"
19
 
20
#include "utils.h"
21
 
22
#define MAX_FILE 256
23
 
24
void ipe16_generate_gray_table(Ipe16ColorTable *ct) {
25
        int i;
26
        for (i=0; i<=0xFF; ++i) {
27
                ct->colors[i].r = i;
28
                ct->colors[i].g = i;
29
                ct->colors[i].b = i;
30
        }
31
}
32
 
33
bool ipe16_extract_art_to_folder(FILE* fibArt, const char* szDestFolder, const int verbosity) {
34
        bool bEverythingOK = true;
35
 
36
        fseek(fibArt, 0, SEEK_SET);
37
 
38
        Ipe16FileHeader bfh;
39
        if (fread(&bfh, sizeof(bfh), 1, fibArt) != 1) {
40
                fprintf(stderr, "FATAL: Cannot read Ipe16FileHeader. It is probably not an art file.\n");
41
                return false;
42
        }
43
 
44
        // The "super header" has some different meanings of the fields
45
        // Name and Type are hardcoded
46
        // startOffset is the number of header entries (including the super header)
47
        // length is the complete file size
48
        const size_t fileSize = file_size(fibArt);
49
        if ((strcmp(bfh.magic, IPE16_MAGIC_ART) != 0) || // better memcpy over all 23 bytes?
50
                (bfh.dummy != IPE16_MAGIC_DUMMY) ||
51
                (bfh.totalFileSize != fileSize)) {
52
                fprintf(stderr, "FATAL: Something does not seem to be correct with this art file's header. It is probably not an art file.\n");
53
                return false;
54
        }
55
 
56
        Ipe16LZWDecoder* lzwDecoder = NULL;
57
 
58
        FILE* fotIndex = NULL;
59
        if (strlen(szDestFolder) > 0) {
60
                char szIndexFilename[MAX_FILE];
61
                sprintf(szIndexFilename, "%s/index.txt", szDestFolder);
62
                fotIndex = fopen(szIndexFilename, "wt");
63
                if (!fotIndex) {
64
                        fprintf(stderr, "FATAL: Cannot open %s for writing\n", szIndexFilename);
65
                        return false;
66
                }
67
        }
68
 
69
        const int numPictures = bfh.numHeaderEntries - 1;
70
        char knownNames[numPictures][IPE16_NAME_SIZE];
71
        memset(&knownNames[0][0], 0, numPictures*IPE16_NAME_SIZE);
72
        int iPicNo;
73
        for (iPicNo=0; iPicNo<numPictures; ++iPicNo) {
74
                Ipe16PictureEntryHeader peh;
75
                if (fread(&peh, sizeof(peh), 1, fibArt) != 1) {
76
                        fprintf(stderr, "FATAL: Cannot read Ipe16PictureEntryHeader.\n");
77
                        return false;
78
                }
79
 
80
                // Begin duplicate check
81
                memcpy(&knownNames[iPicNo][0], peh.name, IPE16_NAME_SIZE);
82
                int iCopyNumber = 0;
83
                int j;
84
                for (j=0; j<=iPicNo; ++j) {
85
                        // TODO: should we rather use strcmp() in IPE16?
86
                        if (memcmp(&knownNames[j][0], peh.name, IPE16_NAME_SIZE) == 0) ++iCopyNumber;
87
                }
88
                assert(iCopyNumber > 0);
89
                // End duplicate check
90
 
91
                // in the English version of Blown Away (not the Special Edition), there is a header entry
92
                // with the fields seh.name='', peh.paletteType=0x00, seh.offset[end of file], seh.size=0
93
                // ignore it
94
                if (strlen(peh.name) == 0) continue;
95
                if (peh.size == 0) continue;
96
 
97
                if (strlen(peh.name) > IPE16_NAME_SIZE-1) {
98
                        fprintf(stderr, "FATAL: szName at picture %d is breaking the boundaries. The file is probably corrupt.\n", iPicNo);
99
                        return false;
100
                }
101
 
102
                size_t headersPos = ftell(fibArt);
103
                #define FAIL_CONTINUE { bEverythingOK = false; fseek(fibArt, headersPos, SEEK_SET); continue; }
104
 
105
                if (fseek(fibArt, peh.offset, SEEK_SET) != 0) {
106
                        fprintf(stderr, "ERROR: Error jumping to offset defined for %s\n", peh.name);
107
                        FAIL_CONTINUE;
108
                }
109
 
110
                if (peh.offset+peh.size > fileSize) {
111
                        fprintf(stderr, "ERROR: Defined size of %s exceeds file size\n", peh.name);
112
                        FAIL_CONTINUE;
113
                }
114
 
115
                unsigned char compressionType;
116
                int bakPos = ftell(fibArt);
117
                fread(&compressionType, sizeof(compressionType), 1, fibArt);
118
                fseek(fibArt, bakPos, SEEK_SET);
119
 
120
                if ((compressionType == BA_COMPRESSIONTYPE_LZW) || (compressionType == BA_COMPRESSIONTYPE_NONE)) {
121
                        BAPictureHeader ph;
122
                        if (fread(&ph, sizeof(ph), 1, fibArt) != 1) {
123
                                fprintf(stderr, "ERROR: Cannot read BAPictureHeader of %s\n", peh.name);
124
                                FAIL_CONTINUE;
125
                        }
126
 
127
                        size_t imagedata_len = ph.width * ph.height;
128
                        unsigned char* imagedata = (unsigned char*)malloc(imagedata_len);
129
 
130
                        Ipe16ColorTable ct;
131
                        if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) {
132
                                if ((fseek(fibArt, peh.offset+peh.size-sizeof(ct), SEEK_SET) != 0) ||
133
                                    (fread(&ct, sizeof(ct), 1, fibArt) != 1) ||
134
                                    (fseek(fibArt, peh.offset+sizeof(ph), SEEK_SET) != 0)) {
135
                                        fprintf(stderr, "ERROR: Cannot read palette of %s\n", peh.name);
136
                                        FAIL_CONTINUE;
137
                                }
138
                        } else if (peh.paletteType == IPE16_PALETTETYPE_PARENT) {
139
                                ipe16_generate_gray_table(&ct);
140
                        } else {
141
                                fprintf(stderr, "ERROR: Unknown palette type 0x%x at %s\n", peh.paletteType, peh.name);
142
                                FAIL_CONTINUE;
143
                        }
144
 
145
                        unsigned int bytes_written, expected_uncompressed_len;
146
                        switch (ph.compressionType) {
147
                                case BA_COMPRESSIONTYPE_LZW:
148
                                        if (!lzwDecoder) lzwDecoder = new_ipe16lzw_decoder();
149
                                        bytes_written = ipe16lzw_decode(fibArt, lzwDecoder, imagedata, imagedata_len);
150
                                        if (bytes_written < 0) {
151
                                                fprintf(stderr, "ERROR: LZW decompression error at %s\n", peh.name);
152
                                                FAIL_CONTINUE;
153
                                        }
154
                                        if (bytes_written != imagedata_len) {
155
                                                fprintf(stderr, "ERROR: Image dimensions and decompressed data size does not match for %s\n", peh.name);
156
                                                FAIL_CONTINUE;
157
                                        }
158
 
159
                                        break;
160
                                case BA_COMPRESSIONTYPE_NONE:
161
                                        expected_uncompressed_len = imagedata_len;
162
                                        if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) expected_uncompressed_len += sizeof(ct);
163
                                        expected_uncompressed_len += sizeof(ph.compressionType)+sizeof(ph.width)+sizeof(ph.height);
164
                                        if (expected_uncompressed_len != peh.size) {
165
                                                fprintf(stderr, "ERROR: Image dimensions/palette (%d) and defined memory size (%d) does not match for %s\n", expected_uncompressed_len, peh.size, peh.name);
166
                                                FAIL_CONTINUE;
167
                                        }
168
                                        fread(imagedata, imagedata_len, 1, fibArt); // no error checking, because filesize was already checked
169
                                        break;
170
                        }
171
 
172
                        char szBitmapFilename[MAX_FILE];
173
                        if (iCopyNumber == 1) {
174
                                sprintf(szBitmapFilename, "%s.bmp", sanitize_filename(peh.name));
175
                        } else {
176
                                sprintf(szBitmapFilename, "%s__%d.bmp", sanitize_filename(peh.name), iCopyNumber);
177
                        }
178
 
179
                        if (strlen(szDestFolder) > 0) {
5 daniel-mar 180
                                char szAbsoluteBitmapFilename[MAX_FILE+1];
2 daniel-mar 181
                                sprintf(szAbsoluteBitmapFilename, "%s/%s", szDestFolder, szBitmapFilename);
182
                                FILE* fobBitmap = fopen(szAbsoluteBitmapFilename, "wb");
183
                                if (!fobBitmap) {
184
                                        fprintf(stderr, "FATAL: Cannot open %s for writing\n", szAbsoluteBitmapFilename);
185
                                        FAIL_CONTINUE;
186
                                }
187
                                ipe16_write_bmp(fobBitmap, ph.width, ph.height, imagedata, imagedata_len, ct);
188
                                fclose(fobBitmap);
189
                        }
190
 
191
                        if (fotIndex) {
192
                                // We require this index file for 2 reasons
193
                                // 1. Our packer tool can then know what to pack
194
                                // 2. The packer tool can know which picture would have a palette appended and which one does not
195
                                // The index file won't be written in simulation mode (when no output directory is defined)
196
                                fprintf(fotIndex, "%c %c %s %s\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename);
197
                        }
198
                        if (verbosity >= 1) {
199
                                fprintf(stdout, "%c %c %s %s\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename);
200
                        }
201
 
202
                        free(imagedata);
203
                } else if ((compressionType == PIP_COMPRESSIONTYPE_LZW) || (compressionType == PIP_COMPRESSIONTYPE_NONE)) {
204
                        PipPictureHeader ph;
205
                        if (fread(&ph, sizeof(ph), 1, fibArt) != 1) {
206
                                fprintf(stderr, "ERROR: Cannot read PipPictureHeader of %s\n", peh.name);
207
                                FAIL_CONTINUE;
208
                        }
209
 
210
                        size_t imagedata_len = ph.width * ph.height;
211
                        unsigned char* imagedata = (unsigned char*)malloc(imagedata_len);
212
 
213
                        Ipe16ColorTable ct;
214
                        if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) {
215
                                if ((fseek(fibArt, peh.offset+peh.size-sizeof(ct), SEEK_SET) != 0) ||
216
                                    (fread(&ct, sizeof(ct), 1, fibArt) != 1) ||
217
                                    (fseek(fibArt, peh.offset+sizeof(ph), SEEK_SET) != 0)) {
218
                                        fprintf(stderr, "ERROR: Cannot read palette of %s\n", peh.name);
219
                                        FAIL_CONTINUE;
220
                                }
221
                        } else if (peh.paletteType == IPE16_PALETTETYPE_PARENT) {
222
                                ipe16_generate_gray_table(&ct);
223
                        } else {
224
                                fprintf(stderr, "ERROR: Unknown palette type 0x%x at %s\n", peh.paletteType, peh.name);
225
                                FAIL_CONTINUE;
226
                        }
227
 
228
                        unsigned int bytes_written, expected_uncompressed_len;
229
                        switch (ph.compressionType) {
230
                                case PIP_COMPRESSIONTYPE_LZW:
231
                                        if (!lzwDecoder) lzwDecoder = new_ipe16lzw_decoder();
232
                                        bytes_written = ipe16lzw_decode(fibArt, lzwDecoder, imagedata, imagedata_len);
233
                                        if (bytes_written < 0) {
234
                                                fprintf(stderr, "ERROR: LZW decompression error at %s\n", peh.name);
235
                                                FAIL_CONTINUE;
236
                                        }
237
                                        if (bytes_written != imagedata_len) {
238
                                                fprintf(stderr, "ERROR: Image dimensions and decompressed data size does not match for %s\n", peh.name);
239
                                                FAIL_CONTINUE;
240
                                        }
241
 
242
                                        break;
243
                                case PIP_COMPRESSIONTYPE_NONE:
244
                                        expected_uncompressed_len = imagedata_len;
245
                                        if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) expected_uncompressed_len += sizeof(ct);
246
                                        expected_uncompressed_len += sizeof(ph.compressionType)+sizeof(ph.offsetX)+sizeof(ph.offsetY)+sizeof(ph.width)+sizeof(ph.height);
247
                                        if (expected_uncompressed_len != peh.size) {
248
                                                fprintf(stderr, "ERROR: Image dimensions/palette (%d) and defined memory size (%d) does not match for %s\n", expected_uncompressed_len, peh.size, peh.name);
249
                                                FAIL_CONTINUE;
250
                                        }
251
                                        fread(imagedata, imagedata_len, 1, fibArt); // no error checking, because filesize was already checked
252
                                        break;
253
                        }
254
 
255
                        char szBitmapFilename[MAX_FILE];
256
                        if (iCopyNumber == 1) {
257
                                sprintf(szBitmapFilename, "%s.bmp", sanitize_filename(peh.name));
258
                        } else {
259
                                sprintf(szBitmapFilename, "%s__%d.bmp", sanitize_filename(peh.name), iCopyNumber);
260
                        }
261
 
262
                        if (strlen(szDestFolder) > 0) {
5 daniel-mar 263
                                char szAbsoluteBitmapFilename[MAX_FILE+1];
2 daniel-mar 264
                                sprintf(szAbsoluteBitmapFilename, "%s/%s", szDestFolder, szBitmapFilename);
265
                                FILE* fobBitmap = fopen(szAbsoluteBitmapFilename, "wb");
266
                                if (!fobBitmap) {
267
                                        fprintf(stderr, "FATAL: Cannot open %s for writing\n", szAbsoluteBitmapFilename);
268
                                        FAIL_CONTINUE;
269
                                }
270
                                ipe16_write_bmp(fobBitmap, ph.width, ph.height, imagedata, imagedata_len, ct);
271
                                fclose(fobBitmap);
272
                        }
273
 
274
                        if (fotIndex) {
275
                                // We require this index file for 2 reasons
276
                                // 1. Our packer tool can then know what to pack
277
                                // 2. The packer tool can know which picture would have a palette appended and which one does not
278
                                // The index file won't be written in simulation mode (when no output directory is defined)
279
                                fprintf(fotIndex, "%c %c %s %s %d %d\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename, ph.offsetX, ph.offsetY);
280
                        }
281
                        if (verbosity >= 1) {
282
                                fprintf(stdout, "%c %c %s %s %d %d\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename, ph.offsetX, ph.offsetY);
283
                        }
284
 
285
                        free(imagedata);
286
                } else {
287
                        fprintf(stderr, "ERROR: Unknown compression type 0x%x at %s\n", compressionType, peh.name);
288
                        FAIL_CONTINUE;
289
                }
290
 
291
                fseek(fibArt, headersPos, SEEK_SET); // we were already there, so we don't need to check for errors
292
        }
293
 
294
        if (lzwDecoder) del_ipe16lzw_decoder(lzwDecoder);
295
 
296
        if (strlen(szDestFolder) > 0) fclose(fotIndex);
297
 
298
        return bEverythingOK;
299
}