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
 * - Where's Waldo? Exploring Geography
5
 * - Eraser Turnabout by Imagination Pilots
6
 * - Virtual K'Nex by Imagination Pilots
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 "ipe32_bmpexport.h"
17
#include "ipe32_artfile.h"
18
#include "ipe32_lzw_decoder.h"
19
 
20
#include "utils.h"
21
 
22
#define MAX_FILE 256
23
 
24
typedef struct tagIpe32ReadPictureResult {
25
        uint32_t   writtenBytes;
26
        uint32_t   numCompressedChunks;
27
        uint32_t   numRawChunks;
28
} Ipe32ReadPictureResult;
29
 
30
Ipe32ReadPictureResult ipe32_read_picture(FILE* hFile, unsigned char* outbuf, const int outputBufLength, bool bVerbose) {
31
        unsigned char* lzwbuf = (unsigned char*)malloc(0x8000);
32
        int availableOutputBytes = outputBufLength;
33
 
34
        Ipe32ReadPictureResult res;
35
        res.numCompressedChunks = 0;
36
        res.numRawChunks = 0;
37
        res.writtenBytes = 0;
38
 
39
        Ipe32LZWDecoder *decoder = new_ipe32lzw_decoder();
40
        ipe32lzw_init_decoder(decoder);
41
        if (outputBufLength != 0) {
42
                int chunkNo = 0;
43
                do {
44
                        uint16_t len;
45
                        fread(&len, 1, 2, hFile);
46
 
47
                        uint16_t writtenBytes;
48
                        if (len < 0x8000) {
49
                                fread(lzwbuf, 1, len, hFile);
50
                                res.numCompressedChunks++;
51
                                if (bVerbose) fprintf(stdout, "Chunk %d (compressed, length: %d) ...\n", chunkNo, len);
52
 
53
                                // Requirement 1: Each chunk (except the last one) MUST have 0x3FFE of uncompressed data
54
                                uint16_t expectedOutputSize = availableOutputBytes > 0x3FFE ? 0x3FFE : availableOutputBytes;
55
 
56
                                // Requirement 2: The size of the uncompressed data must not exceed the size of the compressed data
57
                                size_t maxReadBytes = expectedOutputSize;
58
 
59
                                writtenBytes = ipe32lzw_decode(decoder, outbuf, outputBufLength, lzwbuf, maxReadBytes); // returns bytes written, or -1
60
 
61
                                if (writtenBytes == -1) {
62
                                        fprintf(stderr, "ERROR: Fatal error during decompression of chunk %d!\n", chunkNo);
63
                                        break;
64
                                }
65
 
66
                                if (writtenBytes != expectedOutputSize) {
67
                                        fprintf(stderr, "ERROR: Chunk %d decompressed %d bytes, but %d bytes are expected!\n", chunkNo, writtenBytes, expectedOutputSize);
68
                                        break;
69
                                }
70
                        } else {
71
                                len &= 0x7FFF;
72
                                res.numRawChunks++;
73
                                if (bVerbose) fprintf(stdout, "Chunk %d (raw, length: %d) ...\n", chunkNo, len);
74
                                fread(outbuf, 1, len, hFile);
75
                                writtenBytes = len;
76
                        }
77
                        outbuf += writtenBytes;
78
                        availableOutputBytes -= writtenBytes;
79
                        chunkNo++;
80
                } while (availableOutputBytes != 0);
81
        }
82
        ipe32lzw_free_decoder(decoder);
83
 
84
        free(lzwbuf);
85
        res.writtenBytes = outputBufLength-availableOutputBytes;
86
        return res;
87
}
88
 
