scummvm/scumm/resource.cpp
Eugene Sandulenko ff7e16dc2b o Added few targets
* baseball2003 and Soccer2004 use Bink videos but just for intro movies and
    logos, so we may add them.
  * artdemo and readdemo also use Bink, but seems that additionally it uses
    them in cutscenes, but since there are just few of them, not like in
    full games, we may try to look at them too
  * SoccerMLS is (alsmost) working. It runs the intro, shows menu, lets
    to select from it but then fails at some Wiz stuff and there is no
    hotspots to choose. I think it may be related to overall HE99 problem
    with inventory where there is a bug preventing from item selection
o baseball2003 and Soccer2004 featured new LECF index block. Add stub for it
o SoccerMLS used kernelSetFunction 2001 in intro. add stub for it
o Alternative russian freddi3 uses badly formatted logo substitution in intro,
  so error() in default case in Gdi::drawBMAPBg() was replaced with warning().

svn-id: r16722
2005-02-02 00:32:02 +00:00

1510 lines
40 KiB
C++

/* ScummVM - Scumm Interpreter
* Copyright (C) 2001 Ludvig Strigeus
* Copyright (C) 2001-2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Header$
*
*/
#include "stdafx.h"
#include "common/str.h"
#include "scumm/dialogs.h"
#include "scumm/imuse.h"
#include "scumm/imuse_digi/dimuse.h"
#include "scumm/object.h"
#include "scumm/resource.h"
#include "scumm/scumm.h"
#include "scumm/sound.h"
#include "scumm/verbs.h"
#include "sound/mididrv.h" // Need MD_ enum values
namespace Scumm {
static uint16 newTag2Old(uint32 oldTag);
static const char *resTypeFromId(int id);
/* Open a room */
void ScummEngine::openRoom(int room) {
int room_offs, roomlimit;
bool result;
char buf[128];
char buf2[128] = "";
byte encByte = 0;
debugC(DEBUG_GENERAL, "openRoom(%d)", room);
assert(room >= 0);
/* Don't load the same room again */
if (_lastLoadedRoom == room)
return;
_lastLoadedRoom = room;
/* Room -1 means close file */
if (room == -1) {
deleteRoomOffsets();
_fileHandle.close();
return;
}
/* Either xxx.lfl or monkey.xxx file name */
while (1) {
if (_features & GF_SMALL_NAMES)
roomlimit = 98;
else
roomlimit = 900;
if (_features & GF_EXTERNAL_CHARSET && room >= roomlimit)
room_offs = 0;
else
room_offs = room ? res.roomoffs[rtRoom][room] : 0;
if (room_offs == -1)
break;
if (room_offs != 0 && room != 0) {
_fileOffset = res.roomoffs[rtRoom][room];
return;
}
if (!(_features & GF_SMALL_HEADER)) {
if (_heversion >= 70) { // Windows titles
if (_heversion >= 98) {
sprintf(buf, "%s.%s", _gameName.c_str(), room == 0 ? "he0" : "(a)");
sprintf(buf2, "%s.(b)", _gameName.c_str());
} else
sprintf(buf, "%s.he%.1d", _gameName.c_str(), room == 0 ? 0 : 1);
} else if (_version >= 7) {
if (room > 0 && (_version == 8))
VAR(VAR_CURRENTDISK) = res.roomno[rtRoom][room];
sprintf(buf, "%s.la%d", _gameName.c_str(), room == 0 ? 0 : res.roomno[rtRoom][room]);
sprintf(buf2, "%s.%.3d", _gameName.c_str(), room == 0 ? 0 : res.roomno[rtRoom][room]);
} else if (_features & GF_HUMONGOUS)
sprintf(buf, "%s.he%.1d", _gameName.c_str(), room == 0 ? 0 : res.roomno[rtRoom][room]);
else {
sprintf(buf, "%s.%.3d", _gameName.c_str(), room == 0 ? 0 : res.roomno[rtRoom][room]);
if (_gameId == GID_SAMNMAX)
sprintf(buf2, "%s.sm%.1d", _gameName.c_str(), room == 0 ? 0 : res.roomno[rtRoom][room]);
}
encByte = (_features & GF_USE_KEY) ? 0x69 : 0;
} else if (!(_features & GF_SMALL_NAMES)) {
if (room == 0 || room >= 900) {
sprintf(buf, "%.3d.lfl", room);
encByte = 0;
if (openResourceFile(buf, encByte)) {
return;
}
askForDisk(buf, room == 0 ? 0 : res.roomno[rtRoom][room]);
} else {
sprintf(buf, "disk%.2d.lec", room == 0 ? 0 : res.roomno[rtRoom][room]);
encByte = 0x69;
}
} else {
sprintf(buf, "%.2d.lfl", room);
// Maniac Mansion demo has .man instead of .lfl
if (_gameId == GID_MANIAC)
sprintf(buf2, "%.2d.man", room);
encByte = (_features & GF_USE_KEY) ? 0xFF : 0;
}
// If we have substitute
if (_heMacFileNameIndex > 0) {
char tmpBuf[128];
generateMacFileName(buf, tmpBuf, 128, 0, _heMacFileNameIndex);
strcpy(buf, tmpBuf);
generateMacFileName(buf2, tmpBuf, 128, 0, _heMacFileNameIndex);
strcpy(buf2, tmpBuf);
}
result = openResourceFile(buf, encByte);
if ((result == false) && (buf2[0])) {
result = openResourceFile(buf2, encByte);
// We have .man files so set demo mode
if (_gameId == GID_MANIAC)
_demoMode = true;
}
if (result) {
if (room == 0)
return;
if (_features & GF_EXTERNAL_CHARSET && room >= roomlimit)
return;
readRoomsOffsets();
_fileOffset = res.roomoffs[rtRoom][room];
if (_fileOffset != 8)
return;
error("Room %d not in %s", room, buf);
return;
}
askForDisk(buf, room == 0 ? 0 : res.roomno[rtRoom][room]);
}
do {
sprintf(buf, "%.3d.lfl", room);
encByte = 0;
if (openResourceFile(buf, encByte))
break;
askForDisk(buf, room == 0 ? 0 : res.roomno[rtRoom][room]);
} while (1);
deleteRoomOffsets();
_fileOffset = 0; // start of file
}
void ScummEngine::closeRoom() {
if (_lastLoadedRoom != -1) {
_lastLoadedRoom = -1;
deleteRoomOffsets();
_fileHandle.close();
}
}
/** Delete the currently loaded room offsets. */
void ScummEngine::deleteRoomOffsets() {
if (!(_features & GF_SMALL_HEADER) && !_dynamicRoomOffsets)
return;
for (int i = 0; i < _numRooms; i++) {
if (res.roomoffs[rtRoom][i] != 0xFFFFFFFF)
res.roomoffs[rtRoom][i] = 0;
}
}
/** Read room offsets */
void ScummEngine::readRoomsOffsets() {
int num, room, i;
byte *ptr;
debug(9, "readRoomOffsets()");
deleteRoomOffsets();
if (_features & GF_SMALL_NAMES)
return;
if (_heversion >= 70) { // Windows titles
num = READ_LE_UINT16(_heV7RoomOffsets);
ptr = _heV7RoomOffsets + 2;
for (i = 0; i < num; i++) {
res.roomoffs[rtRoom][i] = READ_LE_UINT32(ptr);
ptr += 4;
}
return;
}
if (!(_features & GF_SMALL_HEADER)) {
if (!_dynamicRoomOffsets)
return;
_fileHandle.seek(16, SEEK_SET);
} else {
_fileHandle.seek(12, SEEK_SET); // Directly searching for the room offset block would be more generic...
}
num = _fileHandle.readByte();
while (num--) {
room = _fileHandle.readByte();
if (res.roomoffs[rtRoom][room] != 0xFFFFFFFF) {
res.roomoffs[rtRoom][room] = _fileHandle.readUint32LE();
} else {
_fileHandle.readUint32LE();
}
}
}
bool ScummEngine::openFile(ScummFile &file, const char *filename) {
bool result = false;
if (!_containerFile.isEmpty()) {
file.close();
file.open(_containerFile.c_str());
assert(file.isOpen());
result = file.openSubFile(filename);
}
if (!result) {
file.close();
result = file.open(filename);
}
return result;
}
bool ScummEngine::openResourceFile(const char *filename, byte encByte) {
debugC(DEBUG_GENERAL, "openResourceFile(%s)", filename);
if (openFile(_fileHandle, filename)) {
_fileHandle.setEnc(encByte);
return true;
}
return false;
}
void ScummEngine::askForDisk(const char *filename, int disknum) {
char buf[128];
if (_version == 8) {
char result;
_imuseDigital->stopAllSounds();
#ifdef MACOSX
sprintf(buf, "Cannot find file: '%s'\nPlease insert disc %d.\nPress OK to retry, Quit to exit", filename, disknum);
#else
sprintf(buf, "Cannot find file: '%s'\nInsert disc %d into drive %s\nPress OK to retry, Quit to exit", filename, disknum, _gameDataPath.c_str());
#endif
result = displayMessage("Quit", buf);
if (!result) {
error("Cannot find file: '%s'", filename);
}
} else {
sprintf(buf, "Cannot find file: '%s'", filename);
InfoDialog dialog(this, (char*)buf);
runDialog(dialog);
error("Cannot find file: '%s'", filename);
}
}
void ScummEngine::readIndexFile() {
uint32 blocktype, itemsize;
int numblock = 0;
int num, i;
bool stop = false;
debugC(DEBUG_GENERAL, "readIndexFile()");
closeRoom();
openRoom(0);
if (_version <= 5) {
/* Figure out the sizes of various resources */
while (!_fileHandle.eof()) {
blocktype = fileReadDword();
itemsize = _fileHandle.readUint32BE();
if (_fileHandle.ioFailed())
break;
switch (blocktype) {
case MKID('DOBJ'):
_numGlobalObjects = _fileHandle.readUint16LE();
itemsize -= 2;
break;
case MKID('DROO'):
_numRooms = _fileHandle.readUint16LE();
itemsize -= 2;
break;
case MKID('DSCR'):
_numScripts = _fileHandle.readUint16LE();
itemsize -= 2;
break;
case MKID('DCOS'):
_numCostumes = _fileHandle.readUint16LE();
itemsize -= 2;
break;
case MKID('DSOU'):
_numSounds = _fileHandle.readUint16LE();
itemsize -= 2;
break;
}
_fileHandle.seek(itemsize - 8, SEEK_CUR);
}
_fileHandle.clearIOFailed();
_fileHandle.seek(0, SEEK_SET);
}
while (!stop) {
blocktype = fileReadDword();
if (_fileHandle.ioFailed())
break;
itemsize = _fileHandle.readUint32BE();
numblock++;
switch (blocktype) {
case MKID('DCHR'):
case MKID('DIRF'):
readResTypeList(rtCharset, MKID('CHAR'), "charset");
break;
case MKID('DOBJ'):
debug(9, "found DOBJ block, reading object table");
if (_version == 8)
num = _fileHandle.readUint32LE();
else
num = _fileHandle.readUint16LE();
assert(num == _numGlobalObjects);
if (_version == 8) { /* FIXME: Not sure.. */
char buffer[40];
for (i = 0; i < num; i++) {
_fileHandle.read(buffer, 40);
if (buffer[0]) {
// Add to object name-to-id map
_objectIDMap[buffer] = i;
}
_objectStateTable[i] = _fileHandle.readByte();
_objectRoomTable[i] = _fileHandle.readByte();
_classData[i] = _fileHandle.readUint32LE();
}
memset(_objectOwnerTable, 0xFF, num);
} else if (_version == 7) {
_fileHandle.read(_objectStateTable, num);
_fileHandle.read(_objectRoomTable, num);
memset(_objectOwnerTable, 0xFF, num);
} else if (_heversion >= 70) { // HE Windows titles
_fileHandle.read(_objectStateTable, num);
_fileHandle.read(_objectOwnerTable, num);
_fileHandle.read(_objectRoomTable, num);
} else {
_fileHandle.read(_objectOwnerTable, num);
for (i = 0; i < num; i++) {
_objectStateTable[i] = _objectOwnerTable[i] >> OF_STATE_SHL;
_objectOwnerTable[i] &= OF_OWNER_MASK;
}
}
if (_version != 8) {
_fileHandle.read(_classData, num * sizeof(uint32));
// Swap flag endian where applicable
#if defined(SCUMM_BIG_ENDIAN)
for (i = 0; i != num; i++)
_classData[i] = FROM_LE_32(_classData[i]);
#endif
}
break;
case MKID('RNAM'):
// Names of rooms
_fileHandle.seek(itemsize - 8, SEEK_CUR);
debug(9, "found RNAM block, skipping");
break;
case MKID('DLFL'):
i = _fileHandle.readUint16LE();
_fileHandle.seek(-2, SEEK_CUR);
_heV7RoomOffsets = (byte *)calloc(2 + (i * 4), 1);
_fileHandle.read(_heV7RoomOffsets, (2 + (i * 4)) );
break;
case MKID('DIRM'):
readResTypeList(rtImage, MKID('AWIZ'), "images");
break;
case MKID('DIRT'):
readResTypeList(rtTalkie, MKID('TLKE'), "talkie");
break;
case MKID('SVER'):
_fileHandle.seek(itemsize - 8, SEEK_CUR);
warning("SVER index block not yet handled, skipping");
break;
case MKID('DISK'):
_fileHandle.seek(itemsize - 8, SEEK_CUR);
debug(2, "DISK index block not yet handled, skipping");
break;
case MKID('INIB'):
_fileHandle.seek(itemsize - 8, SEEK_CUR);
debug(2, "INIB index block not yet handled, skipping");
break;
case MKID('DIRI'):
readResTypeList(rtRoomImage, MKID('RMIM'), "room image");
break;
case MKID('ANAM'): // Used by: The Dig, FT
debug(9, "found ANAM block, reading audio names");
_numAudioNames = _fileHandle.readUint16LE();
_audioNames = (char*)malloc(_numAudioNames * 9);
_fileHandle.read(_audioNames, _numAudioNames * 9);
break;
case MKID('DIRR'):
case MKID('DROO'):
readResTypeList(rtRoom, MKID('ROOM'), "room");
break;
case MKID('DRSC'):
readResTypeList(rtRoomScripts, MKID('RMSC'), "room script");
break;
case MKID('DSCR'):
case MKID('DIRS'):
readResTypeList(rtScript, MKID('SCRP'), "script");
break;
case MKID('DCOS'):
case MKID('DIRC'):
readResTypeList(rtCostume, MKID('COST'), "costume");
break;
case MKID('MAXS'):
readMAXS(itemsize);
break;
case MKID('DIRN'):
case MKID('DSOU'):
readResTypeList(rtSound, MKID('SOUN'), "sound");
break;
case MKID('AARY'):
readArrayFromIndexFile();
break;
case MKID('LECF'):
_fileHandle.seek(itemsize - 8, SEEK_CUR);
debug(2, "LECF index block not yet handled, skipping");
break;
default:
error("Bad ID %04X('%s') found in index file directory!", blocktype,
tag2str(blocktype));
return;
}
}
// if (numblock!=9)
// error("Not enough blocks read from directory");
closeRoom();
}
void ScummEngine::readArrayFromIndexFile() {
error("readArrayFromIndexFile() not supported in pre-V6 games");
}
void ScummEngine::readResTypeList(int id, uint32 tag, const char *name) {
int num;
int i;
debug(9, "readResTypeList(%s,%s,%s)", resTypeFromId(id), tag2str(TO_BE_32(tag)), name);
if (_version == 8)
num = _fileHandle.readUint32LE();
else if (!(_features & GF_OLD_BUNDLE))
num = _fileHandle.readUint16LE();
else
num = _fileHandle.readByte();
if (_features & GF_OLD_BUNDLE) {
if (num >= 0xFF) {
error("Too many %ss (%d) in directory", name, num);
}
} else {
if (num != res.num[id]) {
error("Invalid number of %ss (%d) in directory", name, num);
}
}
if (_features & GF_OLD_BUNDLE) {
if (id == rtRoom) {
for (i = 0; i < num; i++)
res.roomno[id][i] = i;
_fileHandle.seek(num, SEEK_CUR);
} else {
for (i = 0; i < num; i++)
res.roomno[id][i] = _fileHandle.readByte();
}
for (i = 0; i < num; i++) {
res.roomoffs[id][i] = _fileHandle.readUint16LE();
if (res.roomoffs[id][i] == 0xFFFF)
res.roomoffs[id][i] = 0xFFFFFFFF;
}
} else if (_features & GF_SMALL_HEADER) {
for (i = 0; i < num; i++) {
res.roomno[id][i] = _fileHandle.readByte();
res.roomoffs[id][i] = _fileHandle.readUint32LE();
}
} else {
for (i = 0; i < num; i++) {
res.roomno[id][i] = _fileHandle.readByte();
}
for (i = 0; i < num; i++) {
res.roomoffs[id][i] = _fileHandle.readUint32LE();
if (id == rtRoom && _heversion >= 70)
_heV7RoomIntOffsets[i] = res.roomoffs[id][i];
}
if (_heversion >= 70) {
for (i = 0; i < num; i++) {
res.globsize[id][i] = _fileHandle.readUint32LE();
}
}
}
}
void ScummEngine::allocResTypeData(int id, uint32 tag, int num, const char *name, int mode) {
debug(9, "allocResTypeData(%s/%s,%s,%d,%d)", resTypeFromId(id), name, tag2str(TO_BE_32(tag)), num, mode);
assert(id >= 0 && id < (int)(ARRAYSIZE(res.mode)));
if (num >= 8000)
error("Too many %ss (%d) in directory", name, num);
res.mode[id] = mode;
res.num[id] = num;
res.tags[id] = tag;
res.name[id] = name;
res.address[id] = (byte **)calloc(num, sizeof(void *));
res.flags[id] = (byte *)calloc(num, sizeof(byte));
if (mode) {
res.roomno[id] = (byte *)calloc(num, sizeof(byte));
res.roomoffs[id] = (uint32 *)calloc(num, sizeof(uint32));
}
if (_heversion >= 70) {
res.globsize[id] = (uint32 *)calloc(num, sizeof(uint32));
if (id == rtRoom)
_heV7RoomIntOffsets = (uint32 *)calloc(num, sizeof(uint32));
}
}
void ScummEngine::loadCharset(int no) {
int i;
byte *ptr;
debugC(DEBUG_GENERAL, "loadCharset(%d)", no);
/* FIXME - hack around crash in Indy4 (occurs if you try to load after dieing) */
if (_gameId == GID_INDY4 && no == 0)
no = 1;
/* for Humongous catalogs */
if (_heversion >= 70 && _numCharsets == 1) {
warning("not loading charset as it doesn't seem to exist?");
return;
}
assert(no < (int)sizeof(_charsetData) / 16);
checkRange(_numCharsets - 1, 1, no, "Loading illegal charset %d");
// ensureResourceLoaded(rtCharset, no);
ptr = getResourceAddress(rtCharset, no);
if (_features & GF_SMALL_HEADER)
ptr -= 12;
for (i = 0; i < 15; i++) {
_charsetData[no][i + 1] = ptr[i + 14];
}
}
void ScummEngine::nukeCharset(int i) {
checkRange(_numCharsets - 1, 1, i, "Nuking illegal charset %d");
nukeResource(rtCharset, i);
}
void ScummEngine::ensureResourceLoaded(int type, int i) {
void *addr = NULL;
debugC(DEBUG_RESOURCE, "ensureResourceLoaded(%s,%d)", resTypeFromId(type), i);
if ((type == rtRoom) && i > 0x7F && _version < 7) {
i = _resourceMapper[i & 0x7F];
}
// FIXME - TODO: This check used to be "i==0". However, that causes
// problems when using this function to ensure charset 0 is loaded.
// This is done for many games, e.g. Zak256 or Indy3 (EGA and VGA).
// For now we restrict the check to anything which is not a charset.
// Question: Why was this check like that in the first place?
// Answer: costumes with an index of zero in the newer games at least.
// TODO: determine why the heck anything would try to load a costume
// with id 0. Is that "normal", or is it caused by yet another bug in
// our code base? After all we also have to add special cases for many
// of our script opcodes that check for the (invalid) actor 0... so
// maybe both issues are related...
if (type != rtCharset && i == 0)
return;
if (i <= res.num[type])
addr = res.address[type][i];
if (addr)
return;
loadResource(type, i);
if (_version == 5 && type == rtRoom && i == _roomResource)
VAR(VAR_ROOM_FLAG) = 1;
}
int ScummEngine::loadResource(int type, int idx) {
int roomNr;
uint32 fileOffs;
uint32 size, tag;
debugC(DEBUG_RESOURCE, "loadResource(%s,%d)", resTypeFromId(type),idx);
if (type == rtCharset && (_features & GF_SMALL_HEADER)) {
loadCharset(idx);
return (1);
}
roomNr = getResourceRoomNr(type, idx);
if (idx >= res.num[type])
error("%s %d undefined %d %d", res.name[type], idx, res.num[type], roomNr);
if (roomNr == 0)
roomNr = _roomResource;
if (type == rtRoom) {
if (_version == 8)
fileOffs = 8;
else if (_heversion >= 70)
fileOffs = _heV7RoomIntOffsets[idx];
else
fileOffs = 0;
} else {
fileOffs = res.roomoffs[type][idx];
if (fileOffs == 0xFFFFFFFF)
return 0;
}
openRoom(roomNr);
_fileHandle.seek(fileOffs + _fileOffset, SEEK_SET);
if (_features & GF_OLD_BUNDLE) {
if ((_version == 3) && !(_features & GF_AMIGA) && (type == rtSound)) {
return readSoundResourceSmallHeader(type, idx);
} else {
size = _fileHandle.readUint16LE();
_fileHandle.seek(-2, SEEK_CUR);
}
} else if (_features & GF_SMALL_HEADER) {
if (!(_features & GF_SMALL_NAMES))
_fileHandle.seek(8, SEEK_CUR);
size = _fileHandle.readUint32LE();
tag = _fileHandle.readUint16LE();
_fileHandle.seek(-6, SEEK_CUR);
if ((type == rtSound) && !(_features & GF_AMIGA) && !(_features & GF_FMTOWNS)) {
return readSoundResourceSmallHeader(type, idx);
}
} else {
if (type == rtSound) {
return readSoundResource(type, idx);
}
tag = fileReadDword();
if (tag != res.tags[type] && _heversion < 70) {
error("%s %d not in room %d at %d+%d in file %s",
res.name[type], idx, roomNr,
_fileOffset, fileOffs, _fileHandle.name());
}
size = _fileHandle.readUint32BE();
_fileHandle.seek(-8, SEEK_CUR);
}
_fileHandle.read(createResource(type, idx, size), size);
// dump the resource if requested
if (_dumpScripts && type == rtScript) {
dumpResource("script-", idx, getResourceAddress(rtScript, idx));
}
if (!_fileHandle.ioFailed()) {
return 1;
}
nukeResource(type, idx);
error("Cannot read resource");
}
int ScummEngine::getResourceRoomNr(int type, int idx) {
if (type == rtRoom && _heversion < 70)
return idx;
return res.roomno[type][idx];
}
int ScummEngine::getResourceSize(int type, int idx) {
byte *ptr = getResourceAddress(type, idx);
MemBlkHeader *hdr = (MemBlkHeader *)(ptr - sizeof(MemBlkHeader));
return hdr->size;
}
byte *ScummEngine::getResourceAddress(int type, int idx) {
byte *ptr;
CHECK_HEAP
if (!validateResource("getResourceAddress", type, idx))
return NULL;
if (!res.address[type]) {
debugC(DEBUG_RESOURCE, "getResourceAddress(%s,%d), res.address[type] == NULL", resTypeFromId(type), idx);
return NULL;
}
if (res.mode[type] && !res.address[type][idx]) {
ensureResourceLoaded(type, idx);
}
if (!(ptr = (byte *)res.address[type][idx])) {
debugC(DEBUG_RESOURCE, "getResourceAddress(%s,%d) == NULL", resTypeFromId(type), idx);
return NULL;
}
setResourceCounter(type, idx, 1);
debugC(DEBUG_RESOURCE, "getResourceAddress(%s,%d) == %p", resTypeFromId(type), idx, ptr + sizeof(MemBlkHeader));
return ptr + sizeof(MemBlkHeader);
}
byte *ScummEngine::getStringAddress(int i) {
byte *addr = getResourceAddress(rtString, i);
if (addr == NULL)
return NULL;
if (_heversion >= 72)
return (addr + 0x14); // ArrayHeader->data
if (_features & GF_NEW_OPCODES)
return (addr + 0x6); // ArrayHeader->data
return addr;
}
byte *ScummEngine::getStringAddressVar(int i) {
return getStringAddress(_scummVars[i]);
}
void ScummEngine::setResourceCounter(int type, int idx, byte flag) {
res.flags[type][idx] &= ~RF_USAGE;
res.flags[type][idx] |= flag;
}
/* 2 bytes safety area to make "precaching" of bytes in the gdi drawer easier */
#define SAFETY_AREA 2
byte *ScummEngine::createResource(int type, int idx, uint32 size) {
byte *ptr;
CHECK_HEAP
debugC(DEBUG_RESOURCE, "createResource(%s,%d,%d)", resTypeFromId(type), idx, size);
if (!validateResource("allocating", type, idx))
return NULL;
nukeResource(type, idx);
expireResources(size);
CHECK_HEAP
ptr = (byte *)calloc(size + sizeof(MemBlkHeader) + SAFETY_AREA, 1);
if (ptr == NULL) {
error("Out of memory while allocating %d", size);
}
_allocatedSize += size;
res.address[type][idx] = ptr;
((MemBlkHeader *)ptr)->size = size;
setResourceCounter(type, idx, 1);
return ptr + sizeof(MemBlkHeader); /* skip header */
}
bool ScummEngine::validateResource(const char *str, int type, int idx) const {
if (type < rtFirst || type > rtLast || (uint) idx >= (uint) res.num[type]) {
warning("%s Illegal Glob type %s (%d) num %d", str, resTypeFromId(type), type, idx);
return false;
}
return true;
}
void ScummEngine::nukeResource(int type, int idx) {
byte *ptr;
CHECK_HEAP
if (!res.address[type])
return;
assert(idx >= 0 && idx < res.num[type]);
if ((ptr = res.address[type][idx]) != NULL) {
debugC(DEBUG_RESOURCE, "nukeResource(%s,%d)", resTypeFromId(type), idx);
res.address[type][idx] = 0;
res.flags[type][idx] = 0;
_allocatedSize -= ((MemBlkHeader *)ptr)->size;
free(ptr);
}
}
const byte *ScummEngine::findResourceData(uint32 tag, const byte *ptr) {
if (_features & GF_OLD_BUNDLE)
error("findResourceData must not be used in GF_OLD_BUNDLE games");
else if (_features & GF_SMALL_HEADER)
ptr = findResourceSmall(tag, ptr);
else
ptr = findResource(tag, ptr);
if (ptr == NULL)
return NULL;
return ptr + _resourceHeaderSize;
}
int ScummEngine::getResourceDataSize(const byte *ptr) const {
if (ptr == NULL)
return 0;
if (_features & GF_OLD_BUNDLE)
return READ_LE_UINT16(ptr) - 4;
else if (_features & GF_SMALL_HEADER)
return READ_LE_UINT32(ptr) - 6;
else
return READ_BE_UINT32(ptr - 4) - 8;
}
void ScummEngine::lock(int type, int i) {
if (!validateResource("Locking", type, i))
return;
res.flags[type][i] |= RF_LOCK;
}
void ScummEngine::unlock(int type, int i) {
if (!validateResource("Unlocking", type, i))
return;
res.flags[type][i] &= ~RF_LOCK;
}
bool ScummEngine::isResourceInUse(int type, int i) const {
if (!validateResource("isResourceInUse", type, i))
return false;
switch (type) {
case rtRoom:
return _roomResource == (byte)i;
case rtRoomScripts:
return _roomResource == (byte)i;
case rtScript:
return isScriptInUse(i);
case rtCostume:
return isCostumeInUse(i);
case rtSound:
return _sound->isSoundInUse(i);
default:
return false;
}
}
void ScummEngine::increaseResourceCounter() {
int i, j;
byte counter;
for (i = rtFirst; i <= rtLast; i++) {
for (j = res.num[i]; --j >= 0;) {
counter = res.flags[i][j] & RF_USAGE;
if (counter && counter < RF_USAGE_MAX) {
setResourceCounter(i, j, counter + 1);
}
}
}
}
void ScummEngine::expireResources(uint32 size) {
int i, j;
byte flag;
byte best_counter;
int best_type, best_res = 0;
uint32 oldAllocatedSize;
if (_expire_counter != 0xFF) {
_expire_counter = 0xFF;
increaseResourceCounter();
}
if (size + _allocatedSize < _maxHeapThreshold)
return;
oldAllocatedSize = _allocatedSize;
do {
best_type = 0;
best_counter = 2;
for (i = rtFirst; i <= rtLast; i++)
if (res.mode[i]) {
for (j = res.num[i]; --j >= 0;) {
flag = res.flags[i][j];
if (!(flag & RF_LOCK) && flag >= best_counter && res.address[i][j] && !isResourceInUse(i, j)) {
best_counter = flag;
best_type = i;
best_res = j;
}
}
}
if (!best_type)
break;
nukeResource(best_type, best_res);
} while (size + _allocatedSize > _minHeapThreshold);
increaseResourceCounter();
debugC(DEBUG_RESOURCE, "Expired resources, mem %d -> %d", oldAllocatedSize, _allocatedSize);
}
void ScummEngine::freeResources() {
int i, j;
for (i = rtFirst; i <= rtLast; i++) {
for (j = res.num[i]; --j >= 0;) {
if (isResourceLoaded(i, j))
nukeResource(i, j);
}
free(res.address[i]);
free(res.flags[i]);
free(res.roomno[i]);
free(res.roomoffs[i]);
if (_heversion >= 70)
free(res.globsize[i]);
}
if (_heversion >= 70) {
free(_heV7RoomIntOffsets);
free(_heV7RoomOffsets);
}
}
void ScummEngine::loadPtrToResource(int type, int resindex, const byte *source) {
byte *alloced;
int i, len;
nukeResource(type, resindex);
len = resStrLen(source) + 1;
if (len <= 0)
return;
alloced = createResource(type, resindex, len);
if (!source) {
alloced[0] = fetchScriptByte();
for (i = 1; i < len; i++)
alloced[i] = *_scriptPointer++;
} else {
for (i = 0; i < len; i++)
alloced[i] = source[i];
}
}
bool ScummEngine::isResourceLoaded(int type, int idx) const {
if (!validateResource("isResourceLoaded", type, idx))
return false;
return res.address[type][idx] != NULL;
}
void ScummEngine::resourceStats() {
int i, j;
uint32 lockedSize = 0, lockedNum = 0;
byte flag;
for (i = rtFirst; i <= rtLast; i++)
for (j = res.num[i]; --j >= 0;) {
flag = res.flags[i][j];
if (flag & RF_LOCK && res.address[i][j]) {
lockedSize += ((MemBlkHeader *)res.address[i][j])->size;
lockedNum++;
}
}
debug(1, "Total allocated size=%d, locked=%d(%d)", _allocatedSize, lockedSize, lockedNum);
}
void ScummEngine::readMAXS(int blockSize) {
debug(9, "readMAXS: MAXS has blocksize %d", blockSize);
if (_version == 8) { // CMI
_fileHandle.seek(50 + 50, SEEK_CUR); // 176 - 8
_numVariables = _fileHandle.readUint32LE(); // 1500
_numBitVariables = _fileHandle.readUint32LE(); // 2048
_fileHandle.readUint32LE(); // 40
_numScripts = _fileHandle.readUint32LE(); // 458
_numSounds = _fileHandle.readUint32LE(); // 789
_numCharsets = _fileHandle.readUint32LE(); // 1
_numCostumes = _fileHandle.readUint32LE(); // 446
_numRooms = _fileHandle.readUint32LE(); // 95
_fileHandle.readUint32LE(); // 80
_numGlobalObjects = _fileHandle.readUint32LE(); // 1401
_fileHandle.readUint32LE(); // 60
_numLocalObjects = _fileHandle.readUint32LE(); // 200
_numNewNames = _fileHandle.readUint32LE(); // 100
_numFlObject = _fileHandle.readUint32LE(); // 128
_numInventory = _fileHandle.readUint32LE(); // 80
_numArray = _fileHandle.readUint32LE(); // 200
_numVerbs = _fileHandle.readUint32LE(); // 50
_objectRoomTable = (byte *)calloc(_numGlobalObjects, 1);
_numGlobalScripts = 2000;
_shadowPaletteSize = NUM_SHADOW_PALETTE * 256;
} else if (_version == 7) {
_fileHandle.seek(50 + 50, SEEK_CUR);
_numVariables = _fileHandle.readUint16LE();
_numBitVariables = _fileHandle.readUint16LE();
_fileHandle.readUint16LE(); // 40 in FT; 16 in Dig
_numGlobalObjects = _fileHandle.readUint16LE();
_numLocalObjects = _fileHandle.readUint16LE();
_numNewNames = _fileHandle.readUint16LE();
_numVerbs = _fileHandle.readUint16LE();
_numFlObject = _fileHandle.readUint16LE();
_numInventory = _fileHandle.readUint16LE();
_numArray = _fileHandle.readUint16LE();
_numRooms = _fileHandle.readUint16LE();
_numScripts = _fileHandle.readUint16LE();
_numSounds = _fileHandle.readUint16LE();
_numCharsets = _fileHandle.readUint16LE();
_numCostumes = _fileHandle.readUint16LE();
_objectRoomTable = (byte *)calloc(_numGlobalObjects, 1);
if ((_gameId == GID_FT) && (_features & GF_DEMO) &&
(_features & GF_PC))
_numGlobalScripts = 300;
else
_numGlobalScripts = 2000;
_shadowPaletteSize = NUM_SHADOW_PALETTE * 256;
} else if (_heversion >= 70 && (blockSize == 44 + 8)) { // C++ based engine
_numVariables = _fileHandle.readUint16LE();
_fileHandle.readUint16LE();
_numRoomVariables = _fileHandle.readUint16LE();
_numLocalObjects = _fileHandle.readUint16LE();
_numArray = _fileHandle.readUint16LE();
_fileHandle.readUint16LE(); // unknown
_fileHandle.readUint16LE(); // unknown
_numFlObject = _fileHandle.readUint16LE();
_numInventory = _fileHandle.readUint16LE();
_numRooms = _fileHandle.readUint16LE();
_numScripts = _fileHandle.readUint16LE();
_numSounds = _fileHandle.readUint16LE();
_numCharsets = _fileHandle.readUint16LE();
_numCostumes = _fileHandle.readUint16LE();
_numGlobalObjects = _fileHandle.readUint16LE();
_numImages = _fileHandle.readUint16LE();
_numSprites = _fileHandle.readUint16LE();
_numLocalScripts = _fileHandle.readUint16LE();
_fileHandle.readUint16LE(); // heap related
_numPalettes = _fileHandle.readUint16LE();
_numUnk = _fileHandle.readUint16LE();
_numTalkies = _fileHandle.readUint16LE();
_numNewNames = 10;
_objectRoomTable = (byte *)calloc(_numGlobalObjects, 1);
_numGlobalScripts = 2048;
} else if (_heversion >= 70 && (blockSize == 38 + 8)) { // Scummsys.9x
_numVariables = _fileHandle.readUint16LE();
_fileHandle.readUint16LE();
_numRoomVariables = _fileHandle.readUint16LE();
_numLocalObjects = _fileHandle.readUint16LE();
_numArray = _fileHandle.readUint16LE();
_fileHandle.readUint16LE(); // unknown
_fileHandle.readUint16LE(); // unknown
_numFlObject = _fileHandle.readUint16LE();
_numInventory = _fileHandle.readUint16LE();
_numRooms = _fileHandle.readUint16LE();
_numScripts = _fileHandle.readUint16LE();
_numSounds = _fileHandle.readUint16LE();
_numCharsets = _fileHandle.readUint16LE();
_numCostumes = _fileHandle.readUint16LE();
_numGlobalObjects = _fileHandle.readUint16LE();
_numImages = _fileHandle.readUint16LE();
_numSprites = _fileHandle.readUint16LE();
_numLocalScripts = _fileHandle.readUint16LE();
_fileHandle.readUint16LE(); // heap releated
_numNewNames = 10;
_objectRoomTable = (byte *)calloc(_numGlobalObjects, 1);
if (_gameId == GID_FREDDI4)
_numGlobalScripts = 2048;
else
_numGlobalScripts = 200;
} else if (_heversion >= 70 && blockSize > 38) { // sputm7.2
if (blockSize != 32 + 8)
error("MAXS block of size %d not supported, please report", blockSize);
_numVariables = _fileHandle.readUint16LE();
_fileHandle.readUint16LE();
_numBitVariables = _numRoomVariables = _fileHandle.readUint16LE();
_numLocalObjects = _fileHandle.readUint16LE();
_numArray = _fileHandle.readUint16LE();
_fileHandle.readUint16LE();
_numVerbs = _fileHandle.readUint16LE();
_numFlObject = _fileHandle.readUint16LE();
_numInventory = _fileHandle.readUint16LE();
_numRooms = _fileHandle.readUint16LE();
_numScripts = _fileHandle.readUint16LE();
_numSounds = _fileHandle.readUint16LE();
_numCharsets = _fileHandle.readUint16LE();
_numCostumes = _fileHandle.readUint16LE();
_numGlobalObjects = _fileHandle.readUint16LE();
_numImages = _fileHandle.readUint16LE();
_numNewNames = 10;
_objectRoomTable = (byte *)calloc(_numGlobalObjects, 1);
_numGlobalScripts = 200;
} else if (_version == 6) {
if (blockSize != 30 + 8)
error("MAXS block of size %d not supported", blockSize);
_numVariables = _fileHandle.readUint16LE();
_fileHandle.readUint16LE(); // 16 in Sam/DOTT
_numBitVariables = _fileHandle.readUint16LE();
_numLocalObjects = _fileHandle.readUint16LE();
_numArray = _fileHandle.readUint16LE();
_fileHandle.readUint16LE(); // 0 in Sam/DOTT
_numVerbs = _fileHandle.readUint16LE();
_numFlObject = _fileHandle.readUint16LE();
_numInventory = _fileHandle.readUint16LE();
_numRooms = _fileHandle.readUint16LE();
_numScripts = _fileHandle.readUint16LE();
_numSounds = _fileHandle.readUint16LE();
_numCharsets = _fileHandle.readUint16LE();
_numCostumes = _fileHandle.readUint16LE();
_numGlobalObjects = _fileHandle.readUint16LE();
_numNewNames = 50;
_objectRoomTable = NULL;
_numGlobalScripts = 200;
_shadowPaletteSize = 256;
if (_heversion >= 70) {
_objectRoomTable = (byte *)calloc(_numGlobalObjects, 1);
}
} else {
_numVariables = _fileHandle.readUint16LE(); // 800
_fileHandle.readUint16LE(); // 16
_numBitVariables = _fileHandle.readUint16LE(); // 2048
_numLocalObjects = _fileHandle.readUint16LE(); // 200
_numArray = 50;
_numVerbs = 100;
// Used to be 50, which wasn't enough for MI2 and FOA. See bugs
// #933610, #936323 and #941275.
_numNewNames = 150;
_objectRoomTable = NULL;
_fileHandle.readUint16LE(); // 50
_numCharsets = _fileHandle.readUint16LE(); // 9
_fileHandle.readUint16LE(); // 100
_fileHandle.readUint16LE(); // 50
_numInventory = _fileHandle.readUint16LE(); // 80
_numGlobalScripts = 200;
_shadowPaletteSize = 256;
_numFlObject = 50;
}
if (_shadowPaletteSize)
_shadowPalette = (byte *)calloc(_shadowPaletteSize, 1);
allocateArrays();
_dynamicRoomOffsets = true;
}
void ScummEngine::allocateArrays() {
// Note: Buffers are now allocated in scummMain to allow for
// early GUI init.
_objectOwnerTable = (byte *)calloc(_numGlobalObjects, 1);
_objectStateTable = (byte *)calloc(_numGlobalObjects, 1);
_classData = (uint32 *)calloc(_numGlobalObjects, sizeof(uint32));
_newNames = (uint16 *)calloc(_numNewNames, sizeof(uint16));
_inventory = (uint16 *)calloc(_numInventory, sizeof(uint16));
_verbs = (VerbSlot *)calloc(_numVerbs, sizeof(VerbSlot));
_objs = (ObjectData *)calloc(_numLocalObjects, sizeof(ObjectData));
_roomVars = (int32 *)calloc(_numRoomVariables, sizeof(int32));
_scummVars = (int32 *)calloc(_numVariables, sizeof(int32));
_bitVars = (byte *)calloc(_numBitVariables >> 3, 1);
if (_features & GF_HUMONGOUS)
_arraySlot = (byte *)calloc(_numArray, 1);
allocResTypeData(rtCostume, (_features & GF_NEW_COSTUMES) ? MKID('AKOS') : MKID('COST'),
_numCostumes, "costume", 1);
allocResTypeData(rtRoom, MKID('ROOM'), _numRooms, "room", 1);
allocResTypeData(rtRoomImage, MKID('RMIM'), _numRooms, "room image", 1);
allocResTypeData(rtRoomScripts, MKID('RMSC'), _numRooms, "room script", 1);
allocResTypeData(rtSound, MKID('SOUN'), _numSounds, "sound", 2);
allocResTypeData(rtScript, MKID('SCRP'), _numScripts, "script", 1);
allocResTypeData(rtCharset, MKID('CHAR'), _numCharsets, "charset", 1);
allocResTypeData(rtObjectName, MKID('NONE'), _numNewNames, "new name", 0);
allocResTypeData(rtInventory, MKID('NONE'), _numInventory, "inventory", 0);
allocResTypeData(rtTemp, MKID('NONE'), 10, "temp", 0);
allocResTypeData(rtScaleTable, MKID('NONE'), 5, "scale table", 0);
allocResTypeData(rtActorName, MKID('NONE'), _numActors, "actor name", 0);
allocResTypeData(rtVerb, MKID('NONE'), _numVerbs, "verb", 0);
allocResTypeData(rtString, MKID('NONE'), _numArray, "array", 0);
allocResTypeData(rtFlObject, MKID('NONE'), _numFlObject, "flobject", 0);
allocResTypeData(rtMatrix, MKID('NONE'), 10, "boxes", 0);
allocResTypeData(rtImage, MKID('AWIZ'), _numImages, "images", 1);
allocResTypeData(rtTalkie, MKID('TLKE'), _numTalkies, "talkie", 1);
}
void ScummEngine::dumpResource(const char *tag, int idx, const byte *ptr, int length) {
char buf[256];
File out;
uint32 size;
if (length >= 0)
size = length;
else if (_features & GF_OLD_BUNDLE)
size = READ_LE_UINT16(ptr);
else if (_features & GF_SMALL_HEADER)
size = READ_LE_UINT32(ptr);
else
size = READ_BE_UINT32(ptr + 4);
#if defined(MACOS_CARBON)
sprintf(buf, ":dumps:%s%d.dmp", tag, idx);
#else
sprintf(buf, "dumps/%s%d.dmp", tag, idx);
#endif
out.open(buf, File::kFileWriteMode);
if (out.isOpen() == false)
return;
out.write(ptr, size);
out.close();
}
ResourceIterator::ResourceIterator(const byte *searchin, bool smallHeader)
: _ptr(searchin), _smallHeader(smallHeader) {
assert(searchin);
if (_smallHeader) {
_size = READ_LE_UINT32(searchin);
_pos = 6;
_ptr = searchin + 6;
} else {
_size = READ_BE_UINT32(searchin + 4);
_pos = 8;
_ptr = searchin + 8;
}
}
const byte *ResourceIterator::findNext(uint32 tag) {
uint32 size = 0;
const byte *result = 0;
if (_smallHeader) {
uint16 smallTag = newTag2Old(tag);
do {
if (_pos >= _size)
return 0;
result = _ptr;
size = READ_LE_UINT32(result);
if ((int32)size <= 0)
return 0; // Avoid endless loop
_pos += size;
_ptr += size;
} while (READ_LE_UINT16(result + 4) != smallTag);
} else {
do {
if (_pos >= _size)
return 0;
result = _ptr;
size = READ_BE_UINT32(result + 4);
if ((int32)size <= 0)
return 0; // Avoid endless loop
_pos += size;
_ptr += size;
} while (READ_UINT32(result) != tag);
}
return result;
}
const byte *ScummEngine::findResource(uint32 tag, const byte *searchin) {
uint32 curpos, totalsize, size;
debugC(DEBUG_RESOURCE, "findResource(%s, %lx)", tag2str(tag), searchin);
if (!searchin) {
if (_heversion >= 70) {
searchin = _resourceLastSearchBuf;
totalsize = _resourceLastSearchSize;
curpos = 0;
} else {
assert(searchin);
return NULL;
}
} else {
searchin += 4;
_resourceLastSearchSize = totalsize = READ_BE_UINT32(searchin);
curpos = 8;
searchin += 4;
}
while (curpos < totalsize) {
if (READ_UINT32(searchin) == tag) {
_resourceLastSearchBuf = searchin;
return searchin;
}
size = READ_BE_UINT32(searchin + 4);
if ((int32)size <= 0) {
error("(%s) Not found in %d... illegal block len %d", tag2str(tag), 0, size);
return NULL;
}
curpos += size;
searchin += size;
}
return NULL;
}
const byte *findResourceSmall(uint32 tag, const byte *searchin) {
uint32 curpos, totalsize, size;
uint16 smallTag;
smallTag = newTag2Old(tag);
if (smallTag == 0)
return NULL;
assert(searchin);
totalsize = READ_LE_UINT32(searchin);
searchin += 6;
curpos = 6;
while (curpos < totalsize) {
size = READ_LE_UINT32(searchin);
if (READ_LE_UINT16(searchin + 4) == smallTag)
return searchin;
if ((int32)size <= 0) {
error("(%s) Not found in %d... illegal block len %d", tag2str(tag), 0, size);
return NULL;
}
curpos += size;
searchin += size;
}
return NULL;
}
uint16 newTag2Old(uint32 oldTag) {
switch (oldTag) {
case (MKID('RMHD')):
return (0x4448); // HD
case (MKID('IM00')):
return (0x4D42); // BM
case (MKID('EXCD')):
return (0x5845); // EX
case (MKID('ENCD')):
return (0x4E45); // EN
case (MKID('SCAL')):
return (0x4153); // SA
case (MKID('LSCR')):
return (0x534C); // LS
case (MKID('OBCD')):
return (0x434F); // OC
case (MKID('OBIM')):
return (0x494F); // OI
case (MKID('SMAP')):
return (0x4D42); // BM
case (MKID('CLUT')):
return (0x4150); // PA
case (MKID('BOXD')):
return (0x5842); // BX
case (MKID('CYCL')):
return (0x4343); // CC
case (MKID('EPAL')):
return (0x5053); // SP
default:
return (0);
}
}
const char *resTypeFromId(int id) {
static char buf[100];
switch (id) {
case rtRoom:
return "Room";
case rtScript:
return "Script";
case rtCostume:
return "Costume";
case rtSound:
return "Sound";
case rtInventory:
return "Inventory";
case rtCharset:
return "Charset";
case rtString:
return "String";
case rtVerb:
return "Verb";
case rtActorName:
return "ActorName";
case rtBuffer:
return "Buffer";
case rtScaleTable:
return "ScaleTable";
case rtTemp:
return "Temp";
case rtFlObject:
return "FlObject";
case rtMatrix:
return "Matrix";
case rtBox:
return "Box";
case rtObjectName:
return "ObjectName";
case rtRoomScripts:
return "RoomScripts";
case rtRoomImage:
return "RoomImage";
case rtImage:
return "Image";
case rtTalkie:
return "Talkie";
case rtNumTypes:
return "NumTypes";
default:
sprintf(buf, "%d", id);
return buf;
}
}
} // End of namespace Scumm