1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * 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 
23 #include "scumm/file.h"
24 
25 #include "common/memstream.h"
26 #include "common/substream.h"
27 
28 namespace Scumm {
29 
30 #pragma mark -
31 #pragma mark --- ScummFile ---
32 #pragma mark -
33 
ScummFile()34 ScummFile::ScummFile() : _subFileStart(0), _subFileLen(0), _myEos(false) {
35 }
36 
setSubfileRange(int32 start,int32 len)37 void ScummFile::setSubfileRange(int32 start, int32 len) {
38 	// TODO: Add sanity checks
39 	const int32 fileSize = File::size();
40 	assert(start <= fileSize);
41 	assert(start + len <= fileSize);
42 	_subFileStart = start;
43 	_subFileLen = len;
44 	seek(0, SEEK_SET);
45 }
46 
resetSubfile()47 void ScummFile::resetSubfile() {
48 	_subFileStart = 0;
49 	_subFileLen = 0;
50 	seek(0, SEEK_SET);
51 }
52 
open(const Common::Path & filename)53 bool ScummFile::open(const Common::Path &filename) {
54 	if (File::open(filename)) {
55 		resetSubfile();
56 		return true;
57 	} else {
58 		return false;
59 	}
60 }
61 
openSubFile(const Common::String & filename)62 bool ScummFile::openSubFile(const Common::String &filename) {
63 	assert(isOpen());
64 
65 	// Disable the XOR encryption and reset any current subfile range
66 	setEnc(0);
67 	resetSubfile();
68 
69 	// Read in the filename table and look for the specified file
70 
71 	unsigned long file_off, file_len;
72 	char file_name[0x20+1];
73 	unsigned long i;
74 
75 	// Get the length of the data file to use for consistency checks
76 	const uint32 data_file_len = size();
77 
78 	// Read offset and length to the file records */
79 	const uint32 file_record_off = readUint32BE();
80 	const uint32 file_record_len = readUint32BE();
81 
82 	// Do a quick check to make sure the offset and length are good
83 	if (file_record_off + file_record_len > data_file_len) {
84 		return false;
85 	}
86 
87 	// Do a little consistancy check on file_record_length
88 	if (file_record_len % 0x28) {
89 		return false;
90 	}
91 
92 	// Scan through the files
93 	for (i = 0; i < file_record_len; i += 0x28) {
94 		// read a file record
95 		seek(file_record_off + i, SEEK_SET);
96 		file_off = readUint32BE();
97 		file_len = readUint32BE();
98 		read(file_name, 0x20);
99 		file_name[0x20] = 0;
100 
101 		assert(file_name[0]);
102 		//debug(7, "  extracting \'%s\'", file_name);
103 
104 		// Consistency check. make sure the file data is in the file
105 		if (file_off + file_len > data_file_len) {
106 			return false;
107 		}
108 
109 		if (scumm_stricmp(file_name, filename.c_str()) == 0) {
110 			// We got a match!
111 			setSubfileRange(file_off, file_len);
112 			return true;
113 		}
114 	}
115 
116 	return false;
117 }
118 
119 
eos() const120 bool ScummFile::eos() const {
121 	return _subFileLen ? _myEos : File::eos();
122 }
123 
pos() const124 int64 ScummFile::pos() const {
125 	return File::pos() - _subFileStart;
126 }
127 
size() const128 int64 ScummFile::size() const {
129 	return _subFileLen ? _subFileLen : File::size();
130 }
131 
seek(int64 offs,int whence)132 bool ScummFile::seek(int64 offs, int whence) {
133 	if (_subFileLen) {
134 		// Constrain the seek to the subfile
135 		switch (whence) {
136 		case SEEK_END:
137 			offs = _subFileStart + _subFileLen + offs;
138 			break;
139 		case SEEK_SET:
140 		default:
141 			offs += _subFileStart;
142 			break;
143 		case SEEK_CUR:
144 			offs += File::pos();
145 			break;
146 		}
147 		assert((int32)_subFileStart <= offs && offs <= (int32)(_subFileStart + _subFileLen));
148 		whence = SEEK_SET;
149 	}
150 	bool ret = File::seek(offs, whence);
151 	if (ret)
152 		_myEos = false;
153 	return ret;
154 }
155 
read(void * dataPtr,uint32 dataSize)156 uint32 ScummFile::read(void *dataPtr, uint32 dataSize) {
157 	uint32 realLen;
158 
159 	if (_subFileLen) {
160 		// Limit the amount we read by the subfile boundaries.
161 		const int32 curPos = pos();
162 		assert(_subFileLen >= curPos);
163 		int32 newPos = curPos + dataSize;
164 		if (newPos > _subFileLen) {
165 			dataSize = _subFileLen - curPos;
166 			_myEos = true;
167 		}
168 	}
169 
170 	realLen = File::read(dataPtr, dataSize);
171 
172 
173 	// If an encryption byte was specified, XOR the data we just read by it.
174 	// This simple kind of "encryption" was used by some of the older SCUMM
175 	// games.
176 	if (_encbyte) {
177 		byte *p = (byte *)dataPtr;
178 		byte *end = p + realLen;
179 		while (p < end)
180 			*p++ ^= _encbyte;
181 	}
182 
183 	return realLen;
184 }
185 
186 #pragma mark -
187 #pragma mark --- ScummSteamFile ---
188 #pragma mark -
189 
open(const Common::Path & filename)190 bool ScummSteamFile::open(const Common::Path &filename) {
191 	if (filename.toString().equalsIgnoreCase(_indexFile.indexFileName)) {
192 		return openWithSubRange(_indexFile.executableName, _indexFile.start, _indexFile.len);
193 	} else {
194 		// Regular non-bundled file
195 		return ScummFile::open(filename);
196 	}
197 }
198 
openWithSubRange(const Common::String & filename,int32 subFileStart,int32 subFileLen)199 bool ScummSteamFile::openWithSubRange(const Common::String &filename, int32 subFileStart, int32 subFileLen) {
200 	if (ScummFile::open(filename)) {
201 		_subFileStart = subFileStart;
202 		_subFileLen = subFileLen;
203 		seek(0, SEEK_SET);
204 		return true;
205 	} else {
206 		return false;
207 	}
208 }
209 
210 #pragma mark -
211 #pragma mark --- ScummDiskImage ---
212 #pragma mark -
213 
214 static const int maniacResourcesPerFile[55] = {
215 	 0, 11,  1,  3,  9, 12,  1, 13, 10,  6,
216 	 4,  1,  7,  1,  1,  2,  7,  8, 19,  9,
217 	 6,  9,  2,  6,  8,  4, 16,  8,  3,  3,
218 	12, 12,  2,  8,  1,  1,  2,  1,  9,  1,
219 	 3,  7,  3,  3, 13,  5,  4,  3,  1,  1,
220 	 3, 10,  1,  0,  0
221 };
222 
223 static const int maniacDemoResourcesPerFile[55] = {
224 	 0, 12,  0,  2,  1, 12,  1, 13,  6,  0,
225 	 31, 0,  1,  0,  0,  0,  0,  1,  1,  1,
226 	 0,  1,  0,  0,  2,  0,  0,  1,  0,  0,
227 	 2,  7,  1, 11,  0,  0,  5,  1,  0,  0,
228 	 1,  0,  1,  3,  4,  3,  1,  0,  0,  1,
229 	 2,  2,  0,  0,  0
230 };
231 
232 static const int zakResourcesPerFile[59] = {
233 	 0, 29, 12, 14, 13,  4,  4, 10,  7,  4,
234 	14, 19,  5,  4,  7,  6, 11,  9,  4,  4,
235 	 1,  3,  3,  5,  1,  9,  4, 10, 13,  6,
236 	 7, 10,  2,  6,  1, 11,  2,  5,  7,  1,
237 	 7,  1,  4,  2,  8,  6,  6,  6,  4, 13,
238 	 3,  1,  2,  1,  2,  1, 10,  1,  1
239 };
240 
241 
write_byte(Common::WriteStream * out,byte val)242 static uint16 write_byte(Common::WriteStream *out, byte val) {
243 	val ^= 0xFF;
244 	if (out != 0)
245 		out->writeByte(val);
246 	return 1;
247 }
248 
write_word(Common::WriteStream * out,uint16 val)249 static uint16 write_word(Common::WriteStream *out, uint16 val) {
250 	val ^= 0xFFFF;
251 	if (out != 0)
252 		out->writeUint16LE(val);
253 	return 2;
254 }
255 
ScummDiskImage(const char * disk1,const char * disk2,GameSettings game)256 ScummDiskImage::ScummDiskImage(const char *disk1, const char *disk2, GameSettings game)
257 	: _stream(0), _buf(0), _game(game),
258 	_disk1(disk1), _disk2(disk2), _openedDisk(0) {
259 
260 	if (_game.id == GID_MANIAC) {
261 		_numGlobalObjects = 256;
262 		_numRooms = 55;
263 		_numCostumes = 25;
264 
265 		if (_game.features & GF_DEMO) {
266 			_numScripts = 55;
267 			_numSounds = 40;
268 			_resourcesPerFile = maniacDemoResourcesPerFile;
269 		} else {
270 			_numScripts = 160;
271 			_numSounds = 70;
272 			_resourcesPerFile = maniacResourcesPerFile;
273 		}
274 
275 	} else {
276 		_numGlobalObjects = 775;
277 		_numRooms = 59;
278 		_numCostumes = 38;
279 		_numScripts = 155;
280 		_numSounds = 127;
281 		_resourcesPerFile = zakResourcesPerFile;
282 	}
283 }
284 
fileReadByte()285 byte ScummDiskImage::fileReadByte() {
286 	byte b = 0;
287 	File::read(&b, 1);
288 	return b;
289 }
290 
fileReadUint16LE()291 uint16 ScummDiskImage::fileReadUint16LE() {
292 	uint16 a = fileReadByte();
293 	uint16 b = fileReadByte();
294 	return a | (b << 8);
295 }
296 
openDisk(char num)297 bool ScummDiskImage::openDisk(char num) {
298 	if (num == '1')
299 		num = 1;
300 	if (num == '2')
301 		num = 2;
302 
303 	if (_openedDisk != num || !File::isOpen()) {
304 		if (File::isOpen())
305 			File::close();
306 
307 		if (num == 1)
308 			File::open(_disk1);
309 		else if (num == 2)
310 			File::open(_disk2);
311 		else {
312 			error("ScummDiskImage::open(): wrong disk (%c)", num);
313 			return false;
314 		}
315 
316 		_openedDisk = num;
317 
318 		if (!File::isOpen()) {
319 			error("ScummDiskImage::open(): cannot open disk (%d)", num);
320 			return false;
321 		}
322 	}
323 	return true;
324 }
325 
open(const Common::Path & filename)326 bool ScummDiskImage::open(const Common::Path &filename) {
327 	uint16 signature;
328 
329 	// check signature
330 	openDisk(1);
331 
332 	if (_game.platform == Common::kPlatformApple2GS) {
333 		File::seek(142080);
334 	} else {
335 		File::seek(0);
336 	}
337 
338 	signature = fileReadUint16LE();
339 	if (signature != 0x0A31) {
340 		error("ScummDiskImage::open(): signature not found in disk 1");
341 		return false;
342 	}
343 
344 	extractIndex(0); // Fill in resource arrays
345 
346 	if (_game.features & GF_DEMO)
347 		return true;
348 
349 	openDisk(2);
350 
351 	if (_game.platform == Common::kPlatformApple2GS) {
352 		File::seek(143104);
353 		signature = fileReadUint16LE();
354 		if (signature != 0x0032)
355 			error("Error: signature not found in disk 2");
356 	} else {
357 		File::seek(0);
358 		signature = fileReadUint16LE();
359 		if (signature != 0x0132)
360 			error("Error: signature not found in disk 2");
361 	}
362 
363 
364 	return true;
365 }
366 
367 
extractIndex(Common::WriteStream * out)368 uint16 ScummDiskImage::extractIndex(Common::WriteStream *out) {
369 	int i;
370 	uint16 reslen = 0;
371 
372 	openDisk(1);
373 
374 	if (_game.platform == Common::kPlatformApple2GS) {
375 		File::seek(142080);
376 	} else {
377 		File::seek(0);
378 	}
379 
380 	// skip signature
381 	fileReadUint16LE();
382 
383 	// write expected signature
384 	if (_game.platform == Common::kPlatformApple2GS) {
385 		reslen += write_word(out, 0x0032);
386 	} else {
387 		reslen += write_word(out, 0x0132);
388 	}
389 
390 	// copy object flags
391 	for (i = 0; i < _numGlobalObjects; i++)
392 		reslen += write_byte(out, fileReadByte());
393 
394 	// copy room offsets
395 	for (i = 0; i < _numRooms; i++) {
396 		_roomDisks[i] = fileReadByte();
397 		reslen += write_byte(out, _roomDisks[i]);
398 	}
399 	for (i = 0; i < _numRooms; i++) {
400 		_roomSectors[i] = fileReadByte();
401 		reslen += write_byte(out, _roomSectors[i]);
402 		_roomTracks[i] = fileReadByte();
403 		reslen += write_byte(out, _roomTracks[i]);
404 	}
405 	for (i = 0; i < _numCostumes; i++)
406 		reslen += write_byte(out, fileReadByte());
407 	for (i = 0; i < _numCostumes; i++)
408 		reslen += write_word(out, fileReadUint16LE());
409 
410 	for (i = 0; i < _numScripts; i++)
411 		reslen += write_byte(out, fileReadByte());
412 	for (i = 0; i < _numScripts; i++)
413 		reslen += write_word(out, fileReadUint16LE());
414 
415 	for (i = 0; i < _numSounds; i++)
416 		reslen += write_byte(out, fileReadByte());
417 	for (i = 0; i < _numSounds; i++)
418 		reslen += write_word(out, fileReadUint16LE());
419 
420 	return reslen;
421 }
422 
generateIndex()423 bool ScummDiskImage::generateIndex() {
424 	int bufsize;
425 
426 	bufsize = extractIndex(0);
427 
428 	free(_buf);
429 	_buf = (byte *)calloc(1, bufsize);
430 
431 	Common::MemoryWriteStream out(_buf, bufsize);
432 
433 	extractIndex(&out);
434 
435 	delete _stream;
436 	_stream = new Common::MemoryReadStream(_buf, bufsize);
437 
438 	return true;
439 }
440 
extractResource(Common::WriteStream * out,int res)441 uint16 ScummDiskImage::extractResource(Common::WriteStream *out, int res) {
442 	const int AppleSectorOffset[36] = {
443 		0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256,
444 		272, 288, 304, 320, 336, 352, 368,
445 		384, 400, 416, 432, 448, 464,
446 		480, 496, 512, 528, 544, 560
447 	};
448 	const int C64SectorOffset[36] = {
449 		0,
450 		0, 21, 42, 63, 84, 105, 126, 147, 168, 189, 210, 231, 252, 273, 294, 315, 336,
451 		357, 376, 395, 414, 433, 452, 471,
452 		490, 508, 526, 544, 562, 580,
453 		598, 615, 632, 649, 666
454 	};
455 	int i;
456 	uint16 reslen = 0;
457 
458 	openDisk(_roomDisks[res]);
459 
460 	if (_game.platform == Common::kPlatformApple2GS) {
461 		File::seek((AppleSectorOffset[_roomTracks[res]] + _roomSectors[res]) * 256);
462 	} else {
463 		File::seek((C64SectorOffset[_roomTracks[res]] + _roomSectors[res]) * 256);
464 	}
465 
466 	for (i = 0; i < _resourcesPerFile[res]; i++) {
467 		uint16 len;
468 		do {
469 			// Note: len might be 0xFFFF for padding in zak-c64-german
470 			len = fileReadUint16LE();
471 			reslen += write_word(out, len);
472 		} while (len == 0xFFFF);
473 
474 		for (len -= 2; len > 0; len--)
475 			reslen += write_byte(out, fileReadByte());
476 	}
477 
478 	return reslen;
479 }
480 
generateResource(int res)481 bool ScummDiskImage::generateResource(int res) {
482 	int bufsize;
483 
484 	if (res >= _numRooms)
485 		return false;
486 
487 	bufsize = extractResource(0, res);
488 
489 	free(_buf);
490 	_buf = (byte *)calloc(1, bufsize);
491 
492 	Common::MemoryWriteStream out(_buf, bufsize);
493 
494 	extractResource(&out, res);
495 
496 	delete _stream;
497 	_stream = new Common::MemoryReadStream(_buf, bufsize);
498 
499 	return true;
500 }
501 
close()502 void ScummDiskImage::close() {
503 	delete _stream;
504 	_stream = 0;
505 
506 	free(_buf);
507 	_buf = 0;
508 
509 	File::close();
510 }
511 
openSubFile(const Common::String & filename)512 bool ScummDiskImage::openSubFile(const Common::String &filename) {
513 	assert(isOpen());
514 
515 	const char *ext = strrchr(filename.c_str(), '.');
516 	char resNum[3];
517 	int res;
518 
519 	// We always have file name in form of XX.lfl
520 	resNum[0] = ext[-2];
521 	resNum[1] = ext[-1];
522 	resNum[2] = 0;
523 
524 	res = atoi(resNum);
525 
526 	if (res == 0) {
527 		return generateIndex();
528 	} else {
529 		return generateResource(res);
530 	}
531 
532 	return true;
533 }
534 
read(void * dataPtr,uint32 dataSize)535 uint32 ScummDiskImage::read(void *dataPtr, uint32 dataSize) {
536 	uint32 realLen = _stream->read(dataPtr, dataSize);
537 
538 	if (_encbyte) {
539 		byte *p = (byte *)dataPtr;
540 		byte *end = p + realLen;
541 		while (p < end)
542 			*p++ ^= _encbyte;
543 	}
544 
545 	return realLen;
546 }
547 
548 } // End of namespace Scumm
549