89
bool ipe32_extract_art_to_folder(FILE* fibArt, const char* szDestFolder, const int verbosity) {
90
        bool bEverythingOK = true;
91
 
92
        fseek(fibArt, 0, SEEK_SET);
93
 
94
        Ipe32FileHeader efh;
95
        if (fread(&efh, sizeof(efh), 1, fibArt) != 1) {
96
                fprintf(stderr, "FATAL: Cannot read Ipe32FileHeader. It is probably not an art file.\n");
97
                return false;
98
        }
99
 
100
        // Check if the super header is correct
101
        if ((memcmp(efh.magic, IPE32_MAGIC_ART, 8) != 0) || (efh.reserved != 0)) {
102
                fprintf(stderr, "FATAL: Something does not seem to be correct with this art file's header. It is probably not an art file.\n");
103
                return false;
104
        }
105
 
106
        FILE* fotIndex = NULL;
107
        if (strlen(szDestFolder) > 0) {
108
                char szIndexFilename[MAX_FILE];
109
                sprintf(szIndexFilename, "%s/index.txt", szDestFolder);
110
                fotIndex = fopen(szIndexFilename, "wt");
111
                if (!fotIndex) {
112
                        fprintf(stderr, "FATAL: Cannot open %s for writing\n", szIndexFilename);
113
                        return false;
114
                }
115
        }
116
 
117
        const int numPictures = efh.totalHeaderSize/sizeof(efh) - 1;
118
        char knownNames[numPictures][IPE32_NAME_SIZE];
119
        memset(knownNames, 0, numPictures*IPE32_NAME_SIZE);
120
        int iPicNo;
121
        for (iPicNo=0; iPicNo<numPictures; ++iPicNo) {
122
                Ipe32PictureEntryHeader peh;
123
                if (fread(&peh, sizeof(peh), 1, fibArt) != 1) {
124
                        fprintf(stderr, "FATAL: Cannot read Ipe32PictureEntryHeader.\n");
125
                        return false;
126
                }
127
 
128
                char szName[IPE32_NAME_SIZE+1]={0};
129
                memcpy(szName, peh.name, IPE32_NAME_SIZE);
130
 
131
                // Begin duplicate check
132
                // In ERASER, there are a few pictures which have the same identifier, in the same ART file!
133
                memcpy(&knownNames[iPicNo][0], peh.name, IPE32_NAME_SIZE);
134
                int iCopyNumber = 0;
135
                int j;
136
                for (j=0; j<=iPicNo; ++j) {
137
                        if (memcmp(&knownNames[j][0], peh.name, IPE32_NAME_SIZE) == 0) ++iCopyNumber;
138
                }
139
                assert(iCopyNumber > 0);
140
                // End duplicate check
141
 
142
                if (verbosity >= 2) fprintf(stdout, "Extracting %s (expected file size: %d bytes) ...\n", szName, peh.uncompressedSize);
143
 
144
                size_t headersPos = ftell(fibArt);
145
                #define FAIL_CONTINUE { bEverythingOK = false; fseek(fibArt, headersPos, SEEK_SET); continue; }
146
 
147
                if (fseek(fibArt, peh.offset, SEEK_SET) != 0) {
148
                        fprintf(stderr, "ERROR: Error jumping to offset defined for %s\n", szName);
149
                        FAIL_CONTINUE;
150
                }
151
 
152
                int outputBufLen = peh.uncompressedSize;
153
                unsigned char* outputBuf = (unsigned char*)malloc(outputBufLen);
154
 
155
                char szBitmapFilename[MAX_FILE];
156
                if (iCopyNumber == 1) {
157
                        sprintf(szBitmapFilename, "%s.bmp", sanitize_filename(szName));
158
                } else {
159
                        sprintf(szBitmapFilename, "%s__%d.bmp", sanitize_filename(szName), iCopyNumber);
160
                }
161
 
162
                Ipe32ReadPictureResult res = ipe32_read_picture(fibArt, outputBuf, outputBufLen, verbosity >= 2);
163
                if (res.writtenBytes != outputBufLen) {
164
                        fprintf(stderr, "FATAL: Error reading picture %s (compression failure?)\n", szName);
165
                        FAIL_CONTINUE;
166
                }
167
 
168
                if (strlen(szDestFolder) > 0) {
5 daniel-mar 169
                        char szAbsoluteBitmapFilename[MAX_FILE+1];
2 daniel-mar 170
                        sprintf(szAbsoluteBitmapFilename, "%s/%s", szDestFolder, szBitmapFilename);
171
                        FILE* fobBitmap = fopen(szAbsoluteBitmapFilename, "wb");
172
                        if (!fobBitmap) {
173
                                fprintf(stderr, "FATAL: Cannot open %s for writing\n", szAbsoluteBitmapFilename);
174
                                FAIL_CONTINUE;
175
                        }
176
                        ipe32_write_bmp(fobBitmap, outputBuf, outputBufLen);
177
                        fclose(fobBitmap);
178
                }
179
 
180
                free(outputBuf);
181
 
182
                if (fotIndex) {
183
                        // We require this index file so that our packer tool can know what to pack
184
                        // The index file won't be written in simulation mode (when no output directory is defined)
185
                        fprintf(fotIndex, "%s %d(C) %d(R) %s\n", szName, res.numCompressedChunks, res.numRawChunks, szBitmapFilename);
186
                }
187
                if (verbosity >= 1) {
188
                        fprintf(stdout, "%s %d(C) %d(R) %s\n", szName, res.numCompressedChunks, res.numRawChunks, szBitmapFilename);
189
                }
190
 
191
                fseek(fibArt, headersPos, SEEK_SET); // we were already there, so we don't need to check for errors
192
        }
193
 
194
        if (strlen(szDestFolder) > 0) fclose(fotIndex);
195
 
196
        return bEverythingOK;
197
}