aboutsummaryrefslogtreecommitdiff
path: root/engines/sword2/resman.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sword2/resman.cpp')
-rw-r--r--engines/sword2/resman.cpp633
1 files changed, 633 insertions, 0 deletions
diff --git a/engines/sword2/resman.cpp b/engines/sword2/resman.cpp
new file mode 100644
index 0000000000..7387dc8f50
--- /dev/null
+++ b/engines/sword2/resman.cpp
@@ -0,0 +1,633 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * 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/file.h"
+#include "common/system.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/console.h"
+#include "sword2/logic.h"
+#include "sword2/memory.h"
+#include "sword2/resman.h"
+#include "sword2/router.h"
+#include "sword2/sound.h"
+
+#define Debug_Printf _vm->_debugger->DebugPrintf
+
+namespace Sword2 {
+
+// Welcome to the easy resource manager - written in simple code for easy
+// maintenance
+//
+// The resource compiler will create two files
+//
+// resource.inf which is a list of ascii cluster file names
+// resource.tab which is a table which tells us which cluster a resource
+// is located in and the number within the cluster
+
+enum {
+ BOTH = 0x0, // Cluster is on both CDs
+ CD1 = 0x1, // Cluster is on CD1 only
+ CD2 = 0x2, // Cluster is on CD2 only
+ LOCAL_CACHE = 0x4, // Cluster is cached on HDD
+ LOCAL_PERM = 0x8 // Cluster is on HDD.
+};
+
+struct CdInf {
+ uint8 clusterName[20]; // Null terminated cluster name.
+ uint8 cd; // Cd cluster is on and whether it is on the local drive or not.
+};
+
+ResourceManager::ResourceManager(Sword2Engine *vm) {
+ _vm = vm;
+
+ // Until proven differently, assume we're on CD 1. This is so the start
+ // dialog will be able to play any music at all.
+ setCD(1);
+
+ // We read in the resource info which tells us the names of the
+ // resource cluster files ultimately, although there might be groups
+ // within the clusters at this point it makes no difference. We only
+ // wish to know what resource files there are and what is in each
+
+ Common::File file;
+ uint32 size;
+ byte *temp;
+
+ _totalClusters = 0;
+ _resConvTable = NULL;
+
+ if (!file.open("resource.inf"))
+ error("Cannot open resource.inf");
+
+ size = file.size();
+
+ // Get some space for the incoming resource file - soon to be trashed
+ temp = (byte *)malloc(size);
+
+ if (file.read(temp, size) != size) {
+ file.close();
+ error("init cannot *READ* resource.inf");
+ }
+
+ file.close();
+
+ // Ok, we've loaded in the resource.inf file which contains a list of
+ // all the files now extract the filenames.
+
+ // Using this method the Gode generated resource.inf must have #0d0a on
+ // the last entry
+
+ uint32 i = 0;
+ uint32 j = 0;
+
+ do {
+ // item must have an #0d0a
+ while (temp[i] != 13) {
+ _resFiles[_totalClusters].fileName[j] = temp[i];
+ i++;
+ j++;
+ }
+
+ // NULL terminate our extracted string
+ _resFiles[_totalClusters].fileName[j] = '\0';
+ _resFiles[_totalClusters].numEntries = -1;
+ _resFiles[_totalClusters].entryTab = NULL;
+
+ // Reset position in current slot between entries, skip the
+ // 0x0a in the source and increase the number of clusters.
+
+ j = 0;
+ i += 2;
+ _totalClusters++;
+
+ // TODO: put overload check here
+ } while (i != size);
+
+ free(temp);
+
+ // Now load in the binary id to res conversion table
+ if (!file.open("resource.tab"))
+ error("Cannot open resource.tab");
+
+ // Find how many resources
+ size = file.size();
+
+ _totalResFiles = size / 4;
+
+ // Table seems ok so malloc some space
+ _resConvTable = (uint16 *)malloc(size);
+
+ for (i = 0; i < size / 2; i++)
+ _resConvTable[i] = file.readUint16LE();
+
+ if (file.ioFailed()) {
+ file.close();
+ error("Cannot read resource.tab");
+ }
+
+ file.close();
+
+ if (!file.open("cd.inf"))
+ error("Cannot open cd.inf");
+
+ CdInf *cdInf = new CdInf[_totalClusters];
+
+ for (i = 0; i < _totalClusters; i++) {
+ file.read(cdInf[i].clusterName, sizeof(cdInf[i].clusterName));
+
+ cdInf[i].cd = file.readByte();
+
+ if (file.ioFailed())
+ error("Cannot read cd.inf");
+
+ // It has been reported that there are two different versions
+ // of the cd.inf file: One where all clusters on CD also have
+ // the LOCAL_CACHE bit set. This bit is no longer used. To
+ // avoid future problems, let's normalize the flag once and for
+ // all here.
+
+ if (cdInf[i].cd & LOCAL_PERM)
+ cdInf[i].cd = 0;
+ else if (cdInf[i].cd & CD1)
+ cdInf[i].cd = 1;
+ else if (cdInf[i].cd & CD2)
+ cdInf[i].cd = 2;
+ else
+ cdInf[i].cd = 0;
+ }
+
+ file.close();
+
+ for (i = 0; i < _totalClusters; i++) {
+ for (j = 0; j < _totalClusters; j++) {
+ if (scumm_stricmp((char *)cdInf[j].clusterName, _resFiles[i].fileName) == 0)
+ break;
+ }
+
+ if (j == _totalClusters)
+ error("%s is not in cd.inf", _resFiles[i].fileName);
+
+ _resFiles[i].cd = cdInf[j].cd;
+ }
+
+ delete [] cdInf;
+
+ debug(1, "%d resources in %d cluster files", _totalResFiles, _totalClusters);
+ for (i = 0; i < _totalClusters; i++)
+ debug(2, "filename of cluster %d: -%s (%d)", i, _resFiles[i].fileName, _resFiles[i].cd);
+
+ _resList = (Resource *)malloc(_totalResFiles * sizeof(Resource));
+
+ for (i = 0; i < _totalResFiles; i++) {
+ _resList[i].ptr = NULL;
+ _resList[i].size = 0;
+ _resList[i].refCount = 0;
+ _resList[i].prev = _resList[i].next = NULL;
+ }
+ _cacheStart = _cacheEnd = NULL;
+ _usedMem = 0;
+}
+
+ResourceManager::~ResourceManager() {
+ Resource *res = _cacheStart;
+ while (res) {
+ _vm->_memory->memFree(res->ptr);
+ res = res->next;
+ }
+ for (uint i = 0; i < _totalClusters; i++)
+ free(_resFiles[i].entryTab);
+ free(_resList);
+ free(_resConvTable);
+}
+
+/**
+ * Returns the address of a resource. Loads if not in memory. Retains a count.
+ */
+
+byte *ResourceManager::openResource(uint32 res, bool dump) {
+ assert(res < _totalResFiles);
+
+ // Is the resource in memory already? If not, load it.
+
+ if (!_resList[res].ptr) {
+ // Fetch the correct file and read in the correct portion.
+ uint16 cluFileNum = _resConvTable[res * 2]; // points to the number of the ascii filename
+ assert(cluFileNum != 0xffff);
+
+ // Relative resource within the file
+ // First we have to find the file via the _resConvTable
+ uint16 actual_res = _resConvTable[(res * 2) + 1];
+
+ debug(5, "openResource %s res %d", _resFiles[cluFileNum].fileName, res);
+
+ // If we're loading a cluster that's only available from one
+ // of the CDs, remember which one so that we can play the
+ // correct speech and music.
+
+ setCD(_resFiles[cluFileNum].cd);
+
+ // Actually, as long as the file can be found we don't really
+ // care which CD it's on. But if we can't find it, keep asking
+ // for the CD until we do.
+
+ Common::File *file = openCluFile(cluFileNum);
+
+ if (_resFiles[cluFileNum].entryTab == NULL) {
+ // we didn't read from this file before, get its index table
+ readCluIndex(cluFileNum, file);
+ }
+
+ uint32 pos = _resFiles[cluFileNum].entryTab[actual_res * 2 + 0];
+ uint32 len = _resFiles[cluFileNum].entryTab[actual_res * 2 + 1];
+
+ file->seek(pos, SEEK_SET);
+
+ debug(6, "res len %d", len);
+
+ // Ok, we know the length so try and allocate the memory.
+ _resList[res].ptr = _vm->_memory->memAlloc(len, res);
+ _resList[res].size = len;
+ _resList[res].refCount = 0;
+
+ file->read(_resList[res].ptr, len);
+
+ debug(3, "Loaded resource '%s' from '%s' on CD %d (%d)", fetchName(_resList[res].ptr), _resFiles[cluFileNum].fileName, getCD(), _resFiles[cluFileNum].cd);
+
+ if (dump) {
+ char buf[256];
+ const char *tag;
+ Common::File out;
+
+ switch (fetchType(_resList[res].ptr)) {
+ case ANIMATION_FILE:
+ tag = "anim";
+ break;
+ case SCREEN_FILE:
+ tag = "layer";
+ break;
+ case GAME_OBJECT:
+ tag = "object";
+ break;
+ case WALK_GRID_FILE:
+ tag = "walkgrid";
+ break;
+ case GLOBAL_VAR_FILE:
+ tag = "globals";
+ break;
+ case PARALLAX_FILE_null:
+ tag = "parallax"; // Not used!
+ break;
+ case RUN_LIST:
+ tag = "runlist";
+ break;
+ case TEXT_FILE:
+ tag = "text";
+ break;
+ case SCREEN_MANAGER:
+ tag = "screen";
+ break;
+ case MOUSE_FILE:
+ tag = "mouse";
+ break;
+ case WAV_FILE:
+ tag = "wav";
+ break;
+ case ICON_FILE:
+ tag = "icon";
+ break;
+ case PALETTE_FILE:
+ tag = "palette";
+ break;
+ default:
+ tag = "unknown";
+ break;
+ }
+
+#if defined(MACOS_CARBON)
+ sprintf(buf, ":dumps:%s-%d.dmp", tag, res);
+#else
+ sprintf(buf, "dumps/%s-%d.dmp", tag, res);
+#endif
+
+ if (!out.exists(buf, "")) {
+ if (out.open(buf, Common::File::kFileWriteMode, ""))
+ out.write(_resList[res].ptr, len);
+ }
+ }
+
+ // close the cluster
+ file->close();
+ delete file;
+
+ _usedMem += len;
+ checkMemUsage();
+ } else if (_resList[res].refCount == 0)
+ removeFromCacheList(_resList + res);
+
+ _resList[res].refCount++;
+
+ return _resList[res].ptr;
+}
+
+void ResourceManager::closeResource(uint32 res) {
+ assert(res < _totalResFiles);
+
+ // Don't try to close the resource if it has already been forcibly
+ // closed, e.g. by fnResetGlobals().
+
+ if (_resList[res].ptr == NULL)
+ return;
+
+ assert(_resList[res].refCount > 0);
+
+ _resList[res].refCount--;
+ if (_resList[res].refCount == 0)
+ addToCacheList(_resList + res);
+
+ // It's tempting to free the resource immediately when refCount
+ // reaches zero, but that'd be a mistake. Closing a resource does not
+ // mean "I'm not going to use this resource any more". It means that
+ // "the next time I use this resource I'm going to ask for a new
+ // pointer to it".
+ //
+ // Since the original memory manager had to deal with memory
+ // fragmentation, keeping a resource open - and thus locked down to a
+ // specific memory address - was considered a bad thing.
+}
+
+void ResourceManager::removeFromCacheList(Resource *res) {
+ if (_cacheStart == res)
+ _cacheStart = res->next;
+
+ if (_cacheEnd == res)
+ _cacheEnd = res->prev;
+
+ if (res->prev)
+ res->prev->next = res->next;
+ if (res->next)
+ res->next->prev = res->prev;
+ res->prev = res->next = NULL;
+}
+
+void ResourceManager::addToCacheList(Resource *res) {
+ res->prev = NULL;
+ res->next = _cacheStart;
+ if (_cacheStart)
+ _cacheStart->prev = res;
+ _cacheStart = res;
+ if (!_cacheEnd)
+ _cacheEnd = res;
+}
+
+Common::File *ResourceManager::openCluFile(uint16 fileNum) {
+ Common::File *file = new Common::File;
+ while (!file->open(_resFiles[fileNum].fileName)) {
+ // HACK: We have to check for this, or it'll be impossible to
+ // quit while the game is asking for the user to insert a CD.
+ // But recovering from this situation gracefully is just too
+ // much trouble, so quit now.
+ if (_vm->_quit)
+ g_system->quit();
+
+ // If the file is supposed to be on hard disk, or we're
+ // playing a demo, then we're in trouble if the file
+ // can't be found!
+
+ if ((_vm->_features & GF_DEMO) || _resFiles[fileNum].cd == 0)
+ error("Could not find '%s'", _resFiles[fileNum].fileName);
+
+ askForCD(_resFiles[fileNum].cd);
+ }
+ return file;
+}
+
+void ResourceManager::readCluIndex(uint16 fileNum, Common::File *file) {
+ if (_resFiles[fileNum].entryTab == NULL) {
+ // we didn't read from this file before, get its index table
+ if (file == NULL)
+ file = openCluFile(fileNum);
+ else
+ file->incRef();
+
+ // 1st DWORD of a cluster is an offset to the look-up table
+ uint32 table_offset = file->readUint32LE();
+ debug(6, "table offset = %d", table_offset);
+ uint32 tableSize = file->size() - table_offset; // the table is stored at the end of the file
+ file->seek(table_offset);
+
+ assert((tableSize % 8) == 0);
+ _resFiles[fileNum].entryTab = (uint32*)malloc(tableSize);
+ _resFiles[fileNum].numEntries = tableSize / 8;
+ file->read(_resFiles[fileNum].entryTab, tableSize);
+ if (file->ioFailed())
+ error("unable to read index table from file %s\n", _resFiles[fileNum].fileName);
+#ifdef SCUMM_BIG_ENDIAN
+ for (int tabCnt = 0; tabCnt < _resFiles[fileNum].numEntries * 2; tabCnt++)
+ _resFiles[fileNum].entryTab[tabCnt] = FROM_LE_32(_resFiles[fileNum].entryTab[tabCnt]);
+#endif
+ file->decRef();
+ }
+}
+
+/**
+ * Returns true if resource is valid, otherwise false.
+ */
+
+bool ResourceManager::checkValid(uint32 res) {
+ // Resource number out of range
+ if (res >= _totalResFiles)
+ return false;
+
+ // Points to the number of the ascii filename
+ uint16 parent_res_file = _resConvTable[res * 2];
+
+ // Null & void resource
+ if (parent_res_file == 0xffff)
+ return false;
+
+ return true;
+}
+
+/**
+ * Returns the total file length of a resource - i.e. all headers are included
+ * too.
+ */
+
+uint32 ResourceManager::fetchLen(uint32 res) {
+ if (_resList[res].ptr)
+ return _resList[res].size;
+
+ // Does this ever happen?
+ warning("fetchLen: Resource %u is not loaded; reading length from file", res);
+
+ // Points to the number of the ascii filename
+ uint16 parent_res_file = _resConvTable[res * 2];
+
+ // relative resource within the file
+ uint16 actual_res = _resConvTable[(res * 2) + 1];
+
+ // first we have to find the file via the _resConvTable
+ // open the cluster file
+
+ if (_resFiles[parent_res_file].entryTab == NULL) {
+ readCluIndex(parent_res_file);
+ }
+ return _resFiles[parent_res_file].entryTab[actual_res * 2 + 1];
+}
+
+void ResourceManager::checkMemUsage() {
+ while (_usedMem > MAX_MEM_CACHE) {
+ // we're using up more memory than we wanted to. free some old stuff.
+ // Newly loaded objects are added to the start of the list,
+ // we start freeing from the end, to free the oldest items first
+ if (_cacheEnd) {
+ Resource *tmp = _cacheEnd;
+ assert((tmp->refCount == 0) && (tmp->ptr) && (tmp->next == NULL));
+ removeFromCacheList(tmp);
+
+ _vm->_memory->memFree(tmp->ptr);
+ tmp->ptr = NULL;
+ _usedMem -= tmp->size;
+ } else {
+ warning("%d bytes of memory used, but cache list is empty!\n");
+ return;
+ }
+ }
+}
+
+void ResourceManager::remove(int res) {
+ if (_resList[res].ptr) {
+ removeFromCacheList(_resList + res);
+
+ _vm->_memory->memFree(_resList[res].ptr);
+ _resList[res].ptr = NULL;
+ _resList[res].refCount = 0;
+ _usedMem -= _resList[res].size;
+ }
+}
+
+/**
+ * Remove all res files from memory - ready for a total restart. This includes
+ * the player object and global variables resource.
+ */
+
+void ResourceManager::removeAll() {
+ // We need to clear the FX queue, because otherwise the sound system
+ // will still believe that the sound resources are in memory, and that
+ // it's ok to close them.
+
+ _vm->_sound->clearFxQueue();
+
+ for (uint i = 0; i < _totalResFiles; i++)
+ remove(i);
+}
+
+/**
+ * Remove all resources from memory.
+ */
+
+void ResourceManager::killAll(bool wantInfo) {
+ int nuked = 0;
+
+ // We need to clear the FX queue, because otherwise the sound system
+ // will still believe that the sound resources are in memory, and that
+ // it's ok to close them.
+
+ _vm->_sound->clearFxQueue();
+
+ for (uint i = 0; i < _totalResFiles; i++) {
+ // Don't nuke the global variables or the player object!
+ if (i == 1 || i == CUR_PLAYER_ID)
+ continue;
+
+ if (_resList[i].ptr) {
+ if (wantInfo)
+ Debug_Printf("Nuked %5d: %s\n", i, fetchName(_resList[i].ptr));
+
+ remove(i);
+ nuked++;
+ }
+ }
+
+ if (wantInfo)
+ Debug_Printf("Expelled %d resources\n", nuked);
+}
+
+/**
+ * Like killAll but only kills objects (except George & the variable table of
+ * course) - ie. forcing them to reload & restart their scripts, which
+ * simulates the effect of a save & restore, thus checking that each object's
+ * re-entrant logic works correctly, and doesn't cause a statuette to
+ * disappear forever, or some plaster-filled holes in sand to crash the game &
+ * get James in trouble again.
+ */
+
+void ResourceManager::killAllObjects(bool wantInfo) {
+ int nuked = 0;
+
+ for (uint i = 0; i < _totalResFiles; i++) {
+ // Don't nuke the global variables or the player object!
+ if (i == 1 || i == CUR_PLAYER_ID)
+ continue;
+
+ if (_resList[i].ptr) {
+ if (fetchType(_resList[i].ptr) == GAME_OBJECT) {
+ if (wantInfo)
+ Debug_Printf("Nuked %5d: %s\n", i, fetchName(_resList[i].ptr));
+
+ remove(i);
+ nuked++;
+ }
+ }
+ }
+
+ if (wantInfo)
+ Debug_Printf("Expelled %d resources\n", nuked);
+}
+
+void ResourceManager::askForCD(int cd) {
+ byte *textRes;
+
+ // Stop any music from playing - so the system no longer needs the
+ // current CD - otherwise when we take out the CD, Windows will
+ // complain!
+
+ _vm->_sound->stopMusic(true);
+
+ textRes = openResource(2283);
+ _vm->_screen->displayMsg(_vm->fetchTextLine(textRes, 5 + cd) + 2, 0);
+ closeResource(2283);
+
+ // The original code probably determined automagically when the correct
+ // CD had been inserted, but our backend doesn't support that, and
+ // anyway I don't know if all systems allow that sort of thing. So we
+ // wait for the user to press any key instead, or click the mouse.
+ //
+ // But just in case we ever try to identify the CDs by their labels,
+ // they should be:
+ //
+ // CD1: "RBSII1" (or "PCF76" for the PCF76 version, whatever that is)
+ // CD2: "RBSII2"
+}
+
+} // End of namespace Sword2