diff options
Diffstat (limited to 'engines/saga/resource.cpp')
-rw-r--r-- | engines/saga/resource.cpp | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/engines/saga/resource.cpp b/engines/saga/resource.cpp new file mode 100644 index 0000000000..4176488117 --- /dev/null +++ b/engines/saga/resource.cpp @@ -0,0 +1,521 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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$ + * + */ + +// RSC Resource file management module + +#include "saga/saga.h" + +#include "saga/actor.h" +#include "saga/animation.h" +#include "saga/interface.h" +#include "saga/music.h" +#include "saga/resource.h" +#include "saga/scene.h" +#include "saga/sndres.h" + +#include "common/advancedDetector.h" + +namespace Saga { + +Resource::Resource(SagaEngine *vm): _vm(vm) { + _contexts = NULL; + _contextsCount = 0; +} + +Resource::~Resource() { + clearContexts(); +} + +bool Resource::loadResContext_v1(ResourceContext *context, uint32 contextOffset, uint32 contextSize) { + size_t i; + bool result; + byte tableInfo[RSC_TABLEINFO_SIZE]; + byte *tableBuffer; + size_t tableSize; + uint32 resourceTableOffset; + ResourceData *resourceData; + + if (contextSize < RSC_MIN_FILESIZE) { + return false; + } + + context->file->seek(contextOffset + contextSize - RSC_TABLEINFO_SIZE); + + if (context->file->read(tableInfo, RSC_TABLEINFO_SIZE) != RSC_TABLEINFO_SIZE) { + return false; + } + + MemoryReadStreamEndian readS(tableInfo, RSC_TABLEINFO_SIZE, context->isBigEndian); + + resourceTableOffset = readS.readUint32(); + context->count = readS.readUint32(); + + // Check for sane table offset + if (resourceTableOffset != contextSize - RSC_TABLEINFO_SIZE - RSC_TABLEENTRY_SIZE * context->count) { + return false; + } + + // Load resource table + tableSize = RSC_TABLEENTRY_SIZE * context->count; + + tableBuffer = (byte *)malloc(tableSize); + + context->file->seek(resourceTableOffset + contextOffset, SEEK_SET); + + result = (context->file->read(tableBuffer, tableSize) == tableSize); + if (result) { + context->table = (ResourceData *)calloc(context->count, sizeof(*context->table)); + + MemoryReadStreamEndian readS1(tableBuffer, tableSize, context->isBigEndian); + + for (i = 0; i < context->count; i++) { + resourceData = &context->table[i]; + resourceData->offset = contextOffset + readS1.readUint32(); + resourceData->size = readS1.readUint32(); + //sanity check + if ((resourceData->offset > (uint)context->file->size()) || (resourceData->size > contextSize)) { + result = false; + break; + } + } + } + + free(tableBuffer); + return result; +} + +bool Resource::loadContext(ResourceContext *context) { + size_t i; + const GamePatchDescription *patchDescription; + ResourceData *resourceData; + uint16 subjectResourceType; + ResourceContext *subjectContext; + uint32 subjectResourceId; + uint32 patchResourceId; + ResourceData *subjectResourceData; + byte *tableBuffer; + size_t tableSize; + bool isMacBinary; + + if (!context->file->open(context->fileName)) { + return false; + } + + context->isBigEndian = _vm->isBigEndian(); + + if (context->fileType & GAME_SWAPENDIAN) + context->isBigEndian = !context->isBigEndian; + + isMacBinary = (context->fileType & GAME_MACBINARY) > 0; + context->fileType &= ~GAME_MACBINARY; + + if (!isMacBinary) { + if (!loadResContext(context, 0, context->file->size())) { + return false; + } + } else { + if (!loadMacContext(context)) { + return false; + } + } + + //process internal patch files + if (context->fileType & GAME_PATCHFILE) { + subjectResourceType = ~GAME_PATCHFILE & context->fileType; + subjectContext = getContext((GameFileTypes)subjectResourceType); + if (subjectContext == NULL) { + error("Resource::loadContext() Subject context not found"); + } + loadResource(context, context->count - 1, tableBuffer, tableSize); + + MemoryReadStreamEndian readS2(tableBuffer, tableSize, context->isBigEndian); + for (i = 0; i < tableSize / 8; i++) { + subjectResourceId = readS2.readUint32(); + patchResourceId = readS2.readUint32(); + subjectResourceData = subjectContext->getResourceData(subjectResourceId); + resourceData = context->getResourceData(patchResourceId); + subjectResourceData->patchData = new PatchData(context->file); + subjectResourceData->offset = resourceData->offset; + subjectResourceData->size = resourceData->size; + } + free(tableBuffer); + } + + //process external patch files + for (patchDescription = _vm->getPatchDescriptions(); patchDescription && patchDescription->fileName; ++patchDescription) { + if ((patchDescription->fileType & context->fileType) != 0) { + if (patchDescription->resourceId < context->count) { + resourceData = &context->table[patchDescription->resourceId]; + resourceData->patchData = new PatchData(patchDescription); + if (resourceData->patchData->_patchFile->open(patchDescription->fileName)) { + resourceData->offset = 0; + resourceData->size = resourceData->patchData->_patchFile->size(); + // ITE uses several patch files which are loaded and then not needed + // anymore (as they're in memory), so close them here. IHNM uses only + // 1 patch file, which is reused, so don't close it + if (_vm->getGameId() == GID_ITE) + resourceData->patchData->_patchFile->close(); + } else { + delete resourceData->patchData; + resourceData->patchData = NULL; + } + } + } + } + + // Close the file if it's part of a series of files + // This prevents having all voice files open in IHNM for no reason, as each chapter uses + // a different voice file + if (context->serial > 0) + context->file->close(); + + return true; +} + +bool Resource::createContexts() { + int i; + ResourceContext *context; + char musicFileName[256]; + char soundFileName[256]; + int soundFileIndex = 0; + int voicesFileIndex = 0; + bool digitalMusic = false; + bool soundFileInArray = false; + bool multipleVoices = false; + bool censoredVersion = false; + uint16 voiceFileType = GAME_VOICEFILE; + bool fileFound = false; + int maxFile = 0; + + _vm->_voiceFilesExist = true; + + struct SoundFileInfo { + char fileName[40]; + bool isCompressed; + }; + + SoundFileInfo *curSoundfiles; + + // If the Wyrmkeep credits file is found, set the Wyrmkeep version flag to true + if (Common::File::exists("graphics/credit3n.dlt")) { + _vm->_gf_wyrmkeep = true; + } + + _contextsCount = 0; + for (i = 0; _vm->getFilesDescriptions()[i].fileName; i++) { + _contextsCount++; + if (_vm->getFilesDescriptions()[i].fileType == GAME_SOUNDFILE) + soundFileInArray = true; + } + + //// Detect and add SFX files //////////////////////////////////////////////// + SoundFileInfo sfxFilesITE[] = { + { "sounds.rsc", false }, + { "sounds.cmp", true }, + { "soundsd.rsc", false }, + { "soundsd.cmp", true } + }; + + SoundFileInfo sfxFilesIHNM[] = { + { "sfx.res", false }, + { "sfx.cmp", true } + }; + + SoundFileInfo sfxFilesFTA2[] = { + { "ftasound.hrs", false } + }; + + if (!soundFileInArray) { + // If the sound file is not specified in the detector table, add it here + fileFound = false; + + switch (_vm->getGameId()) { + case GID_ITE: + curSoundfiles = sfxFilesITE; + maxFile = 4; + break; + case GID_IHNM: + curSoundfiles = sfxFilesIHNM; + maxFile = 2; + break; + case GID_DINO: + // TODO + curSoundfiles = NULL; + maxFile = 0; + break; + case GID_FTA2: + curSoundfiles = sfxFilesFTA2; + maxFile = 1; + break; + } + + for (i = 0; i < maxFile; i++) { + if (Common::File::exists(curSoundfiles[i].fileName)) { + _contextsCount++; + soundFileIndex = _contextsCount - 1; + strcpy(soundFileName, curSoundfiles[i].fileName); + _vm->_gf_compressed_sounds = curSoundfiles[i].isCompressed; + fileFound = true; + break; + } + } + + if (!fileFound) { + // No sound file found, don't add any file to the array + soundFileInArray = true; + if (_vm->getGameId() == GID_ITE) { + // ITE floppy versions have both voices and sounds in voices.rsc + voiceFileType = GAME_SOUNDFILE | GAME_VOICEFILE; + } + } + } + + //// Detect and add voice files ///////////////////////////////////////////// + SoundFileInfo voiceFilesITE[] = { + { "voices.rsc", false }, + { "voices.cmp", true }, + { "voicesd.rsc", false }, + { "voicesd.cmp", true }, + { "inherit the earth voices", false }, + { "inherit the earth voices.cmp", true }, + { "ite voices.bin", false } + }; + + SoundFileInfo voiceFilesIHNM[] = { + { "voicess.res", false }, + { "voicess.cmp", true }, + { "voicesd.res", false }, + { "voicesd.cmp", true }, + }; + + SoundFileInfo voiceFilesFTA2[] = { + { "ftavoice.hrs", false }, + }; + + // Detect and add voice files + fileFound = false; + + switch (_vm->getGameId()) { + case GID_ITE: + curSoundfiles = voiceFilesITE; + maxFile = 7; + break; + case GID_IHNM: + curSoundfiles = voiceFilesIHNM; + maxFile = 4; + break; + case GID_DINO: + // TODO + curSoundfiles = NULL; + maxFile = 0; + break; + case GID_FTA2: + curSoundfiles = voiceFilesFTA2; + maxFile = 1; + break; + } + + for (i = 0; i < maxFile; i++) { + if (Common::File::exists(curSoundfiles[i].fileName)) { + _contextsCount++; + voicesFileIndex = _contextsCount - 1; + strcpy(_voicesFileName[0], curSoundfiles[i].fileName); + _vm->_gf_compressed_sounds = curSoundfiles[i].isCompressed; + fileFound = true; + + // Special cases + if (!scumm_stricmp(curSoundfiles[i].fileName, "inherit the earth voices") || + !scumm_stricmp(curSoundfiles[i].fileName, "inherit the earth voices.cmp")) { + // The resources in the Wyrmkeep combined Windows/Mac/Linux CD version are little endian, but + // the voice file is big endian. If we got such a version with mixed files, mark this voice file + // as big endian + if (!_vm->isBigEndian()) + voiceFileType = GAME_VOICEFILE | GAME_SWAPENDIAN; // This file is big endian + } + + if (!scumm_stricmp(curSoundfiles[i].fileName, "ite voices.bin")) { + voiceFileType = GAME_VOICEFILE | GAME_MACBINARY; + } + + if (!scumm_stricmp(curSoundfiles[i].fileName, "voicess.res") || + !scumm_stricmp(curSoundfiles[i].fileName, "voicess.cmp")) { + // IHNM has multiple voice files + multipleVoices = true; + // Note: it is assumed that the voice files are always last in the list + if (Common::File::exists("voices4.res") || Common::File::exists("voices4.cmp")) { + _contextsCount += 6; // voices1-voices6 + } else { + // The German and French versions of IHNM don't have Nimdok's chapter, + // therefore the voices file for that chapter is missing + _contextsCount += 5; // voices1-voices3, voices4-voices5 + censoredVersion = true; + } + } + + break; + } + } + + if (!fileFound) { + if (_vm->getGameId() == GID_IHNM && _vm->isMacResources()) { + // The Macintosh version of IHNM has no voices.res, and it has all + // its voice files in subdirectories, so don't do anything here + } else { + warning("No voice file found, voices will be disabled"); + _vm->_voicesEnabled = false; + _vm->_subtitlesEnabled = true; + _vm->_voiceFilesExist = false; + } + } + + //// Detect and add ITE music files ///////////////////////////////////////// + // We don't set the compressed flag here + SoundFileInfo musicFilesITE[] = { + { "music.rsc", true }, + { "music.cmp", true }, + { "musicd.rsc", true }, + { "musicd.cmp", true }, + }; + + // Check for digital music in ITE + if (_vm->getGameId() == GID_ITE) { + fileFound = false; + + for (i = 0; i < 4; i++) { + if (Common::File::exists(musicFilesITE[i].fileName)) { + _contextsCount++; + digitalMusic = true; + fileFound = true; + strcpy(musicFileName, musicFilesITE[i].fileName); + break; + } + } + + if (!fileFound) { + // No sound file found, don't add any file to the array + digitalMusic = false; + } + } + + _contexts = (ResourceContext*)calloc(_contextsCount, sizeof(*_contexts)); + + for (i = 0; i < _contextsCount; i++) { + context = &_contexts[i]; + context->file = new Common::File(); + context->serial = 0; + + // For ITE, add the digital music file and sfx file information here + if (_vm->getGameId() == GID_ITE && digitalMusic && i == _contextsCount - 1) { + context->fileName = musicFileName; + context->fileType = GAME_MUSICFILE; + } else if (!soundFileInArray && i == soundFileIndex) { + context->fileName = soundFileName; + context->fileType = GAME_SOUNDFILE; + } else if (_vm->_voiceFilesExist && i == voicesFileIndex) { + context->fileName = _voicesFileName[0]; + // can be GAME_VOICEFILE or GAME_SOUNDFILE | GAME_VOICEFILE or GAME_VOICEFILE | GAME_SWAPENDIAN + context->fileType = voiceFileType; + } else { + if (!(_vm->_voiceFilesExist && multipleVoices && (i > voicesFileIndex))) { + context->fileName = _vm->getFilesDescriptions()[i].fileName; + context->fileType = _vm->getFilesDescriptions()[i].fileType; + } else { + int token = (censoredVersion && (i - voicesFileIndex >= 4)) ? 1 : 0; // censored versions don't have voice4 + + if (_vm->getFeatures() & GF_COMPRESSED_SOUNDS) + sprintf(_voicesFileName[i - voicesFileIndex + token], "voices%i.cmp", i - voicesFileIndex + token); + else + sprintf(_voicesFileName[i - voicesFileIndex + token], "voices%i.res", i - voicesFileIndex + token); + + context->fileName = _voicesFileName[i - voicesFileIndex + token]; + context->fileType = GAME_VOICEFILE; + + // IHNM has several different voice files, so we need to allow + // multiple resource contexts of the same type. We tell them + // apart by assigning each of the duplicates a unique serial + // number. The default behaviour when requesting a context will + // be to look for serial number 0. + context->serial = i - voicesFileIndex + token; + } + } + + if (!loadContext(context)) { + return false; + } + } + return true; +} + +void Resource::clearContexts() { + int i; + size_t j; + ResourceContext *context; + if (_contexts == NULL) { + return; + } + for (i = 0; i < _contextsCount; i++) { + context = &_contexts[i]; + delete context->file; + if (context->table != NULL) { + for (j = 0; j < context->count; j++) { + delete context->table[j].patchData; + } + } + free(context->table); + } + free(_contexts); + _contexts = NULL; +} + +void Resource::loadResource(ResourceContext *context, uint32 resourceId, byte*&resourceBuffer, size_t &resourceSize) { + Common::File *file; + uint32 resourceOffset; + ResourceData *resourceData; + + debug(8, "loadResource %d", resourceId); + + resourceData = context->getResourceData(resourceId); + + file = context->getFile(resourceData); + + resourceOffset = resourceData->offset; + resourceSize = resourceData->size; + + resourceBuffer = (byte*)malloc(resourceSize); + + file->seek((long)resourceOffset, SEEK_SET); + + if (file->read(resourceBuffer, resourceSize) != resourceSize) { + error("Resource::loadResource() failed to read"); + } + + // ITE uses several patch files which are loaded and then not needed + // anymore (as they're in memory), so close them here. IHNM uses only + // 1 patch file, which is reused, so don't close it + if (resourceData->patchData != NULL && _vm->getGameId() == GID_ITE) + file->close(); +} + +} // End of namespace Saga |