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 | } |