/* ScummVM - Scumm Interpreter * Copyright (C) 2003-2006 The ScummVM project * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ * */ #include "common/stdafx.h" #include "common/endian.h" #include "common/file.h" #include "common/util.h" #include "sky/disk.h" #include "sky/rnc_deco.h" #include "sky/sky.h" #include "sky/struc.h" namespace Sky { static const char *dataFilename = "sky.dsk"; static const char *dinnerFilename = "sky.dnr"; Disk::Disk(const Common::String &gameDataPath) { _dataDiskHandle = new Common::File(); _dnrHandle = new Common::File(); _dnrHandle->open(dinnerFilename); if (!_dnrHandle->isOpen()) error("Could not open %s%s", gameDataPath.c_str(), dinnerFilename); if (!(_dinnerTableEntries = _dnrHandle->readUint32LE())) error("Error reading from sky.dnr"); //even though it was opened correctly?! _dinnerTableArea = (uint8 *)malloc(_dinnerTableEntries * 8); uint32 entriesRead = _dnrHandle->read(_dinnerTableArea, 8 * _dinnerTableEntries) / 8; if (entriesRead != _dinnerTableEntries) error("entriesRead != dinnerTableEntries. [%d/%d]", entriesRead, _dinnerTableEntries); _dataDiskHandle->open(dataFilename); if (!_dataDiskHandle->isOpen()) error("Error opening %s%s", gameDataPath.c_str(), dataFilename); printf("Found BASS version v0.0%d (%d dnr entries)\n", determineGameVersion(), _dinnerTableEntries); memset(_buildList, 0, 60 * 2); memset(_loadedFilesList, 0, 60 * 4); } Disk::~Disk(void) { if (_dnrHandle->isOpen()) _dnrHandle->close(); if (_dataDiskHandle->isOpen()) _dataDiskHandle->close(); fnFlushBuffers(); free(_dinnerTableArea); delete _dnrHandle; delete _dataDiskHandle; } bool Disk::fileExists(uint16 fileNr) { return (getFileInfo(fileNr) != NULL); } // allocate memory, load the file and return a pointer uint8 *Disk::loadFile(uint16 fileNr) { uint8 cflag; debug(2, "load file %d,%d (%d)", (fileNr >> 11), (fileNr & 2047), fileNr); uint8 *fileInfoPtr = getFileInfo(fileNr); if (fileInfoPtr == NULL) { debug(1, "File %d not found", fileNr); return NULL; } uint32 fileFlags = READ_LE_UINT24(fileInfoPtr + 5); uint32 fileSize = fileFlags & 0x03fffff; uint32 fileOffset = READ_LE_UINT32(fileInfoPtr + 2) & 0x0ffffff; _lastLoadedFileSize = fileSize; cflag = (uint8)((fileOffset >> 23) & 0x1); fileOffset &= 0x7FFFFF; if (cflag) { if (SkyEngine::_systemVars.gameVersion == 331) fileOffset <<= 3; else fileOffset <<= 4; } uint8 *fileDest = (uint8 *)malloc(fileSize + 4); // allocate memory for file _dataDiskHandle->seek(fileOffset, SEEK_SET); //now read in the data int32 bytesRead = _dataDiskHandle->read(fileDest, fileSize); if (bytesRead != (int32)fileSize) warning("Unable to read %d bytes from datadisk (%d bytes read)", fileSize, bytesRead); cflag = (uint8)((fileFlags >> 23) & 0x1); //if cflag == 0 then file is compressed, 1 == uncompressed dataFileHeader *fileHeader = (dataFileHeader*)fileDest; if ((!cflag) && ((FROM_LE_16(fileHeader->flag) >> 7) & 1)) { debug(2, "File is RNC compressed."); uint32 decompSize = (FROM_LE_16(fileHeader->flag) & ~0xFF) << 8; decompSize |= FROM_LE_16(fileHeader->s_tot_size); uint8 *uncompDest = (uint8 *)malloc(decompSize); int32 unpackLen; void *output, *input = fileDest + sizeof(dataFileHeader); if ((fileFlags >> 22) & 0x1) { //do we include the header? // don't return the file's header output = uncompDest; unpackLen = _rncDecoder.unpackM1(input, output, 0); } else { #ifdef SCUMM_BIG_ENDIAN // Convert dataFileHeader to BE (it only consists of 16 bit words) uint16 *headPtr = (uint16 *)fileDest; for (uint i = 0; i < sizeof(struct dataFileHeader) / 2; i++) *(headPtr + i) = READ_LE_UINT16(headPtr + i); #endif memcpy(uncompDest, fileDest, sizeof(dataFileHeader)); output = uncompDest + sizeof(dataFileHeader); unpackLen = _rncDecoder.unpackM1(input, output, 0); if (unpackLen) unpackLen += sizeof(dataFileHeader); } debug(3, "UnpackM1 returned: %d", unpackLen); if (unpackLen == 0) { //Unpack returned 0: file was probably not packed. free(uncompDest); return fileDest; } else { if (unpackLen != (int32)decompSize) debug(1, "ERROR: File %d: invalid decomp size! (was: %d, should be: %d)", fileNr, unpackLen, decompSize); _lastLoadedFileSize = decompSize; free(fileDest); return uncompDest; } } else { #ifdef SCUMM_BIG_ENDIAN if (!cflag) { uint16 *headPtr = (uint16 *)fileDest; for (uint i = 0; i < sizeof(struct dataFileHeader) / 2; i++) *(headPtr + i) = READ_LE_UINT16(headPtr + i); } #endif return fileDest; } } uint16 *Disk::loadScriptFile(uint16 fileNr) { uint16 *buf = (uint16*)loadFile(fileNr); #ifdef SCUMM_BIG_ENDIAN for (uint i = 0; i < _lastLoadedFileSize / 2; i++) buf[i] = FROM_LE_16(buf[i]); #endif return buf; } uint8 *Disk::getFileInfo(uint16 fileNr) { uint16 i; uint16 *dnrTbl16Ptr = (uint16 *)_dinnerTableArea; for (i = 0; i < _dinnerTableEntries; i++) { if (READ_LE_UINT16(dnrTbl16Ptr) == fileNr) { debug(2, "file %d found", fileNr); return (uint8 *)dnrTbl16Ptr; } dnrTbl16Ptr += 4; } return 0; //not found } void Disk::fnCacheChip(uint16 *fList) { // fnCacheChip is called after fnCacheFast uint16 cnt = 0; while (_buildList[cnt]) cnt++; uint16 fCnt = 0; do { _buildList[cnt + fCnt] = fList[fCnt] & 0x7FFFU; fCnt++; } while (fList[fCnt-1]); fnCacheFiles(); } void Disk::fnCacheFast(uint16 *fList) { if (fList != NULL) { uint8 cnt = 0; do { _buildList[cnt] = fList[cnt] & 0x7FFFU; cnt++; } while (fList[cnt-1]); } } void Disk::fnCacheFiles(void) { uint16 lCnt, bCnt, targCnt; targCnt = lCnt = 0; bool found; while (_loadedFilesList[lCnt]) { bCnt = 0; found = false; while (_buildList[bCnt] && (!found)) { if ((_buildList[bCnt] & 0x7FFFU) == _loadedFilesList[lCnt]) found = true; else bCnt++; } if (found) { _loadedFilesList[targCnt] = _loadedFilesList[lCnt]; targCnt++; } else { free(SkyEngine::_itemList[_loadedFilesList[lCnt] & 2047]); SkyEngine::_itemList[_loadedFilesList[lCnt] & 2047] = NULL; } lCnt++; } _loadedFilesList[targCnt] = 0; // mark end of list bCnt = 0; while (_buildList[bCnt]) { if ((_buildList[bCnt] & 0x7FF) == 0x7FF) { // amiga dummy files bCnt++; continue; } lCnt = 0; found = false; while (_loadedFilesList[lCnt] && (!found)) { if (_loadedFilesList[lCnt] == (_buildList[bCnt] & 0x7FFFU)) found = true; lCnt++; } if (found) { bCnt++; continue; } // ok, we really have to load the file. _loadedFilesList[targCnt] = _buildList[bCnt] & 0x7FFFU; targCnt++; _loadedFilesList[targCnt] = 0; SkyEngine::_itemList[_buildList[bCnt] & 2047] = (void**)loadFile(_buildList[bCnt] & 0x7FFF); if (!SkyEngine::_itemList[_buildList[bCnt] & 2047]) warning("fnCacheFiles: Disk::loadFile() returned NULL for file %d",_buildList[bCnt] & 0x7FFF); bCnt++; } _buildList[0] = 0; } void Disk::refreshFilesList(uint32 *list) { uint8 cnt = 0; while (_loadedFilesList[cnt]) { if (SkyEngine::_itemList[_loadedFilesList[cnt] & 2047]) free(SkyEngine::_itemList[_loadedFilesList[cnt] & 2047]); SkyEngine::_itemList[_loadedFilesList[cnt] & 2047] = NULL; cnt++; } cnt = 0; while (list[cnt]) { _loadedFilesList[cnt] = list[cnt]; SkyEngine::_itemList[_loadedFilesList[cnt] & 2047] = (void**)loadFile((uint16)(_loadedFilesList[cnt] & 0x7FFF)); cnt++; } _loadedFilesList[cnt] = 0; } void Disk::fnMiniLoad(uint16 fileNum) { uint16 cnt = 0; while (_loadedFilesList[cnt]) { if (_loadedFilesList[cnt] == fileNum) return; cnt++; } _loadedFilesList[cnt] = fileNum & 0x7FFFU; _loadedFilesList[cnt + 1] = 0; SkyEngine::_itemList[fileNum & 2047] = (void**)loadFile(fileNum); } void Disk::fnFlushBuffers(void) { // dump all loaded sprites uint8 lCnt = 0; while (_loadedFilesList[lCnt]) { free(SkyEngine::_itemList[_loadedFilesList[lCnt] & 2047]); SkyEngine::_itemList[_loadedFilesList[lCnt] & 2047] = NULL; lCnt++; } _loadedFilesList[0] = 0; } void Disk::dumpFile(uint16 fileNr) { char buf[128]; Common::File out; byte* filePtr; filePtr = loadFile(fileNr); sprintf(buf, "dumps/file-%d.dmp", fileNr); if (!out.exists(buf)) { if (out.open(buf, Common::File::kFileWriteMode)) out.write(filePtr, _lastLoadedFileSize); } free(filePtr); } uint32 Disk::determineGameVersion() { //determine game version based on number of entries in dinner table switch (_dinnerTableEntries) { case 243: // pc gamer demo (v0.0109) return 109; case 247: //floppy demo (v0.0267) return 267; case 1404: //floppy (v0.0288) return 288; case 1413: //floppy (v0.0303) return 303; case 1445: //floppy (v0.0331 or v0.0348) if (_dataDiskHandle->size() == 8830435) return 348; else return 331; case 1711: //cd demo (v0.0365) return 365; case 5099: //cd (v0.0368) return 368; case 5097: //cd (v0.0372) return 372; default: //unknown version error("Unknown game version! %d dinner table entries", _dinnerTableEntries); break; } } } // End of namespace Sky