aboutsummaryrefslogtreecommitdiff
path: root/engines/saga/resource.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/saga/resource.cpp')
-rw-r--r--engines/saga/resource.cpp521
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