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