mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-24 18:56:33 +00:00
b78ac6a18b
svn-id: r15913
887 lines
23 KiB
C++
887 lines
23 KiB
C++
/* Copyright (C) 1994-2004 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* $Header$
|
|
*/
|
|
|
|
#include "common/stdafx.h"
|
|
#include "common/file.h"
|
|
#include "sword2/sword2.h"
|
|
#include "sword2/console.h"
|
|
#include "sword2/defs.h"
|
|
#include "sword2/logic.h"
|
|
#include "sword2/memory.h"
|
|
#include "sword2/resman.h"
|
|
#include "sword2/router.h"
|
|
#include "sword2/driver/d_draw.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.
|
|
};
|
|
|
|
#if !defined(__GNUC__)
|
|
#pragma START_PACK_STRUCTS
|
|
#endif
|
|
|
|
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.
|
|
} GCC_PACK;
|
|
|
|
#if !defined(__GNUC__)
|
|
#pragma END_PACK_STRUCTS
|
|
#endif
|
|
|
|
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.
|
|
_curCd = 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
|
|
|
|
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) {
|
|
_resourceFiles[_totalClusters][j] = temp[i];
|
|
i++;
|
|
j++;
|
|
}
|
|
|
|
// NULL terminate our extracted string
|
|
_resourceFiles[_totalClusters][j] = 0;
|
|
|
|
// 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");
|
|
}
|
|
|
|
file.close();
|
|
|
|
for (i = 0; i < _totalClusters; i++) {
|
|
for (j = 0; j < _totalClusters; j++) {
|
|
if (scumm_stricmp((char *) cdInf[j].clusterName, _resourceFiles[i]) == 0)
|
|
break;
|
|
}
|
|
|
|
if (j == _totalClusters)
|
|
error("%s is not in cd.inf", _resourceFiles[i]);
|
|
|
|
_cdTab[i] = 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", i, _resourceFiles[i]);
|
|
|
|
_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].refTime = 0;
|
|
}
|
|
|
|
_resTime = 0;
|
|
}
|
|
|
|
ResourceManager::~ResourceManager(void) {
|
|
free(_resList);
|
|
free(_resConvTable);
|
|
}
|
|
|
|
// Quick macro to make swapping in-place easier to write
|
|
|
|
#define SWAP16(x) x = SWAP_BYTES_16(x)
|
|
#define SWAP32(x) x = SWAP_BYTES_32(x)
|
|
|
|
void convertEndian(byte *file, uint32 len) {
|
|
int i;
|
|
StandardHeader *hdr = (StandardHeader *) file;
|
|
|
|
file += sizeof(StandardHeader);
|
|
|
|
SWAP32(hdr->compSize);
|
|
SWAP32(hdr->decompSize);
|
|
|
|
switch (hdr->fileType) {
|
|
case ANIMATION_FILE: {
|
|
AnimHeader *animHead = (AnimHeader *) file;
|
|
|
|
SWAP16(animHead->noAnimFrames);
|
|
SWAP16(animHead->feetStartX);
|
|
SWAP16(animHead->feetStartY);
|
|
SWAP16(animHead->feetEndX);
|
|
SWAP16(animHead->feetEndY);
|
|
SWAP16(animHead->blend);
|
|
|
|
CdtEntry *cdtEntry = (CdtEntry *) (file + sizeof(AnimHeader));
|
|
for (i = 0; i < animHead->noAnimFrames; i++, cdtEntry++) {
|
|
SWAP16(cdtEntry->x);
|
|
SWAP16(cdtEntry->y);
|
|
SWAP32(cdtEntry->frameOffset);
|
|
|
|
FrameHeader *frameHeader = (FrameHeader *) (file + cdtEntry->frameOffset);
|
|
// Quick trick to prevent us from incorrectly applying the endian
|
|
// fixes multiple times. This assumes that frames are less than 1 MB
|
|
// and have height/width less than 4096.
|
|
if ((frameHeader->compSize & 0xFFF00000) ||
|
|
(frameHeader->width & 0xF000) ||
|
|
(frameHeader->height & 0xF000)) {
|
|
SWAP32(frameHeader->compSize);
|
|
SWAP16(frameHeader->width);
|
|
SWAP16(frameHeader->height);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SCREEN_FILE: {
|
|
MultiScreenHeader *mscreenHeader = (MultiScreenHeader *) file;
|
|
|
|
SWAP32(mscreenHeader->palette);
|
|
SWAP32(mscreenHeader->bg_parallax[0]);
|
|
SWAP32(mscreenHeader->bg_parallax[1]);
|
|
SWAP32(mscreenHeader->screen);
|
|
SWAP32(mscreenHeader->fg_parallax[0]);
|
|
SWAP32(mscreenHeader->fg_parallax[1]);
|
|
SWAP32(mscreenHeader->layers);
|
|
SWAP32(mscreenHeader->paletteTable);
|
|
SWAP32(mscreenHeader->maskOffset);
|
|
|
|
// screenHeader
|
|
ScreenHeader *screenHeader = (ScreenHeader *) (file + mscreenHeader->screen);
|
|
|
|
SWAP16(screenHeader->width);
|
|
SWAP16(screenHeader->height);
|
|
SWAP16(screenHeader->noLayers);
|
|
|
|
// layerHeader
|
|
LayerHeader *layerHeader = (LayerHeader *) (file + mscreenHeader->layers);
|
|
for (i = 0; i < screenHeader->noLayers; i++, layerHeader++) {
|
|
SWAP16(layerHeader->x);
|
|
SWAP16(layerHeader->y);
|
|
SWAP16(layerHeader->width);
|
|
SWAP16(layerHeader->height);
|
|
SWAP32(layerHeader->maskSize);
|
|
SWAP32(layerHeader->offset);
|
|
}
|
|
|
|
// backgroundParallaxLayer
|
|
Parallax *parallax;
|
|
int offset;
|
|
offset = mscreenHeader->bg_parallax[0];
|
|
if (offset > 0) {
|
|
parallax = (Parallax *) (file + offset);
|
|
SWAP16(parallax->w);
|
|
SWAP16(parallax->h);
|
|
}
|
|
|
|
offset = mscreenHeader->bg_parallax[1];
|
|
if (offset > 0) {
|
|
parallax = (Parallax *) (file + offset);
|
|
SWAP16(parallax->w);
|
|
SWAP16(parallax->h);
|
|
}
|
|
|
|
// backgroundLayer
|
|
offset = mscreenHeader->screen + sizeof(ScreenHeader);
|
|
if (offset > 0) {
|
|
parallax = (Parallax *) (file + offset);
|
|
SWAP16(parallax->w);
|
|
SWAP16(parallax->h);
|
|
}
|
|
|
|
// foregroundParallaxLayer
|
|
offset = mscreenHeader->fg_parallax[0];
|
|
if (offset > 0) {
|
|
parallax = (Parallax *) (file + offset);
|
|
SWAP16(parallax->w);
|
|
SWAP16(parallax->h);
|
|
}
|
|
|
|
offset = mscreenHeader->fg_parallax[1];
|
|
if (offset > 0) {
|
|
parallax = (Parallax *) (file + offset);
|
|
SWAP16(parallax->w);
|
|
SWAP16(parallax->h);
|
|
}
|
|
break;
|
|
}
|
|
case GAME_OBJECT: {
|
|
ObjectHub *objectHub = (ObjectHub *) file;
|
|
|
|
objectHub->type = (int) SWAP_BYTES_32(objectHub->type);
|
|
SWAP32(objectHub->logic_level);
|
|
|
|
for (i = 0; i < TREE_SIZE; i++) {
|
|
SWAP32(objectHub->logic[i]);
|
|
SWAP32(objectHub->script_id[i]);
|
|
SWAP32(objectHub->script_pc[i]);
|
|
}
|
|
break;
|
|
}
|
|
case WALK_GRID_FILE: {
|
|
WalkGridHeader *walkGridHeader = (WalkGridHeader *) file;
|
|
|
|
SWAP32(walkGridHeader->numBars);
|
|
SWAP32(walkGridHeader->numNodes);
|
|
|
|
BarData *barData = (BarData *) (file + sizeof(WalkGridHeader));
|
|
for (i = 0; i < walkGridHeader->numBars; i++) {
|
|
SWAP16(barData->x1);
|
|
SWAP16(barData->y1);
|
|
SWAP16(barData->x2);
|
|
SWAP16(barData->y2);
|
|
SWAP16(barData->xmin);
|
|
SWAP16(barData->ymin);
|
|
SWAP16(barData->xmax);
|
|
SWAP16(barData->ymax);
|
|
SWAP16(barData->dx);
|
|
SWAP16(barData->dy);
|
|
SWAP32(barData->co);
|
|
barData++;
|
|
}
|
|
|
|
uint16 *node = (uint16 *) (file + sizeof(WalkGridHeader) + walkGridHeader->numBars * sizeof(BarData));
|
|
for (i = 0; i < walkGridHeader->numNodes * 2; i++) {
|
|
SWAP16(*node);
|
|
node++;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case GLOBAL_VAR_FILE:
|
|
break;
|
|
case PARALLAX_FILE_null:
|
|
break;
|
|
case RUN_LIST: {
|
|
uint32 *list = (uint32 *) file;
|
|
while (*list) {
|
|
SWAP32(*list);
|
|
list++;
|
|
}
|
|
break;
|
|
}
|
|
case TEXT_FILE: {
|
|
TextHeader *textHeader = (TextHeader *) file;
|
|
SWAP32(textHeader->noOfLines);
|
|
break;
|
|
}
|
|
case SCREEN_MANAGER:
|
|
break;
|
|
case MOUSE_FILE:
|
|
break;
|
|
case ICON_FILE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
|
|
// points to the number of the ascii filename
|
|
uint16 parent_res_file = _resConvTable[res * 2];
|
|
|
|
assert(parent_res_file != 0xffff);
|
|
|
|
// Relative resource within the file
|
|
uint16 actual_res = _resConvTable[(res * 2) + 1];
|
|
|
|
// First we have to find the file via the _resConvTable
|
|
|
|
debug(5, "openResource %s res %d", _resourceFiles[parent_res_file], 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 music.
|
|
|
|
if (!(_cdTab[parent_res_file] & LOCAL_PERM))
|
|
_curCd = _cdTab[parent_res_file] & 3;
|
|
|
|
// 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.
|
|
|
|
File file;
|
|
|
|
while (!file.open(_resourceFiles[parent_res_file])) {
|
|
// 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) || (_cdTab[parent_res_file] & LOCAL_PERM))
|
|
error("Could not find '%s'", _resourceFiles[parent_res_file]);
|
|
|
|
getCd(_cdTab[parent_res_file] & 3);
|
|
}
|
|
|
|
// 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);
|
|
|
|
file.seek(table_offset + actual_res * 8, SEEK_SET);
|
|
|
|
uint32 pos = file.readUint32LE();
|
|
uint32 len = file.readUint32LE();
|
|
|
|
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);
|
|
|
|
if (dump) {
|
|
StandardHeader *header = (StandardHeader *) _resList[res].ptr;
|
|
char buf[256];
|
|
const char *tag;
|
|
File out;
|
|
|
|
switch (header->fileType) {
|
|
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, File::kFileWriteMode, ""))
|
|
out.write(_resList[res].ptr, len);
|
|
}
|
|
}
|
|
|
|
// close the cluster
|
|
file.close();
|
|
|
|
#ifdef SCUMM_BIG_ENDIAN
|
|
convertEndian(_resList[res].ptr, len);
|
|
#endif
|
|
}
|
|
|
|
_resList[res].refCount++;
|
|
_resList[res].refTime = _resTime;
|
|
|
|
return _resList[res].ptr;
|
|
}
|
|
|
|
void ResourceManager::closeResource(uint32 res) {
|
|
assert(res < _totalResFiles);
|
|
assert(_resList[res].refCount > 0);
|
|
|
|
_resList[res].refCount--;
|
|
_resList[res].refTime = _resTime;
|
|
|
|
// 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.
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
void ResourceManager::passTime() {
|
|
// In the original game this was called every game cycle. This allowed
|
|
// for a more exact measure of when a loaded resouce was most recently
|
|
// used. When the memory pool got too fragmented, the oldest and
|
|
// largest of the closed resources would be expelled from the cache.
|
|
|
|
// With the new memory manager, there is no single memory block that
|
|
// can become fragmented. Therefore, it makes more sense to me to
|
|
// measure an object's age in how many rooms ago it was last used.
|
|
|
|
// Therefore, this function is now called when a new room is loaded.
|
|
|
|
_resTime++;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
|
|
File file;
|
|
|
|
if (!file.open(_resourceFiles[parent_res_file]))
|
|
error("Cannot open %s", _resourceFiles[parent_res_file]);
|
|
|
|
// 1st DWORD of a cluster is an offset to the look-up table
|
|
uint32 table_offset = file.readUint32LE();
|
|
|
|
// 2 dwords per resource + skip the position dword
|
|
file.seek(table_offset + (actual_res * 8) + 4, SEEK_SET);
|
|
|
|
return file.readUint32LE();
|
|
}
|
|
|
|
// When a resource is opened, regardless of whether it was read from disk or
|
|
// from the cache, its age is zeroed. They then age every time a new room is
|
|
// entered. This function is responsible for cleaning out the resources that
|
|
// have grown too old to live.
|
|
//
|
|
// It could use a bit more tuning, I guess. I picked a max age of three for
|
|
// most resources, because so much of the game seems to consist of areas of
|
|
// about three rooms. I made an exception for SCREEN_FILE resources because
|
|
// they are so large, but maybe the exception ought to be the rule...?
|
|
|
|
void ResourceManager::expireOldResources() {
|
|
int nuked = 0;
|
|
|
|
for (uint i = 0; i < _totalResFiles; i++) {
|
|
if (!_resList[i].ptr || _resList[i].refCount > 0)
|
|
continue;
|
|
|
|
StandardHeader *head = (StandardHeader *) _resList[i].ptr;
|
|
uint maxCacheAge;
|
|
|
|
switch (head->fileType) {
|
|
case SCREEN_FILE:
|
|
// The resource will be read from disk once as soon as
|
|
// the player enters the room, and thrown away when
|
|
// the player enters a new room.
|
|
maxCacheAge = 0;
|
|
break;
|
|
default:
|
|
maxCacheAge = 3;
|
|
break;
|
|
}
|
|
|
|
if (_resTime - _resList[i].refTime >= maxCacheAge) {
|
|
remove(i);
|
|
nuked++;
|
|
}
|
|
}
|
|
|
|
debug(1, "%d resources died of old age", nuked);
|
|
}
|
|
|
|
void ResourceManager::printConsoleClusters(void) {
|
|
if (_totalClusters) {
|
|
for (uint i = 0; i < _totalClusters; i++) {
|
|
Debug_Printf("%-20s ", _resourceFiles[i]);
|
|
if (!(_cdTab[i] & LOCAL_PERM)) {
|
|
switch (_cdTab[i] & 3) {
|
|
case BOTH:
|
|
Debug_Printf("CD 1 & 2\n");
|
|
break;
|
|
case CD1:
|
|
Debug_Printf("CD 1\n");
|
|
break;
|
|
case CD2:
|
|
Debug_Printf("CD 2\n");
|
|
break;
|
|
default:
|
|
Debug_Printf("CD 3? Huh?!\n");
|
|
break;
|
|
}
|
|
} else
|
|
Debug_Printf("HD\n");
|
|
}
|
|
Debug_Printf("%d resources\n", _totalResFiles);
|
|
} else
|
|
Debug_Printf("Argh! No resources!\n");
|
|
}
|
|
|
|
void ResourceManager::listResources(uint minCount) {
|
|
for (uint i = 0; i < _totalResFiles; i++) {
|
|
if (_resList[i].ptr && _resList[i].refCount >= minCount) {
|
|
StandardHeader *head = (StandardHeader *) _resList[i].ptr;
|
|
Debug_Printf("%-4d: %-35s refCount: %-3d age: %-2d\n", i, head->name, _resList[i].refCount, _resTime - _resList[i].refTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ResourceManager::examine(int res) {
|
|
if (res < 0 || res >= (int) _totalResFiles)
|
|
Debug_Printf("Illegal resource %d (there are %d resources 0-%d)\n", res, _totalResFiles, _totalResFiles - 1);
|
|
else if (_resConvTable[res * 2] == 0xffff)
|
|
Debug_Printf("%d is a null & void resource number\n", res);
|
|
else {
|
|
// open up the resource and take a look inside!
|
|
StandardHeader *file_header = (StandardHeader *) openResource(res);
|
|
|
|
switch (file_header->fileType) {
|
|
case ANIMATION_FILE:
|
|
Debug_Printf("<anim> %s\n", file_header->name);
|
|
break;
|
|
case SCREEN_FILE:
|
|
Debug_Printf("<layer> %s\n", file_header->name);
|
|
break;
|
|
case GAME_OBJECT:
|
|
Debug_Printf("<game object> %s\n", file_header->name);
|
|
break;
|
|
case WALK_GRID_FILE:
|
|
Debug_Printf("<walk grid> %s\n", file_header->name);
|
|
break;
|
|
case GLOBAL_VAR_FILE:
|
|
Debug_Printf("<global variables> %s\n", file_header->name);
|
|
break;
|
|
case PARALLAX_FILE_null:
|
|
Debug_Printf("<parallax file NOT USED!> %s\n", file_header->name);
|
|
break;
|
|
case RUN_LIST:
|
|
Debug_Printf("<run list> %s\n", file_header->name);
|
|
break;
|
|
case TEXT_FILE:
|
|
Debug_Printf("<text file> %s\n", file_header->name);
|
|
break;
|
|
case SCREEN_MANAGER:
|
|
Debug_Printf("<screen manager> %s\n", file_header->name);
|
|
break;
|
|
case MOUSE_FILE:
|
|
Debug_Printf("<mouse pointer> %s\n", file_header->name);
|
|
break;
|
|
case ICON_FILE:
|
|
Debug_Printf("<menu icon> %s\n", file_header->name);
|
|
break;
|
|
default:
|
|
Debug_Printf("unrecognised fileType %d\n", file_header->fileType);
|
|
break;
|
|
}
|
|
closeResource(res);
|
|
}
|
|
}
|
|
|
|
void ResourceManager::kill(int res) {
|
|
if (res < 0 || res >= (int) _totalResFiles) {
|
|
Debug_Printf("Illegal resource %d (there are %d resources 0-%d)\n", res, _totalResFiles, _totalResFiles - 1);
|
|
return;
|
|
}
|
|
|
|
if (!_resList[res].ptr) {
|
|
Debug_Printf("Resource %d is not in memory\n", res);
|
|
return;
|
|
}
|
|
|
|
if (_resList[res].refCount) {
|
|
Debug_Printf("Resource %d is open - cannot remove\n", res);
|
|
return;
|
|
}
|
|
|
|
remove(res);
|
|
Debug_Printf("Trashed %d\n", res);
|
|
}
|
|
|
|
void ResourceManager::remove(int res) {
|
|
if (_resList[res].ptr) {
|
|
_vm->_memory->memFree(_resList[res].ptr);
|
|
_resList[res].ptr = NULL;
|
|
_resList[res].refCount = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove all res files from memory - ready for a total restart. This includes
|
|
* the player object and global variables resource.
|
|
*/
|
|
|
|
void ResourceManager::removeAll(void) {
|
|
// 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->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->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) {
|
|
StandardHeader *header = (StandardHeader *) _resList[i].ptr;
|
|
|
|
if (wantInfo)
|
|
Debug_Printf("Nuked %5d: %s\n", i, header->name);
|
|
|
|
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) {
|
|
StandardHeader *header = (StandardHeader *) _resList[i].ptr;
|
|
|
|
if (header->fileType == GAME_OBJECT) {
|
|
if (wantInfo)
|
|
Debug_Printf("Nuked %5d: %s\n", i, header->name);
|
|
|
|
remove(i);
|
|
nuked++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wantInfo)
|
|
Debug_Printf("Expelled %d resources\n", nuked);
|
|
}
|
|
|
|
void ResourceManager::getCd(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->_logic->fnStopMusic(NULL);
|
|
|
|
textRes = openResource(2283);
|
|
_vm->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
|