1 /* ScummVM Tools
2 *
3 * ScummVM Tools is the legal property of its developers, whose
4 * names are too numerous to list here. Please refer to the
5 * COPYRIGHT file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 /* Extractor for Coktel Vision game's .stk/.itk archives */
23
24 #include <string.h>
25 #include <stdio.h>
26
27 #include "extract_gob_stk.h"
28 #include "common/endian.h"
29
30 #define confSTK10 "STK10"
31 #define confSTK21 "STK21"
32
33 struct ExtractGobStk::Chunk {
34 char name[64];
35 uint32 size, offset;
36 bool packed;
37 bool preGob;
38
39 Chunk *next;
40
ChunkExtractGobStk::Chunk41 Chunk() : next(0) { }
~ChunkExtractGobStk::Chunk42 ~Chunk() { delete next; }
43 };
44
ExtractGobStk(const std::string & name)45 ExtractGobStk::ExtractGobStk(const std::string &name) : Tool(name, TOOLTYPE_EXTRACTION) {
46 _chunks = NULL;
47
48 ToolInput input;
49 input.format = "*.*";
50 _inputPaths.push_back(input);
51
52 _shorthelp = "Extract the files from a Stick file used by 'gob' engine (.STK/.ITK/.LTK).";
53 _helptext = "Usage: " + getName() + " [-o outputname] stickname\nwhere\n ouputname is used to force the gob config filename (used by compress_gob)\n stickname is the name of the file to extract/decompress";
54 }
55
~ExtractGobStk()56 ExtractGobStk::~ExtractGobStk() {
57 delete _chunks;
58 }
59
inspectInput(const Common::Filename & filename)60 InspectionMatch ExtractGobStk::inspectInput(const Common::Filename &filename) {
61 // Accept either any file with stk, itk or ltk extension
62 std::string ext = filename.getExtension();
63 if (
64 scumm_stricmp(ext.c_str(), "stk") == 0 ||
65 scumm_stricmp(ext.c_str(), "itk") == 0 ||
66 scumm_stricmp(ext.c_str(), "ltk") == 0
67 )
68 return IMATCH_PERFECT;
69 return IMATCH_AWFUL;
70 }
71
execute()72 void ExtractGobStk::execute() {
73 char signature[7];
74 Common::File stk;
75 Common::File gobConf;
76
77 Common::File f1;
78 Common::File f2;
79
80 Common::Filename inpath(_inputPaths[0].path);
81
82 stk.open(inpath.getFullPath(), "rb");
83
84 if (_outputPath.empty()) {
85 _outputPath = inpath;
86 _outputPath.setExtension("");
87 _outputPath.setFullName(_outputPath.getFullName() + inpath.getExtension());
88 }
89
90 if (_outputPath.directory()) {
91 _outputPath.setFullName(inpath.getFullName());
92 _outputPath.setExtension("");
93 _outputPath.setFullName(_outputPath.getFullName() + inpath.getExtension());
94 }
95
96 _outputPath.setExtension(".gob");
97
98 gobConf.open(_outputPath.getFullPath(), "w");
99 gobConf.print("%s\n", inpath.getFullName().c_str());
100
101 stk.read_throwsOnError(signature, 6);
102
103 if (strncmp(signature, "STK2.1", 6) == 0) {
104 print("Signature of new STK format (STK 2.1) detected in file \"%s\"", inpath.getFullPath().c_str());
105 gobConf.print("%s\n", confSTK21);
106 readChunkListV2(stk, gobConf);
107 } else {
108 gobConf.print("%s\n", confSTK10);
109 stk.rewind();
110 readChunkList(stk, gobConf);
111 }
112
113 print("config file created: %s", _outputPath.getFullPath().c_str());
114
115 extractChunks(_outputPath, stk);
116 }
117
readChunkList(Common::File & stk,Common::File & gobConf)118 void ExtractGobStk::readChunkList(Common::File &stk, Common::File &gobConf) {
119 uint16 numDataChunks = stk.readUint16LE();
120
121 // If we are run multiple times, free previous chunk list
122 delete _chunks;
123 _chunks = new Chunk;
124 Chunk *curChunk = _chunks;
125 char *fakeTotPtr;
126
127 while (numDataChunks-- > 0) {
128 stk.read_throwsOnError(curChunk->name, 13);
129
130 curChunk->size = stk.readUint32LE();
131 curChunk->offset = stk.readUint32LE();
132 curChunk->packed = stk.readByte() != 0;
133 curChunk->preGob = false;
134
135 // Geisha TOTs are compressed without having the flag set
136 fakeTotPtr = strstr(curChunk->name, "0OT");
137 if (fakeTotPtr != 0) {
138 strncpy(fakeTotPtr, "TOT", 3);
139 curChunk->packed = true;
140 curChunk->preGob = true;
141 }
142
143 // Write the chunk info in the gob Conf file
144 gobConf.print("%s %d\n", curChunk->name, curChunk->packed ? 1 : 0);
145
146 if (numDataChunks > 0) {
147 curChunk->next = new Chunk;
148 curChunk = curChunk->next;
149 }
150 }
151 }
152
readChunkListV2(Common::File & stk,Common::File & gobConf)153 void ExtractGobStk::readChunkListV2(Common::File &stk, Common::File &gobConf) {
154 uint32 numDataChunks;
155 _chunks = new Chunk;
156 Chunk *curChunk = _chunks;
157
158 // char *fakeTotPtr;
159
160 int cpt = 0;
161 char buffer[64];
162 char debugStr[256];
163 uint32 filenamePos;
164 uint32 miscPos;
165 uint32 filePos;
166 uint32 compressFlag;
167 uint32 decompSize;
168
169 // Header (Signature already read)
170 // ======
171 // Structure of header is :
172 // + 06 bytes : Signature
173 // + 14 bytes : Date time of STK/ITK creation (format DDMMYYYYHH24MISS)
174 // + 08 bytes : Name / acronym of STK/ITK creator
175 // + 04 bytes : Start position of Filenames Section
176
177 stk.read_throwsOnError(buffer, 14);
178
179 buffer[14] = '\0';
180 sprintf(debugStr, "File generated on %s by ", buffer);
181
182 stk.read_throwsOnError(buffer, 8);
183
184 buffer[8] = '\0';
185 strcat(debugStr, buffer);
186 print("%s",debugStr);
187 filenamePos = stk.readUint32LE();
188
189 // Filenames - Header
190 // ==================
191 // Structure of the header of Common::Filename section is :
192 // + 04 bytes : Number of files stored in STK/ITK
193 // + 04 bytes : Start position of Misc Section
194
195 stk.seek(filenamePos, SEEK_SET);
196
197 numDataChunks = stk.readUint32LE();
198 miscPos = stk.readUint32LE();
199
200 if (numDataChunks == 0)
201 throw ToolException("Empty ITK/STK !");
202
203 while (numDataChunks-- > 0) {
204 // Misc
205 // ====
206 // This section contains Misc infos concerning the files.
207 // For each file, the info is the following :
208 // + 04 bytes : Start position of the filename
209 // + 14 bytes : Date time of the file last modification (format DDMMYYYYHH24MISS)
210 // + 14 bytes : Date time of the file creation (format DDMMYYYYHH24MISS)
211 // + 08 bytes : Name / acronym of STK/ITK creator
212 // + 04 bytes : File section size
213 // + 04 bytes : Uncompressed file size (redondant with info in File Section)
214 // TODO : Understand the use of the unknown bytes !
215 // + 05 bytes : Unknown
216 // + 04 bytes : Start position of the File Section
217 // + 04 bytes : Compression flag (AFAIK : 0= uncompressed, 1= compressed)
218
219 stk.seek(miscPos + (cpt * 61), SEEK_SET);
220 filenamePos = stk.readUint32LE();
221
222 stk.read_throwsOnError(buffer, 36);
223 curChunk->size = stk.readUint32LE();
224 decompSize = stk.readUint32LE();
225
226 stk.read_throwsOnError(buffer, 5);
227
228 filePos = stk.readUint32LE();
229 compressFlag = stk.readUint32LE();
230
231 if (compressFlag == 1) {
232 curChunk->packed = true;
233 } else {
234 if ((curChunk->size != decompSize) | (compressFlag != 0)) {
235 sprintf(debugStr,
236 "Unexpected value in compress flag : %d - Size : %d Uncompressed size : %d",
237 compressFlag, curChunk->size, decompSize);
238 throw ToolException(debugStr);
239 } else {
240 curChunk->packed=false;
241 }
242 }
243
244 // Filenames
245 // =========
246 // Common::Filename are stored one after the other, separated by 0x00.
247 // Those are now long filenames, at the opposite of previous STK version.
248
249 stk.seek(filenamePos, SEEK_SET);
250
251 strcpy(curChunk->name, stk.readString().c_str());
252
253 // Files
254 // =====
255 // The structure of the file section if the following :
256 // + 04 bytes : Uncompressed size (redondant with the one in Misc info)
257 // + ?? bytes : Compressed data
258
259 curChunk->offset = filePos;
260 curChunk->preGob = false;
261
262 // Write the chunk info in the gob Conf file
263 gobConf.print("%s %d\n", curChunk->name, curChunk->packed ? 1 : 0);
264
265 if (numDataChunks > 0) {
266 curChunk->next = new Chunk;
267 curChunk = curChunk->next;
268 }
269 cpt++;
270 }
271 }
272
extractChunks(Common::Filename & outpath,Common::File & stk)273 void ExtractGobStk::extractChunks(Common::Filename &outpath, Common::File &stk) {
274 Chunk *curChunk = _chunks;
275 byte *unpackedData = NULL;
276
277 while (curChunk != 0) {
278 print("Extracting \"%s\"", curChunk->name);
279
280 outpath.setFullName(curChunk->name);
281 Common::File chunkFile(outpath, "wb");
282
283 if (curChunk->size > 0) {
284 stk.seek(curChunk->offset, SEEK_SET);
285
286 byte *data = new byte[curChunk->size];
287
288 stk.read_throwsOnError(data, curChunk->size);
289
290 try {
291 if (curChunk->packed) {
292 uint32 realSize;
293
294 if (curChunk->preGob) {
295 unpackedData = unpackPreGobData(data, realSize, curChunk->size);
296 } else {
297 unpackedData = unpackData(data, realSize);
298 }
299
300 chunkFile.write(unpackedData, realSize);
301
302 delete[] unpackedData;
303 } else {
304 chunkFile.write(data, curChunk->size);
305 }
306 } catch(...) {
307 delete[] data;
308 delete[] unpackedData;
309 throw;
310 }
311 delete[] data;
312 }
313 curChunk = curChunk->next;
314 }
315 }
316
317 // Some LZ77-variant
unpackData(byte * src,uint32 & size)318 byte *ExtractGobStk::unpackData(byte *src, uint32 &size) {
319 uint32 counter;
320 uint16 cmd;
321 byte tmpBuf[4114];
322 int16 off;
323 byte len;
324 uint16 tmpIndex;
325
326 counter = size = READ_LE_UINT32(src);
327
328 for (int i = 0; i < 4078; i++)
329 tmpBuf[i] = 0x20;
330 tmpIndex = 4078;
331
332 src += 4;
333
334 byte *unpacked = new byte[size];
335 byte *dest = unpacked;
336
337 cmd = 0;
338 while (1) {
339 cmd >>= 1;
340 if ((cmd & 0x0100) == 0) {
341 cmd = *src | 0xFF00;
342 src++;
343 }
344 if ((cmd & 1) != 0) { /* copy */
345 *dest++ = *src;
346 tmpBuf[tmpIndex] = *src;
347 src++;
348 tmpIndex++;
349 tmpIndex %= 4096;
350 counter--;
351
352 if (counter == 0)
353 break;
354 } else { /* copy string */
355 off = *src++;
356 off |= (*src & 0xF0) << 4;
357 len = (*src & 0x0F) + 3;
358 src++;
359
360 for (int i = 0; i < len; i++) {
361 *dest++ = tmpBuf[(off + i) % 4096];
362 if (--counter == 0)
363 return unpacked;
364
365 tmpBuf[tmpIndex] = tmpBuf[(off + i) % 4096];
366 tmpIndex++;
367 tmpIndex %= 4096;
368 }
369 }
370 }
371
372 return unpacked;
373 }
374
375 // Some LZ77-variant
unpackPreGobData(byte * src,uint32 & size,uint32 & compSize)376 byte *ExtractGobStk::unpackPreGobData(byte *src, uint32 &size, uint32 &compSize) {
377 uint16 cmd;
378 byte tmpBuf[4114];
379 int16 off;
380 byte len;
381 uint16 tmpIndex;
382 uint32 dummy1;
383 int32 newCounter;
384
385 newCounter = compSize;
386 size = 0;
387
388 dummy1 = READ_LE_UINT16(src);
389 src += 2;
390 newCounter -= 2;
391
392 // The 6 first bytes are grouped by 2 :
393 // - bytes 0&1 : if set to 0xFFFF, the real size is in bytes 2&3. Else : unknown
394 // - bytes 2&3 : Either the real size or 0x007D. Directly related to the size of the file.
395 // - bytes 4&5 : 0x0000 (files are small) ;)
396 if (dummy1 == 0xFFFF)
397 print("Real size %d", READ_LE_UINT32(src));
398 else
399 print("Unknown real size %xX %xX", dummy1>>8, dummy1 & 0x00FF);
400
401 // counter = size = READ_LE_UINT32(src);
402
403 for (int i = 0; i < 4078; i++)
404 tmpBuf[i] = 0x20;
405 tmpIndex = 4078;
406
407 src += 4;
408 newCounter -= 4;
409
410 byte *unpacked = new byte[500000];//[size] Replaced by dummy as real size is not always known;
411 byte *dest = unpacked;
412
413 cmd = 0;
414 while (1) {
415 cmd >>= 1;
416 if ((cmd & 0x0100) == 0) {
417 cmd = *src | 0xFF00;
418 src++;
419 newCounter--;
420 if (newCounter == 0)
421 break;
422 }
423
424 if ((cmd & 1) != 0) { /* copy */
425 *dest++ = *src;
426 size++;
427 tmpBuf[tmpIndex] = *src;
428 src++;
429 newCounter--;
430
431 if (newCounter == 0)
432 break;
433
434 tmpIndex++;
435 tmpIndex %= 4096;
436 } else { /* copy string */
437 off = *src++;
438 off |= (*src & 0xF0) << 4;
439 len = (*src & 0x0F) + 3;
440 src++;
441 newCounter -= 2;
442
443 for (int i = 0; i < len; i++) {
444 *dest++ = tmpBuf[(off + i) % 4096];
445 size++;
446 tmpBuf[tmpIndex] = tmpBuf[(off + i) % 4096];
447 tmpIndex++;
448 tmpIndex %= 4096;
449 }
450 if (newCounter <= 0)
451 break;
452 }
453 }
454
455 return unpacked;
456 }
457
458 #ifdef STANDALONE_MAIN
main(int argc,char * argv[])459 int main(int argc, char *argv[]) {
460 ExtractGobStk gob_stk(argv[0]);
461 return gob_stk.run(argc, argv);
462 }
463 #endif
464
465