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