Logo Search packages:      
Sourcecode: scummvm version File versions  Download package

resman.cpp

/* 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.
 *
 * Additional copyright for this file:
 * Copyright (C) 1994-1998 Revolution Software Ltd.
 *
 * 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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-11-1/engines/sword2/resman.cpp $
 * $Id: resman.cpp 30944 2008-02-23 22:50:18Z sev $
 */


#include "common/file.h"
#include "common/system.h"

#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/console.h"
#include "sword2/logic.h"
#include "sword2/memory.h"
#include "sword2/resman.h"
#include "sword2/router.h"
#include "sword2/screen.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;

      _totalClusters = 0;
      _resList = NULL;
      _resConvTable = NULL;
      _cacheStart = NULL;
      _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);
}


bool ResourceManager::init() {
      uint32 i, j;

      // 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;

      if (!file.open("resource.inf")) {
            _vm->GUIErrorMessage("Broken Sword 2: Cannot open resource.inf");
            return false;
      }

      // The resource.inf file is a simple text file containing the names of
      // all the resource files.

      while (file.readLine(_resFiles[_totalClusters].fileName, sizeof(_resFiles[_totalClusters].fileName))) {
            _resFiles[_totalClusters].numEntries = -1;
            _resFiles[_totalClusters].entryTab = NULL;
            if (++_totalClusters >= MAX_res_files) {
                  _vm->GUIErrorMessage("Broken Sword 2: Too many entries in resource.inf");
                  return false;
            }
      }

      file.close();

      // Now load in the binary id to res conversion table
      if (!file.open("resource.tab")) {
            _vm->GUIErrorMessage("Broken Sword 2: Cannot open resource.tab");
            return false;
      }

      // Find how many resources
      uint32 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();
            _vm->GUIErrorMessage("Broken Sword 2: Cannot read resource.tab");
            return false;
      }

      file.close();

      if (!file.open("cd.inf")) {
            _vm->GUIErrorMessage("Broken Sword 2: Cannot open cd.inf");
            return false;
      }

      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()) {
                  delete cdInf;
                  file.close();
                  _vm->GUIErrorMessage("Broken Sword 2: Cannot read cd.inf");
                  return false;
            }

            // 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;

            // Any file on "CD 0" may be needed at all times. Verify that
            // it exists. Any other missing cluster will be requested with
            // an "insert CD" message. Of course, the file may still vanish
            // during game-play (oh, that wascally wabbit!) in which case
            // the resource manager will print a fatal error.

            if (cdInf[i].cd == 0 && !Common::File::exists((char *)cdInf[i].clusterName)) {
                  _vm->GUIErrorMessage("Broken Sword 2: Cannot find " + Common::String((char *)cdInf[i].clusterName));
                  delete [] cdInf;
                  return false;
            }
      }

      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) {
                  delete [] cdInf;
                  _vm->GUIErrorMessage(Common::String(_resFiles[i].fileName) + " is not in cd.inf");
                  return false;
            }

            _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;
      }

      return true;
}

/**
 * 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' (%d) from '%s' on CD %d (%d)", fetchName(_resList[res].ptr), res, _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;
                  }

                  sprintf(buf, "dumps/%s-%d.dmp", tag, res);

                  if (!Common::File::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) {
      // we didn't read from this file before, get its index table
      assert(_resFiles[fileNum].entryTab == NULL);
      assert(file);

      // 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
}

/**
 * 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) {
            Common::File *file = openCluFile(parent_res_file);
            readCluIndex(parent_res_file, file);
            delete 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", _usedMem);
                  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. We also
      // need to kill the movie lead-in/out.

      _vm->_sound->clearFxQueue(true);

      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. We also
      // need to kill the movie lead-in/out.

      _vm->_sound->clearFxQueue(true);

      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

Generated by  Doxygen 1.6.0   Back to index