/* 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 && _heversion < 98) {
			_fileOffset = res.roomoffs[rtRoom][room];
		}
		if (!(_features & GF_SMALL_HEADER)) {

			if (_heversion >= 70) { // Windows titles
				if (_heversion >= 98) {
					int disk = 0;
					if (_heV7DiskOffsets)
						disk = _heV7DiskOffsets[room];

					switch(disk) {
					case 2:
						sprintf(buf, "%s.%s", _gameName.c_str(), "(b)");
						break;
					case 1:
						sprintf(buf, "%s.%s", _gameName.c_str(), "(a)");
						break;
					default:
						sprintf(buf, "%s.%s", _gameName.c_str(), "he0");
					}
				} 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 (_substResFileNameIndex > 0) {
			char tmpBuf[128];

			generateSubstResFileName(buf, tmpBuf, 128, 0, _substResFileNameIndex);
			strcpy(buf, tmpBuf);
			generateSubstResFileName(buf2, tmpBuf, 128, 0, _substResFileNameIndex);
			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'):
			i = _fileHandle.readUint16LE();
			_heV7DiskOffsets = (byte *)calloc(i, 1);
			_fileHandle.read(_heV7DiskOffsets, i);
			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