[Scummvm-git-logs] scummvm master -> 898068b9518588953288c5bcfafb2679f780963f

fracturehill noreply at scummvm.org
Thu Oct 26 16:51:51 UTC 2023


This automated email contains information about 10 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
fc0988fdfc COMMON: Allow IFF containers to have custom header IDs
74307deca6 NANCY: Rewrite resource handling
172984be7b NANCY: Fix nancy2 dialogue freeze
f0ba64cc08 NANCY: Add ciftree_export console command
97f4938bb2 NANCY: Add keymap for displaying raycast maze map
be0feffb55 NANCY: Load Autotext surfaces from ResourceManager
e6c915186d NANCY: Add missing override in Console
c86abc68a0 NANCY: Implement nancy7 LIFO autotext entries
bbd760ddc1 NANCY: Enum cleanup
898068b951 NANCY: Cursor dependency improvements


Commit: fc0988fdfcc62065a3617d135872d4e3d5ca30da
    https://github.com/scummvm/scummvm/commit/fc0988fdfcc62065a3617d135872d4e3d5ca30da
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-26T19:46:32+03:00

Commit Message:
COMMON: Allow IFF containers to have custom header IDs

The IFF format used in the Nancy Drew engine uses a
non-standard id that reads DATA instead of FORM, though
the rest of the format is identical. This commit allows such
nonstandard ids to be used without needing to massage the
data first.

Changed paths:
    common/formats/iff_container.cpp
    common/formats/iff_container.h


diff --git a/common/formats/iff_container.cpp b/common/formats/iff_container.cpp
index 718fcfa7b76..1bea8cd7da8 100644
--- a/common/formats/iff_container.cpp
+++ b/common/formats/iff_container.cpp
@@ -25,7 +25,7 @@
 
 namespace Common {
 
-IFFParser::IFFParser(ReadStream *stream, bool disposeStream) : _stream(stream), _disposeStream(disposeStream) {
+IFFParser::IFFParser(ReadStream *stream, bool disposeStream, IFF_ID formHeaderID) : _stream(stream), _disposeStream(disposeStream), _formHeaderID(formHeaderID) {
 	setInputStream(stream);
 }
 
@@ -42,7 +42,7 @@ void IFFParser::setInputStream(ReadStream *stream) {
 	_chunk.setInputStream(stream);
 
 	_formChunk.readHeader();
-	if (_formChunk.id != ID_FORM) {
+	if (_formChunk.id != _formHeaderID) {
 		error("IFFParser input is not a FORM type IFF file");
 	}
 	_formSize = _formChunk.size;
diff --git a/common/formats/iff_container.h b/common/formats/iff_container.h
index 17af882c2d7..c377ae730cc 100644
--- a/common/formats/iff_container.h
+++ b/common/formats/iff_container.h
@@ -67,6 +67,8 @@ page 376) */
 /* Amiga Contiguous Bitmap (AmigaBasic) */
 #define ID_8SVX     MKTAG('8','S','V','X')
 /* Amiga 8 bits voice */
+#define ID_DATA     MKTAG('D','A','T','A')
+/* Replaces FORM in Nancy Drew IFFs */
 
 /* generic */
 
@@ -226,6 +228,7 @@ protected:
 
 	uint32 _formSize;
 	IFF_ID _formType;
+	IFF_ID _formHeaderID;
 
 	ReadStream *_stream;
 	bool _disposeStream;
@@ -233,7 +236,7 @@ protected:
 	void setInputStream(ReadStream *stream);
 
 public:
-	IFFParser(ReadStream *stream, bool disposeStream = false);
+	IFFParser(ReadStream *stream, bool disposeStream = false, IFF_ID formHeaderID = ID_FORM);
 	~IFFParser();
 
 	/**


Commit: 74307deca6e5f0d897cc32eaa85af6564d519e0b
    https://github.com/scummvm/scummvm/commit/74307deca6e5f0d897cc32eaa85af6564d519e0b
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-26T19:46:32+03:00

Commit Message:
NANCY: Rewrite resource handling

CifFile/CifTree classes have been completely rewritten and
moved to their own header file. ResourceManager is now
exclusively responsible for loading IFF files, making sure
loose .iff/.cif files take precedence before ones embedded
in a ciftree.
Also, completed work on the cif/ciftree exporting functions.

Changed paths:
  A engines/nancy/cif.cpp
  A engines/nancy/cif.h
    engines/nancy/action/conversation.cpp
    engines/nancy/console.cpp
    engines/nancy/console.h
    engines/nancy/iff.cpp
    engines/nancy/iff.h
    engines/nancy/module.mk
    engines/nancy/nancy.cpp
    engines/nancy/resource.cpp
    engines/nancy/resource.h
    engines/nancy/state/scene.cpp


diff --git a/engines/nancy/action/conversation.cpp b/engines/nancy/action/conversation.cpp
index fdf7ba505ed..e4271770cd6 100644
--- a/engines/nancy/action/conversation.cpp
+++ b/engines/nancy/action/conversation.cpp
@@ -673,37 +673,31 @@ void ConversationCel::readData(Common::SeekableReadStream &stream) {
 	
 	readFilenameArray(stream, _treeNames, 4);
 
-	uint xsheetDataSize = 0;
-	byte *xsbuf = g_nancy->_resource->loadData(xsheetName, xsheetDataSize);
-	if (!xsbuf) {
-		return;
-	}
-
-	Common::MemoryReadStream xsheet(xsbuf, xsheetDataSize, DisposeAfterUse::YES);
+	Common::SeekableReadStream *xsheet = SearchMan.createReadStreamForMember(xsheetName);
 
 	// Read the xsheet and load all images into the arrays
 	// Completely unoptimized, the original engine uses a buffer
-	xsheet.seek(0);
-	Common::String signature = xsheet.readString('\0', 18);
+	xsheet->seek(0);
+	Common::String signature = xsheet->readString('\0', 18);
 	if (signature != "XSHEET WayneSikes") {
 		warning("XSHEET signature doesn't match!");
 		return;
 	}
 
-	xsheet.seek(0x22);
-	uint numFrames = xsheet.readUint16LE();
-	xsheet.skip(2);
-	_frameTime = xsheet.readUint16LE();
-	xsheet.skip(2);
+	xsheet->seek(0x22);
+	uint numFrames = xsheet->readUint16LE();
+	xsheet->skip(2);
+	_frameTime = xsheet->readUint16LE();
+	xsheet->skip(2);
 
 	_celNames.resize(4, Common::Array<Common::String>(numFrames));
 	for (uint i = 0; i < numFrames; ++i) {
 		for (uint j = 0; j < _celNames.size(); ++j) {
-			readFilename(xsheet, _celNames[j][i]);
+			readFilename(*xsheet, _celNames[j][i]);
 		}
 
 		// 4 unknown values
-		xsheet.skip(8);
+		xsheet->skip(8);
 	}
 
 	// Continue reading the AR stream
diff --git a/engines/nancy/cif.cpp b/engines/nancy/cif.cpp
new file mode 100644
index 00000000000..7ef94694e67
--- /dev/null
+++ b/engines/nancy/cif.cpp
@@ -0,0 +1,330 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/nancy/cif.h"
+#include "engines/nancy/decompress.h"
+#include "engines/nancy/util.h"
+#include "engines/nancy/nancy.h"
+
+#include "common/memstream.h"
+#include "common/substream.h"
+#include "common/serializer.h"
+
+namespace Nancy {
+
+// Reads the data common to standalone .cif files and the ones embedded in a ciftree
+static void syncCifInfo(Common::Serializer &ser, CifInfo &info, bool tree) {
+	// gross switch of what "version means"
+	uint ver = ser.getVersion();
+	ser.setVersion(g_nancy->getGameType());
+	readRect(ser, info.src, kGameTypeNancy2);
+	readRect(ser, info.dest, kGameTypeNancy2);
+	ser.setVersion(ver);
+	
+	ser.syncAsUint16LE(info.width);
+	ser.syncAsUint16LE(info.pitch);
+	ser.syncAsUint16LE(info.height);
+	ser.syncAsByte(info.depth);
+
+	ser.syncAsByte(info.comp);
+
+	if (tree) {
+		ser.syncAsUint32LE(info.dataOffset, 0, 1);
+	}
+
+	ser.syncAsUint32LE(info.size);
+	ser.skip(4); // A 2nd size for obsolete Cif type 1
+	ser.syncAsUint32LE(info.compressedSize);
+
+	ser.syncAsByte(info.type);
+
+	if (!tree) {
+		info.dataOffset = ser.bytesSynced();
+	}
+}
+
+// Reads the data for ciftree cif files
+static void syncCiftreeInfo(Common::Serializer &ser, CifInfo &info) {
+	uint nameSize = g_nancy->getGameType() <= kGameTypeNancy2 ? 9 : 33;
+	byte name[33];
+	if (ser.isSaving()) {
+		memcpy(name, info.name.c_str(), nameSize);
+		name[nameSize] = 0;
+	}
+	
+	ser.syncBytes(name, nameSize);
+	name[nameSize] = 0;
+	info.name = (char *)name;
+
+	ser.skip(2); // Index of this block
+
+	ser.syncAsUint32LE(info.dataOffset, 2);
+	ser.skip(2, 2); // Next id in chain
+
+	syncCifInfo(ser, info, true);
+
+	ser.skip(2, 0, 1); // Next id in chain
+}
+
+enum {
+	kHashMapSize = 1024
+};
+
+CifFile::CifFile(Common::SeekableReadStream *stream, const Common::String &name) {
+	assert(stream);
+	_stream = stream;
+
+	_info.name = name;
+	Common::Serializer ser(stream, nullptr);
+	if (!sync(ser)) {
+		return;
+	}
+}
+
+CifFile::~CifFile() {
+	delete _stream;
+}
+
+Common::SeekableReadStream *CifFile::createReadStream() const {
+	byte *buf = new byte[_info.size];
+
+	bool success = true;
+
+	if (_info.comp == CifInfo::kResCompression) {
+		// Decompress the data into the buffer
+		if (_stream->seek(_info.dataOffset)) {
+			Common::MemoryWriteStream write(buf, _info.size);
+			Common::SubReadStream read(_stream, _info.compressedSize);
+			Decompressor dec;
+			success = dec.decompress(read, write);
+		} else {
+			success = false;
+		}
+	} else {
+		if (!_stream->seek(_info.dataOffset) || _stream->read(buf, _info.size) < _info.size) {
+			success = false;
+		}
+	}
+	
+	if (!success) {
+		warning("Failed to read data for CifFile '%s'", _info.name.c_str());
+		delete[] buf;
+		return nullptr;
+		_stream->clearErr();
+	}
+	
+	return new Common::MemoryReadStream(buf, _info.size, DisposeAfterUse::YES);
+}
+
+Common::SeekableReadStream *CifFile::createReadStreamRaw() const {
+	uint size = (_info.comp == CifInfo::kResCompression ? _info.compressedSize : _info.size);
+	byte *buf = new byte[size];
+
+	if (!_stream->seek(_info.dataOffset) || _stream->read(buf, size) < size) {
+		warning("Failed to read data for CifFile '%s'", _info.name.c_str());
+	}
+	
+	return new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES);
+}
+
+bool CifFile::sync(Common::Serializer &ser) {
+	if (!ser.matchBytes("CIF FILE WayneSikes", 20)) {
+		warning("Invalid id string found in CifFile '%s'", _info.name.c_str());
+		return false;
+	}
+
+	// 4 bytes unused
+	ser.skip(4);
+
+	// Version high bytes. These do not change
+	uint16 hi = 2;
+	ser.syncAsUint16LE(hi);
+
+	uint32 ver = (g_nancy->getGameType() <= kGameTypeNancy1) ? 0 : 1;
+	ser.syncAsUint16LE(ver);
+
+	if (ver != 0 && ver != 1) {
+		warning("Unsupported version %d found in CifFile '%s'", ver, _info.name.c_str());
+		return false;
+	}
+
+	if (g_nancy->getGameType() >= kGameTypeNancy6) {
+		++ver; // nancy6 made changes to the CifTree structure, but didn't bump the file version
+	}
+
+	ser.setVersion(ver);
+
+	syncCifInfo(ser, _info, false);
+	return true;
+}
+
+CifTree::CifTree(Common::SeekableReadStream *stream, const Common::String &name) :
+		_stream(stream),
+		_name(name) {}
+
+CifTree::~CifTree() {
+	delete _stream;
+}
+
+const CifInfo &CifTree::getCifInfo(const Common::String &name) const {
+	return _fileMap[name];
+}
+
+bool CifTree::hasFile(const Common::Path &path) const {
+	return _fileMap.contains(path.toString());
+}
+
+int CifTree::listMembers(Common::ArchiveMemberList &list) const {
+	for (auto &f : _fileMap) {
+		list.push_back(Common::ArchiveMemberPtr(new Common::GenericArchiveMember(f._key, *this)));
+	}
+
+	return list.size();
+}
+
+const Common::ArchiveMemberPtr CifTree::getMember(const Common::Path &path) const {
+	if (!hasFile(path)) {
+		return Common::ArchiveMemberPtr();
+	}
+
+	return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
+}
+
+Common::SeekableReadStream *CifTree::createReadStreamForMember(const Common::Path &path) const {
+	if (!hasFile(path)) {
+		return nullptr;
+	}
+
+	const CifInfo &info = _fileMap[path.toString()];
+	byte *buf = new byte[info.size];
+
+	bool success = true;
+
+	if (info.comp == CifInfo::kResCompression) {
+		// Decompress the data into the buffer
+		if (_stream->seek(info.dataOffset)) {
+			Common::MemoryWriteStream write(buf, info.size);
+			Common::SubReadStream read(_stream, info.compressedSize);
+			Decompressor dec;
+			success = dec.decompress(read, write);
+		} else {
+			success = false;
+		}
+	} else {
+		if (!_stream->seek(info.dataOffset) || _stream->read(buf, info.size) < info.size) {
+			success = false;
+		}
+	}
+	
+	if (!success) {
+		warning("Failed to read data for '%s' from CifTree '%s'", info.name.c_str(), _name.c_str());
+		delete[] buf;
+		return nullptr;
+		_stream->clearErr();
+	}
+	
+	return new Common::MemoryReadStream(buf, info.size, DisposeAfterUse::YES);
+}
+
+Common::SeekableReadStream *CifTree::createReadStreamRaw(const Common::Path &path) const {
+	if (!hasFile(path)) {
+		return nullptr;
+	}
+
+	const CifInfo &info = _fileMap[path.toString()];
+	uint32 size = (info.comp == CifInfo::kResCompression ? info.compressedSize : info.size);
+	byte *buf = new byte[size];
+
+	if (!_stream->seek(info.dataOffset) || _stream->read(buf, size) < size) {
+		warning("Failed to read data for '%s' from CifTree '%s'", info.name.c_str(), _name.c_str());
+	}
+	
+	return new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES);
+}
+
+CifTree *CifTree::makeCifTreeArchive(const Common::String &name, const Common::String &ext) {
+	auto *stream = SearchMan.createReadStreamForMember(name + '.' + ext);
+
+	if (!stream) {
+		return nullptr;
+	}
+
+	CifTree *ret = new CifTree(stream, name + '.' + ext);
+	Common::Serializer ser(stream, nullptr);
+
+	if (!ret->sync(ser)) {
+		delete ret;
+		return nullptr;
+	}
+
+	return ret;
+}
+
+bool CifTree::sync(Common::Serializer &ser) {
+	if (!ser.matchBytes("CIF TREE WayneSikes", 20)) {
+		warning("Invalid id string found in CifTree '%s'", _name.c_str());
+		return false;
+	}
+
+	// 4 bytes unused
+	ser.skip(4);
+
+	// Version high bytes. These do not change
+	uint16 hi = 2;
+	ser.syncAsUint16LE(hi);
+
+	uint32 ver = (g_nancy->getGameType() <= kGameTypeNancy1) ? 0 : 1;
+	ser.syncAsUint16LE(ver);
+
+	if (ver != 0 && ver != 1) {
+		warning("Unsupported version %d found in CifTree '%s'", ver, _name.c_str());
+		return false;
+	}
+
+	if (g_nancy->getGameType() >= kGameTypeNancy6) {
+		++ver; // nancy6 made changes to the CifTree structure, but didn't bump the file version
+	}
+	
+	ser.setVersion(ver);
+
+	uint16 infoBlockCount = _writeFileMap.size();
+	ser.syncAsUint16LE(infoBlockCount);
+	ser.skip(2, 1);
+
+	// We will be doing our own hashing, so skip the table built into the tree
+	ser.skip(kHashMapSize * 2);
+
+	CifInfo info;
+	for (int i = 0; i < infoBlockCount; i++) {
+		if (ser.isLoading()) {
+			syncCiftreeInfo(ser, info);
+			if (info.size && info.type != CifInfo::kResTypeEmpty) {
+				_fileMap.setVal(info.name, info);
+			}
+		} else {
+			syncCiftreeInfo(ser, _writeFileMap[i]);
+		}		
+	}
+
+	return true;
+}
+
+} // End of namespace Nancy
diff --git a/engines/nancy/cif.h b/engines/nancy/cif.h
new file mode 100644
index 00000000000..5140074304c
--- /dev/null
+++ b/engines/nancy/cif.h
@@ -0,0 +1,115 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef NANCY_CIF_H
+#define NANCY_CIF_H
+
+#include "common/archive.h"
+#include "common/rect.h"
+#include "common/hashmap.h"
+
+namespace Common {
+class SeekableReadStream;
+class Serializer;
+}
+
+namespace Nancy {
+
+struct CifInfo {
+	enum ResType : byte {
+		kResTypeAny,
+		// Type 1 seems to be obsolete
+		kResTypeImage = 2,
+		kResTypeScript = 3,
+		kResTypeEmpty = 4
+	};
+
+	enum ResCompression {
+		kResCompressionNone = 1,
+		kResCompression = 2
+	};
+
+	Common::String name;
+	ResType type = kResTypeEmpty; // ResType
+	ResCompression comp = kResCompressionNone; // ResCompression
+	uint16 width, pitch, height = 0;
+	byte depth = 0; // Bit depth
+	uint32 compressedSize, size = 0;
+	Common::Rect src, dest; // Used when drawing conversation cels
+
+	uint32 dataOffset;
+};
+
+// Wrapper for a single file. Exclusively used for scene IFFs, though it can contain anything.
+class CifFile {
+private:
+friend class ResourceManager;
+	CifFile() : _stream(nullptr) {}
+public:
+	CifFile(Common::SeekableReadStream *stream, const Common::String &name);
+	~CifFile();
+
+	Common::SeekableReadStream *createReadStream() const;
+
+private:
+	bool sync(Common::Serializer &ser);
+	Common::SeekableReadStream *createReadStreamRaw() const;
+
+	Common::SeekableReadStream *_stream;
+	CifInfo _info;
+};
+
+// Container type comprising of multiple CIF files. Contrary to its name it contains no tree structure.
+class CifTree : public Common::Archive {
+friend class ResourceManager;
+	CifTree() : _stream(nullptr) {}
+	CifTree(Common::SeekableReadStream *stream, const Common::String &name);
+	~CifTree();
+
+public:
+	// Used for extracting additional image data for conversation cels (nancy2 and up)
+	const CifInfo &getCifInfo(const Common::String &name) const;
+
+	// Archive interface
+	bool hasFile(const Common::Path &path) const override;
+	int listMembers(Common::ArchiveMemberList &list) const override;
+
+	const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
+	Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
+
+	Common::String getName() const { return _name; }
+
+	static CifTree *makeCifTreeArchive(const Common::String &name, const Common::String &ext);
+
+private:
+	bool sync(Common::Serializer &ser);
+	Common::SeekableReadStream *createReadStreamRaw(const Common::Path &path) const;
+
+	Common::String _name;
+	Common::SeekableReadStream *_stream;
+	Common::HashMap<Common::String, CifInfo, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _fileMap;
+	Common::Array<CifInfo> _writeFileMap;
+};
+
+
+} // End of namespace Nancy
+
+#endif // NANCY_CIF_H
diff --git a/engines/nancy/console.cpp b/engines/nancy/console.cpp
index 785aa363964..5b14af25596 100644
--- a/engines/nancy/console.cpp
+++ b/engines/nancy/console.cpp
@@ -31,6 +31,7 @@
 #include "engines/nancy/console.h"
 #include "engines/nancy/resource.h"
 #include "engines/nancy/sound.h"
+#include "engines/nancy/cif.h"
 #include "engines/nancy/iff.h"
 #include "engines/nancy/input.h"
 #include "engines/nancy/graphics.h"
@@ -43,7 +44,6 @@ namespace Nancy {
 
 NancyConsole::NancyConsole() : GUI::Debugger() {
 	registerCmd("load_cal", WRAP_METHOD(NancyConsole, Cmd_loadCal));
-	registerCmd("cif_hexdump", WRAP_METHOD(NancyConsole, Cmd_cifHexDump));
 	registerCmd("cif_export", WRAP_METHOD(NancyConsole, Cmd_cifExport));
 	registerCmd("cif_list", WRAP_METHOD(NancyConsole, Cmd_cifList));
 	registerCmd("cif_info", WRAP_METHOD(NancyConsole, Cmd_cifInfo));
@@ -68,7 +68,6 @@ NancyConsole::NancyConsole() : GUI::Debugger() {
 	registerCmd("set_difficulty", WRAP_METHOD(NancyConsole, Cmd_setDifficulty));
 	registerCmd("sound_info", WRAP_METHOD(NancyConsole, Cmd_soundInfo));
 	registerCmd("debug_hotspots", WRAP_METHOD(NancyConsole, Cmd_showHotspots));
-
 }
 
 NancyConsole::~NancyConsole() {}
@@ -153,25 +152,6 @@ void NancyConsole::postEnter() {
 	g_nancy->_input->forceCleanInput();
 }
 
-bool NancyConsole::Cmd_cifHexDump(int argc, const char **argv) {
-	if (argc < 2 || argc > 3) {
-		debugPrintf("Dumps the specified resource to standard output\n");
-		debugPrintf("Usage: %s <name> [cal]\n", argv[0]);
-		return true;
-	}
-
-	uint size;
-	byte *buf = g_nancy->_resource->loadCif((argc == 2 ? "ciftree" : argv[2]), argv[1], size);
-	if (!buf) {
-		debugPrintf("Failed to load resource '%s'\n", argv[1]);
-		return true;
-	}
-
-	Common::hexdump(buf, size);
-	delete[] buf;
-	return true;
-}
-
 bool NancyConsole::Cmd_cifExport(int argc, const char **argv) {
 	if (argc < 2 || argc > 3) {
 		debugPrintf("Exports the specified resource to .cif file\n");
@@ -179,7 +159,7 @@ bool NancyConsole::Cmd_cifExport(int argc, const char **argv) {
 		return true;
 	}
 
-	if (!g_nancy->_resource->exportCif((argc == 2 ? "ciftree" : argv[2]), argv[1]))
+	if (!g_nancy->_resource->exportCif((argc == 2 ? "" : argv[2]), argv[1]))
 		debugPrintf("Failed to export '%s'\n", argv[1]);
 
 	return true;
@@ -194,7 +174,7 @@ bool NancyConsole::Cmd_cifList(int argc, const char **argv) {
 	}
 
 	Common::Array<Common::String> list;
-	g_nancy->_resource->list((argc == 2 ? "ciftree" : argv[2]), list, atoi(argv[1]));
+	g_nancy->_resource->list((argc == 2 ? "" : argv[2]), list, (CifInfo::ResType)atoi(argv[1]));
 
 	debugPrintColumns(list);
 
@@ -219,8 +199,8 @@ bool NancyConsole::Cmd_chunkExport(int argc, const char **argv) {
 		return true;
 	}
 
-	IFF iff(argv[1]);
-	if (!iff.load()) {
+	IFF *iff = g_nancy->_resource->loadIFF(argv[1]);
+	if (!iff) {
 		debugPrintf("Failed to load IFF '%s'\n", argv[1]);
 		return true;
 	}
@@ -237,9 +217,10 @@ bool NancyConsole::Cmd_chunkExport(int argc, const char **argv) {
 	if (argc == 4)
 		index = atoi(argv[3]);
 
-	buf = iff.getChunk(id, size, index);
+	buf = iff->getChunk(id, size, index);
 	if (!buf) {
 		debugPrintf("Failed to find chunk '%s' (index %d) in IFF '%s'\n", argv[2], index, argv[1]);
+		delete iff;
 		return true;
 	}
 
@@ -253,6 +234,7 @@ bool NancyConsole::Cmd_chunkExport(int argc, const char **argv) {
 	dumpfile.open(filename);
 	dumpfile.write(buf, size);
 	dumpfile.close();
+	delete iff;
 	return true;
 }
 
@@ -263,8 +245,8 @@ bool NancyConsole::Cmd_chunkHexDump(int argc, const char **argv) {
 		return true;
 	}
 
-	IFF iff(argv[1]);
-	if (!iff.load()) {
+	IFF *iff = g_nancy->_resource->loadIFF(argv[1]);
+	if (!iff) {
 		debugPrintf("Failed to load IFF '%s'\n", argv[1]);
 		return true;
 	}
@@ -281,13 +263,14 @@ bool NancyConsole::Cmd_chunkHexDump(int argc, const char **argv) {
 	if (argc == 4)
 		index = atoi(argv[3]);
 
-	buf = iff.getChunk(id, size, index);
+	buf = iff->getChunk(id, size, index);
 	if (!buf) {
 		debugPrintf("Failed to find chunk '%s' (index %d) in IFF '%s'\n", argv[2], index, argv[1]);
 		return true;
 	}
 
 	Common::hexdump(buf, size);
+	delete iff;
 	return true;
 }
 
@@ -298,14 +281,14 @@ bool NancyConsole::Cmd_chunkList(int argc, const char **argv) {
 		return true;
 	}
 
-	IFF iff(argv[1]);
-	if (!iff.load()) {
+	IFF *iff = g_nancy->_resource->loadIFF(argv[1]);
+	if (!iff) {
 		debugPrintf("Failed to load IFF '%s'\n", argv[1]);
 		return true;
 	}
 
 	Common::Array<Common::String> list;
-	iff.list(list);
+	iff->list(list);
 	for (uint i = 0; i < list.size(); i++) {
 		debugPrintf("%-6s", list[i].c_str());
 		if ((i % 13) == 12 && i + 1 != list.size())
@@ -313,6 +296,7 @@ bool NancyConsole::Cmd_chunkList(int argc, const char **argv) {
 	}
 
 	debugPrintf("\n");
+	delete iff;
 
 	return true;
 }
@@ -395,7 +379,7 @@ bool NancyConsole::Cmd_loadCal(int argc, const char **argv) {
 		return true;
 	}
 
-	if (!g_nancy->_resource->loadCifTree(argv[1], "cal"))
+	if (!g_nancy->_resource->readCifTree(argv[1], "cal", 3))
 		debugPrintf("Failed to load '%s.cal'\n", argv[1]);
 	return true;
 }
@@ -438,8 +422,8 @@ bool NancyConsole::Cmd_loadScene(int argc, const char **argv) {
 	}
 
 	Common::String sceneName = Common::String::format("S%s", argv[1]);
-	IFF iff(sceneName);
-	if (!iff.load()) {
+	IFF *iff = g_nancy->_resource->loadIFF(sceneName);
+	if (!iff) {
 		debugPrintf("Invalid scene S%s\n", argv[1]);
 		return true;
 	}
@@ -448,6 +432,7 @@ bool NancyConsole::Cmd_loadScene(int argc, const char **argv) {
 	scene.sceneID = (uint16)atoi(argv[1]);
 	NancySceneState.changeScene(scene);
 	NancySceneState._state = State::Scene::kLoad;
+	delete iff;
 	return cmdExit(0, nullptr);
 }
 
@@ -604,13 +589,13 @@ bool NancyConsole::Cmd_listActionRecords(int argc, const char **argv) {
 		Common::Queue<uint> unknownTypes;
 		Common::Queue<Common::String> unknownDescs;
 		Common::SeekableReadStream *chunk;
-		IFF sceneIFF("S" + s);
-		if (!sceneIFF.load()) {
+		IFF *sceneIFF = g_nancy->_resource->loadIFF("S" + s);
+		if (!sceneIFF) {
 			debugPrintf("Invalid scene S%s\n", argv[1]);
 			return true;
 		}
 
-		while (chunk = sceneIFF.getChunkStream("ACT", records.size()), chunk != nullptr) {
+		while (chunk = sceneIFF->getChunkStream("ACT", records.size()), chunk != nullptr) {
 			ActionRecord *rec = ActionManager::createAndLoadNewRecord(*chunk);
 			if (rec == nullptr) {
 				chunk->seek(0);
@@ -642,6 +627,8 @@ bool NancyConsole::Cmd_listActionRecords(int argc, const char **argv) {
 		for (uint i = 0; i < records.size(); ++i) {
 			delete records[i];
 		}
+
+		delete sceneIFF;
 	} else {
 		debugPrintf("Invalid input\n");
 	}
@@ -686,8 +673,8 @@ bool NancyConsole::Cmd_scanForActionRecordType(int argc, const char **argv) {
 
 	Common::Array<Common::String> list;
 	// Action records only appear in the ciftree and promotree
-	g_nancy->_resource->list("ciftree", list, ResourceManager::kResTypeScript);
-	g_nancy->_resource->list("promotree", list, ResourceManager::kResTypeScript);
+	g_nancy->_resource->list("ciftree", list, CifInfo::kResTypeScript);
+	g_nancy->_resource->list("promotree", list, CifInfo::kResTypeScript);
 
 	char descBuf[0x30];
 
@@ -703,11 +690,11 @@ bool NancyConsole::Cmd_scanForActionRecordType(int argc, const char **argv) {
 			name.matchString("S###") ||
 			name.matchString("S####")) {
 
-			IFF iff(cifName);
-			if (iff.load()) {
+			IFF *iff = g_nancy->_resource->loadIFF(cifName);
+			if (iff) {
 				uint num = 0;
 				Common::SeekableReadStream *chunk = nullptr;
-				while (chunk = iff.getChunkStream("ACT", num), chunk != nullptr) {
+				while (chunk = iff->getChunkStream("ACT", num), chunk != nullptr) {
 					bool isSatisfied = true;
 					for (uint i = 0; i < vals.size(); i += 2) {
 						if ((int64)vals[i] >= chunk->size()) {
@@ -732,6 +719,8 @@ bool NancyConsole::Cmd_scanForActionRecordType(int argc, const char **argv) {
 					++num;
 					delete chunk;
 				}
+
+				delete iff;
 			}
 		}
 	}
diff --git a/engines/nancy/console.h b/engines/nancy/console.h
index 48b00d13e3d..b5ca36af02b 100644
--- a/engines/nancy/console.h
+++ b/engines/nancy/console.h
@@ -42,7 +42,6 @@ public:
 
 private:
 	bool Cmd_loadCal(int argc, const char **argv);
-	bool Cmd_cifHexDump(int argc, const char **argv);
 	bool Cmd_cifExport(int argc, const char **argv);
 	bool Cmd_cifList(int argc, const char **argv);
 	bool Cmd_cifInfo(int argc, const char **argv);
diff --git a/engines/nancy/iff.cpp b/engines/nancy/iff.cpp
index 4b7d37b738f..615fc22d559 100644
--- a/engines/nancy/iff.cpp
+++ b/engines/nancy/iff.cpp
@@ -28,6 +28,31 @@
 
 namespace Nancy {
 
+IFF::IFF(Common::SeekableReadStream *stream) {
+	// Scan the file for FORM/DATA wrapper chunks. There can be several of these in a single IFF.
+	uint32 dataString = g_nancy->getGameType() == kGameTypeVampire ? ID_FORM : ID_DATA;
+	_stream = stream;
+
+	while (stream->pos() < stream->size() - 3) {
+		_nextDATAChunk = 0;
+		uint32 id = stream->readUint32BE();
+		stream->seek(-4, SEEK_CUR);
+		if (id == dataString) {
+			Common::IFFParser iff(stream, false, dataString);
+			Common::Functor1Mem<Common::IFFChunk &, bool, IFF> c(this, &IFF::callback);
+			iff.parse(c);
+			if (_nextDATAChunk) {
+				stream->seek(_nextDATAChunk);
+			}
+		} else {
+			stream->skip(1);
+		}
+	}
+
+	delete _stream;
+	_stream = nullptr;
+}
+
 IFF::~IFF() {
 	for (uint i = 0; i < _chunks.size(); i++)
 		delete[] _chunks[i].buf;
@@ -45,9 +70,11 @@ bool IFF::callback(Common::IFFChunk &c) {
 	}
 	chunk.id = READ_BE_UINT32(id);
 
-	if (chunk.id == ID_DATA) {
-		debugN(3, "IFF::callback: Skipping 'DATA' chunk\n");
-		return false;
+	if (chunk.id == (g_nancy->getGameType() == kGameTypeVampire ? ID_FORM : ID_DATA)) {
+		// Encountered the next FORM/DATA wrapper. Signal that we need to stop reading the
+		// current one and mark where from the parser should be called next.
+		_nextDATAChunk = _stream->pos() - 8;
+		return true;
 	}
 
 	chunk.size = c._size;
@@ -63,41 +90,6 @@ bool IFF::callback(Common::IFFChunk &c) {
 	return false;
 }
 
-bool IFF::load() {
-	byte *data;
-	uint size;
-	data = g_nancy->_resource->loadData(_name, size);
-
-	if (!data) {
-		return false;
-	}
-
-	// Scan the file for DATA chunks, completely ignoring IFF structure
-	// Presumably the string "DATA" is not allowed inside of chunks...
-	// The Vampire Diaries uses the standard FORM
-	uint32 dataString = g_nancy->getGameType() == kGameTypeVampire ? ID_FORM : ID_DATA;
-
-	uint offset = 0;
-
-	while (offset < size - 3) {
-		uint32 id = READ_BE_UINT32(data + offset);
-		if (id == dataString) {
-			// Replace 'DATA' with standard 'FORM' for the parser
-			WRITE_BE_UINT32(data + offset, ID_FORM);
-			Common::MemoryReadStream stream(data + offset, size - offset);
-			Common::IFFParser iff(&stream);
-			Common::Functor1Mem<Common::IFFChunk &, bool, IFF> c(this, &IFF::callback);
-			iff.parse(c);
-			offset += 16; // Original engine skips 16, while 12 seems more logical
-		} else {
-			++offset;
-		}
-	}
-
-	delete[] data;
-	return true;
-}
-
 const byte *IFF::getChunk(uint32 id, uint &size, uint index) const {
 	uint found = 0;
 	for (uint i = 0; i < _chunks.size(); ++i) {
diff --git a/engines/nancy/iff.h b/engines/nancy/iff.h
index c849e00d15d..c47e0e15cc9 100644
--- a/engines/nancy/iff.h
+++ b/engines/nancy/iff.h
@@ -32,15 +32,16 @@ class SeekableReadStream;
 namespace Nancy {
 
 class NancyEngine;
-
-#define ID_DATA		MKTAG('D', 'A', 'T', 'A')
+class ResourceManager;
 
 class IFF {
+	friend class ResourceManager;
+private:
+	IFF(Common::SeekableReadStream *stream);
+
 public:
-	IFF(const Common::String &name) : _name(name) { };
 	~IFF();
 
-	bool load();
 	const byte *getChunk(uint32 id, uint &size, uint index = 0) const;
 	Common::SeekableReadStream *getChunkStream(const Common::String &id, uint index = 0) const;
 
@@ -58,8 +59,9 @@ private:
 		uint32 size;
 	};
 
+	const Common::SeekableReadStream *_stream;
 	Common::Array<Chunk> _chunks;
-	const Common::String _name;
+	uint32 _nextDATAChunk;
 };
 
 } // End of namespace Nancy
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index ddf1133048a..6527b396932 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -60,6 +60,7 @@ MODULE_OBJS = \
   misc/lightning.o \
   misc/mousefollow.o \
   misc/specialeffect.o \
+  cif.o \
   commontypes.o \
   console.o \
   cursor.o \
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index e7ef2b78d1a..38ed7698ddd 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -29,6 +29,7 @@
 
 #include "engines/nancy/nancy.h"
 #include "engines/nancy/resource.h"
+#include "engines/nancy/cif.h"
 #include "engines/nancy/iff.h"
 #include "engines/nancy/input.h"
 #include "engines/nancy/sound.h"
@@ -391,7 +392,8 @@ void NancyEngine::bootGameEngine() {
 	}
 
 	_resource = new ResourceManager();
-	_resource->initialize();
+	_resource->readCifTree("ciftree", "dat", 2);
+	_resource->readCifTree("promotree", "dat", 2);
 
 	// Read nancy.dat
 	readDatFile();
@@ -399,8 +401,8 @@ void NancyEngine::bootGameEngine() {
 	// Setup mixer
 	syncSoundSettings();
 
-	IFF *iff = new IFF("boot");
-	if (!iff->load())
+	IFF *iff = _resource->loadIFF("boot");
+	if (!iff)
 		error("Failed to load boot script");
 
 	// Load BOOT chunks data
@@ -459,8 +461,8 @@ void NancyEngine::bootGameEngine() {
 	// Load convo texts and autotext
 	auto *bsum = GetEngineData(BSUM);
 	if (bsum && bsum->conversationTextsFilename.size() && bsum->autotextFilename.size())  {
-		iff = new IFF(bsum->conversationTextsFilename);
-		if (!iff->load()) {
+		iff = _resource->loadIFF(bsum->conversationTextsFilename);
+		if (!iff) {
 			error("Could not load CONVO IFF");
 		}
 
@@ -471,8 +473,8 @@ void NancyEngine::bootGameEngine() {
 
 		delete iff;
 
-		iff = new IFF(bsum->autotextFilename);
-		if (!iff->load()) {
+		iff = _resource->loadIFF(bsum->autotextFilename);
+		if (!iff) {
 			error("Could not load AUTOTEXT IFF");
 		}
 
@@ -573,7 +575,7 @@ void NancyEngine::preloadCals() {
 	}
 
 	for (const Common::String &name : pcal->calNames) {
-		if (!_resource->loadCifTree(name, "cal")) {
+		if (!_resource->readCifTree(name, "cal", 2)) {
 			error("Failed to preload CAL '%s'", name.c_str());
 		}
 	}
diff --git a/engines/nancy/resource.cpp b/engines/nancy/resource.cpp
index 720e4bc06f7..3a672335db7 100644
--- a/engines/nancy/resource.cpp
+++ b/engines/nancy/resource.cpp
@@ -28,897 +28,349 @@
 #include "engines/nancy/decompress.h"
 #include "engines/nancy/graphics.h"
 #include "engines/nancy/util.h"
+#include "engines/nancy/iff.h"
 
-namespace Nancy {
-
-static void readCifInfo20(Common::File &f, ResourceManager::CifInfo &info, uint32 *dataOffset = nullptr) {
-	info.width = f.readUint16LE();
-	info.pitch = f.readUint16LE();
-	info.height = f.readUint16LE();
-	info.depth = f.readByte();
-
-	info.comp = f.readByte();
-	if (dataOffset)
-		*dataOffset = f.readUint32LE();
-	info.size = f.readUint32LE();
-	f.skip(4); // A 2nd size for obsolete Cif type 1
-	info.compressedSize = f.readUint32LE();
-
-	info.type = f.readByte();
-}
-
-class CifFile {
-public:
-	CifFile(const Common::String &name, Common::File *f) : _name(name), _f(f), _dataOffset(0) { };
-	virtual ~CifFile();
-
-	bool initialize();
-	byte *getCifData(ResourceManager::CifInfo &info, uint *size = nullptr) const;
-	void getCifInfo(ResourceManager::CifInfo &info) const;
-
-	static const CifFile *load(const Common::String &name);
-
-protected:
-	virtual void readCifInfo(Common::File &f) = 0;
-
-	ResourceManager::CifInfo _cifInfo;
-	Common::String _name;
-	Common::File *_f;
-	uint32 _dataOffset;
-};
-
-CifFile::~CifFile() {
-	delete _f;
-}
-
-bool CifFile::initialize() {
-	readCifInfo(*_f);
-
-	_cifInfo.name = _name;
-
-	if (_f->eos() || _f->err()) {
-		warning("Error reading from CifFile '%s'", _name.c_str());
-		return false;
-	}
-
-	return true;
-}
-
-byte *CifFile::getCifData(ResourceManager::CifInfo &info, uint *size) const {
-	uint dataSize = (_cifInfo.comp == 2 ? _cifInfo.compressedSize : _cifInfo.size);
-	byte *buf = new byte[dataSize];
-
-	if (_f->read(buf, dataSize) < dataSize) {
-		warning("Failed to read CifFile '%s'", _name.c_str());
-		delete[] buf;
-		return nullptr;
-	}
-
-	if (size)
-		*size = dataSize;
-	info = _cifInfo;
-	return buf;
-}
-
-void CifFile::getCifInfo(ResourceManager::CifInfo &info) const {
-	info = _cifInfo;
-}
-
-class CifFile20 : public CifFile {
-public:
-	CifFile20(const Common::String &name, Common::File *f) : CifFile(name, f) { }
-protected:
-	void readCifInfo(Common::File &f) override;
-};
-
-void CifFile20::readCifInfo(Common::File &f) {
-	readCifInfo20(f, _cifInfo);
-}
-
-class CifFile21 : public CifFile {
-public:
-	CifFile21(const Common::String &name, Common::File *f) : CifFile(name, f) { }
-protected:
-	void readCifInfo(Common::File &f) override;
-};
-
-void CifFile21::readCifInfo(Common::File &f) {
-	readRect(f, _cifInfo.src);
-	readRect(f, _cifInfo.dest);
-	readCifInfo20(f, _cifInfo);
-}
-
-const CifFile *CifFile::load(const Common::String &name) {
-	Common::File *f = new Common::File;
-	CifFile *cifFile = nullptr;
-
-	if (!f->open(name + ".cif")) {
-		delete f;
-		return nullptr;
-	}
-
-	char id[20];
-	f->read(id, 20);
-	id[19] = 0;
-
-	if (f->eos() || Common::String(id) != "CIF FILE WayneSikes") {
-		warning("Invalid id string found in CifFile '%s'", name.c_str());
-		delete f;
-		return nullptr;
-	}
-
-	// 4 bytes unused
-	f->skip(4);
-
-	// Probably some kind of version number
-	uint32 ver;
-	ver = f->readUint16LE() << 16;
-	ver |= f->readUint16LE();
-
-	switch (ver) {
-	case 0x00020000:
-		cifFile = new CifFile20(name, f);
-		break;
-	case 0x00020001:
-		cifFile = new CifFile21(name, f);
-		break;
-	default:
-		warning("Unsupported version %d.%d found in CifFile '%s'", ver >> 16, ver & 0xffff, name.c_str());
-	}
-
-	if (!cifFile || !cifFile->initialize()) {
-		warning("Failed to read CifFile '%s'", name.c_str());
-		delete cifFile;
-		delete f;
-		return nullptr;
-	}
-
-	return cifFile;
-}
-
-class CifTree {
-public:
-	CifTree(const Common::String &name, const Common::String &ext);
-	virtual ~CifTree() { };
-	virtual uint32 getVersion() const = 0;
-
-	bool initialize();
-	void list(Common::Array<Common::String> &nameList, uint type) const;
-	byte *getCifData(const Common::String &name, ResourceManager::CifInfo &info, uint *size = nullptr) const;
-	bool getCifInfo(const Common::String &name, ResourceManager::CifInfo &info, uint32 *dataOffset = nullptr) const;
-	const Common::String &getName() const { return _name; }
-
-	static const CifTree *load(const Common::String &name, const Common::String &ext);
-
-protected:
-	enum {
-		kHashMapSize = 1024
-	};
-
-	struct CifInfoChain {
-		struct ResourceManager::CifInfo info;
-		uint32 dataOffset;
-		uint16 next;
-	};
-
-	virtual uint readHeader(Common::File &f) = 0;
-	virtual void readCifInfo(Common::File &f, CifInfoChain &chain) = 0;
-
-	uint16 _hashMap[kHashMapSize];
-	Common::Array<CifInfoChain> _cifInfo;
-	Common::String _name;
-	Common::String _filename;
-};
-
-CifTree::CifTree(const Common::String &name, const Common::String &ext) : _name(name) {
-	_filename = name + '.' + ext;
-}
-
-bool CifTree::initialize() {
-	Common::File f;
-
-	if (!f.open(_filename) || !f.seek(28))
-		error("Failed to open CifTree '%s'", _name.c_str());
-
-	int infoBlockCount = readHeader(f);
-
-	for (int i = 0; i < kHashMapSize; i++)
-		_hashMap[i] = f.readUint16LE();
-
-	if (f.eos())
-		error("Error reading CifTree '%s'", _name.c_str());
-
-	_cifInfo.reserve(infoBlockCount);
-	for (int i = 0; i < infoBlockCount; i++) {
-		CifInfoChain chain;
-		readCifInfo(f, chain);
-		_cifInfo.push_back(chain);
-	}
-
-	f.close();
-	return true;
-}
-
-void CifTree::list(Common::Array<Common::String> &nameList, uint type) const {
-	for (uint i = 0; i < _cifInfo.size(); i++) {
-		if (type == ResourceManager::kResTypeAny || _cifInfo[i].info.type == type)
-			nameList.push_back(_cifInfo[i].info.name);
-	}
-}
-
-bool CifTree::getCifInfo(const Common::String &name, ResourceManager::CifInfo &info, uint32 *dataOffset) const {
-	Common::String nameUpper = name;
-	nameUpper.toUppercase();
-	uint hash = 0;
-
-	for (uint i = 0; i < nameUpper.size(); i++)
-		hash += nameUpper[i];
-
-	hash &= kHashMapSize - 1;
-
-	uint16 index = _hashMap[hash];
-	while (index != 0xffff) {
-		if (nameUpper == _cifInfo[index].info.name) {
-			info = _cifInfo[index].info;
-			if (dataOffset)
-				*dataOffset = _cifInfo[index].dataOffset;
-			return true;
-		}
-		index = _cifInfo[index].next;
-	}
-
-	warning("Couldn't find '%s' in CifTree '%s'", name.c_str(), _name.c_str());
-	return false;
-}
-
-byte *CifTree::getCifData(const Common::String &name, ResourceManager::CifInfo &info, uint *size) const {
-	uint32 dataOffset;
-
-	if (!getCifInfo(name, info, &dataOffset))
-		return nullptr;
-
-	Common::File f;
-
-	if (!f.open(_filename)) {
-		warning("Failed to open CifTree '%s'", _name.c_str());
-		return nullptr;
-	}
-
-	uint dataSize = (info.comp == 2 ? info.compressedSize : info.size);
-	byte *buf = new byte[dataSize];
+static char treePrefix[] = "_tree_";
 
-	if (!f.seek(dataOffset) || f.read(buf, dataSize) < dataSize) {
-		warning("Failed to read data for '%s' from CifTree '%s'", name.c_str(), _name.c_str());
-		delete[] buf;
-		f.close();
-		return nullptr;
-	}
-
-	f.close();
-	if (size)
-		*size = dataSize;
-	return buf;
-}
-
-byte *ResourceManager::getCifData(const Common::String &treeName, const Common::String &name, CifInfo &info, uint *size) const {
-	const CifFile *cifFile = CifFile::load(name);
-	byte *buf;
+namespace Nancy {
 
-	if (cifFile) {
-		buf = cifFile->getCifData(info, size);
-		delete cifFile;
+bool ResourceManager::loadImage(const Common::String &name, Graphics::ManagedSurface &surf, const Common::String treeName, Common::Rect *outSrc, Common::Rect *outDest) {
+	CifInfo info;
+	bool external = false;
+
+	// First, check for external bitmap
+	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(name + ".bmp");
+	if (stream) {
+		// Found external image
+		Image::BitmapDecoder bmpDec;
+		bmpDec.loadStream(*stream);
+		surf.copyFrom(*bmpDec.getSurface());
+		surf.setPalette(bmpDec.getPalette(), bmpDec.getPaletteStartIndex(), MIN<uint>(256, bmpDec.getPaletteColorCount())); // LOGO.BMP reports 257 colors
+		external = true;
 	} else {
-		const CifTree *cifTree = findCifTree(treeName);
-		if (!cifTree)
-			return nullptr;
+		// Then, search inside the ciftrees
+		stream = SearchMan.createReadStreamForMember(name);
 
-		buf = cifTree->getCifData(name, info, size);
-	}
-
-	if (buf && info.comp == kResCompression) {
-		Common::MemoryReadStream input(buf, info.compressedSize);
-		byte *raw = new byte[info.size];
-		Common::MemoryWriteStream output(raw, info.size);
-		if (!_dec->decompress(input, output)) {
-			warning("Failed to decompress '%s'", name.c_str());
-			delete[] buf;
-			delete[] raw;
-			return nullptr;
+		if (!stream) {
+			warning("Couldn't open image file %s", name.c_str());
+			return false;
 		}
-		delete[] buf;
-		if (size)
-			*size = output.size();
-		return raw;
 	}
 
-	return buf;
-}
-
-class CifTree20 : public CifTree {
-public:
-	CifTree20(const Common::String &name, const Common::String &ext) : CifTree(name, ext) { }
-protected:
-	uint readHeader(Common::File &f) override;
-	void readCifInfo(Common::File &f, CifInfoChain &chain) override;
-	uint32 getVersion() const override { return 0x00020000; }
-};
-
-uint CifTree20::readHeader(Common::File &f) {
-	uint infoBlockCount = f.readUint16LE();
-
-	if (f.eos())
-		error("Failed to read cif info block count from CifTree");
-
-	return infoBlockCount;
-}
-
-void CifTree20::readCifInfo(Common::File &f, CifInfoChain &chain) {
-	ResourceManager::CifInfo &info = chain.info;
-
-	char name[9];
-	f.read(name, 9);
-	name[8] = 0;
-	info.name = name;
-
-	f.skip(2); // Index of this block
-
-	readCifInfo20(f, info, &chain.dataOffset);
-
-	chain.next = f.readUint16LE();
-	if (f.eos())
-		error("Failed to read info block from CifTree");
-}
-
-class CifTree21 : public CifTree20 {
-public:
-	CifTree21(const Common::String &name, const Common::String &ext) : CifTree20(name, ext), _hasLongNames(false), _hasOffsetFirst(false) { };
-
-protected:
-	uint readHeader(Common::File &f) override;
-	void readCifInfo(Common::File &f, CifInfoChain &chain) override;
-	uint32 getVersion() const override { return 0x00020001; }
-
-private:
-	void determineSubtype(Common::File &f);
-	bool _hasLongNames;
-	bool _hasOffsetFirst;
-};
-
-uint CifTree21::readHeader(Common::File &f) {
-	uint infoBlockCount = f.readUint16LE();
-
-	if (f.eos())
-		error("Failed to read cif info block count from CifTree");
-
-	f.readByte(); // Unknown
-	f.readByte(); // Unknown
-
-	determineSubtype(f);
-
-	return infoBlockCount;
-}
-
-void CifTree21::readCifInfo(Common::File &f, CifInfoChain &chain) {
-	ResourceManager::CifInfo &info = chain.info;
-	int nameSize = 8;
-
-	if (_hasLongNames)
-		nameSize = 32;
-
-	char name[33];
-	f.read(name, nameSize + 1);
-	name[nameSize] = 0;
-	info.name = name;
+	// Search for associated CifInfo struct
+	if (!external || outSrc || outDest) {
+		// First, get the correct tree
+		const CifTree *tree = nullptr;
+		if (treeName.size()) {
+			tree = (const CifTree *)SearchMan.getArchive(treePrefix + treeName);
+		} else {
+			for (uint i = 0; i < _cifTreeNames.size(); ++i) {
+				// No provided tree name, check inside every loaded tree
+				if (SearchMan.getArchive(treePrefix + _cifTreeNames[i])->hasFile(name)) {
+					tree = (const CifTree *)SearchMan.getArchive(treePrefix + _cifTreeNames[i]);
+					break;
+				}
+			}
+		}
 
-	f.skip(2); // Index of this block
+		if (!tree) {
+			error("Couldn't find CifInfo struct inside loaded CifTrees");
+		}
 
-	if (_hasOffsetFirst) {
-		chain.dataOffset = f.readUint32LE();
-		chain.next = f.readUint16LE();
-	}
+		// Now, get the info struct and read the data we need from it
+		info = tree->getCifInfo(name);
+		if (info.type != CifInfo::kResTypeImage) {
+			warning("Resource '%s' is not an image", name.c_str());
+			return false;
+		}
 
-	readRect(f, info.src);
-	readRect(f, info.dest);
+		if (info.depth != 16) {
+			warning("Image '%s' has unsupported depth %i", name.c_str(), info.depth);
+			return false;
+		}
 
-	readCifInfo20(f, info, (_hasOffsetFirst ? nullptr : &chain.dataOffset));
+		if (outSrc) {
+			*outSrc = info.src;
+		}
 
-	if (!_hasOffsetFirst)
-		chain.next = f.readUint16LE();
-}
+		if (outDest) {
+			*outDest = info.dest;
+		}
 
-void CifTree21::determineSubtype(Common::File &f) {
-	// Perform heuristic for long filenames
-	// Assume short file names and read indices 1 and 2
-	uint pos = f.pos();
-
-	f.seek(2159);
-	uint16 index1 = f.readUint16LE();
-
-	f.seek(68, SEEK_CUR);
-	uint16 index2 = f.readUint16LE();
-
-	// If they don't match, this file must have long filenames
-	if (index1 != 1 || index2 != 2)
-		_hasLongNames = true;
-
-	if (_hasLongNames) {
-		// Perform heuristic for offset at the beginning of the block
-		// Read offset and next of the first info block
-		// If either of these is zero, offset can't be first
-		f.seek(2115);
-		uint32 offset = f.readUint32LE();
-		uint16 next = f.readUint32LE();
-		if (offset && next)
-			_hasOffsetFirst = true;
+		if (!external) {
+			uint32 bufSize = info.pitch * info.height * (info.depth / 16);
+			byte *buf = new byte[bufSize];
+			stream->read(buf, bufSize);
+			GraphicsManager::copyToManaged(buf, surf, info.width, info.height, g_nancy->_graphicsManager->getInputPixelFormat());
+			#ifdef SCUMM_BIG_ENDIAN
+			if (info.depth == 16) {
+				for (uint i = 0; i < bufSize / 2; ++i) {
+					((uint16 *)buf)[i] = SWAP_BYTES_16(((uint16 *)buf)[i]);
+				}
+			}
+			#endif
+		}
 	}
-
-	f.seek(pos);
+	
+	return true;
 }
 
-const CifTree *CifTree::load(const Common::String &name, const Common::String &ext) {
-	Common::File f;
-	CifTree *cifTree = nullptr;
-
-	if (!f.open(name + '.' + ext)) {
-		warning("Failed to open CifTree '%s'", name.c_str());
-		return nullptr;
+IFF *ResourceManager::loadIFF(const Common::String &name) {
+	// First, try to load external .cif
+	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(name + ".cif");
+	if (stream) {
+		// .cifs are compressed, so we need to extract
+		CifFile cifFile(stream, name); // cifFile takes ownership of the current stream
+		stream = cifFile.createReadStream();
 	}
 
-	char id[20];
-	f.read(id, 20);
-	id[19] = 0;
-
-	if (f.eos() || Common::String(id) != "CIF TREE WayneSikes") {
-		warning("Invalid id string found in CifTree '%s'", name.c_str());
-		f.close();
-		return nullptr;
-	}
+	if (!stream) {
+		// Then, look for external .iff. These are uncompressed
+		stream = SearchMan.createReadStreamForMember(name + ".iff");
 
-	// 4 bytes unused
-	f.skip(4);
-
-	// Probably some kind of version number
-	uint32 ver;
-	ver = f.readUint16LE() << 16;
-	ver |= f.readUint16LE();
-
-	f.close();
-
-	switch (ver) {
-	case 0x00020000:
-		cifTree = new CifTree20(name, ext);
-		break;
-	case 0x00020001:
-		cifTree = new CifTree21(name, ext);
-		break;
-	default:
-		warning("Unsupported version %d.%d found in CifTree '%s'", ver >> 16, ver & 0xffff, name.c_str());
+		// Finally, look inside ciftrees
+		if (!stream) {
+			stream = SearchMan.createReadStreamForMember(name);
+		}
 	}
 
-	if (cifTree && !cifTree->initialize()) {
-		warning("Failed to read CifTree '%s'", name.c_str());
-		delete cifTree;
-		cifTree = nullptr;
+	if (stream) {
+		return new IFF(stream);
 	}
-
-	return cifTree;
-}
-
-class CifExporter {
-public:
-	virtual ~CifExporter() { };
-	bool dump(const byte *data, uint32 size, const ResourceManager::CifInfo &info) const;
-
-	static const CifExporter *create(uint32 version);
-
-protected:
-	virtual void writeCifInfo(Common::DumpFile &f, const ResourceManager::CifInfo &info) const = 0;
-	virtual uint32 getVersion() const = 0;
-	virtual void writeHeader(Common::DumpFile &f) const;
-};
-
-void CifExporter::writeHeader(Common::DumpFile &f) const {
-	f.writeString("CIF FILE WayneSikes");
-	f.writeByte(0);
-	f.writeUint32LE(0);
-	uint32 version = getVersion();
-	f.writeUint16LE(version >> 16);
-	f.writeUint16LE(version);
+	
+	return nullptr;
 }
 
-bool CifExporter::dump(const byte *data, uint32 size, const ResourceManager::CifInfo &info) const {
-	Common::DumpFile f;
-	if (!f.open(info.name + ".cif")) {
-		warning("Failed to open export file '%s.cif'", info.name.c_str());
-		return false;
-	}
-
-	writeHeader(f);
-	writeCifInfo(f, info);
-	f.write(data, size);
-
-	if (f.err()) {
-		warning("Error writing to export file '%s.cif'", info.name.c_str());
-		f.close();
+bool ResourceManager::readCifTree(const Common::String &name, const Common::String &ext, int priority) {
+	CifTree *tree = CifTree::makeCifTreeArchive(name, ext);
+	if (!tree) {
 		return false;
 	}
 
-	f.close();
+	// Add a prefix to avoid clashes with the ciftree folder present in some games
+	SearchMan.add(treePrefix + name, tree, priority, true);
+	_cifTreeNames.push_back(name);
 	return true;
 }
 
-class CifExporter20 : public CifExporter {
-protected:
-	void writeCifInfo(Common::DumpFile &f, const ResourceManager::CifInfo &info) const override;
-	uint32 getVersion() const override { return 0x00020000; }
-};
-
-void CifExporter20::writeCifInfo(Common::DumpFile &f, const ResourceManager::CifInfo &info) const {
-	f.writeUint16LE(info.width);
-	f.writeUint16LE(info.pitch);
-	f.writeUint16LE(info.height);
-	f.writeByte(info.depth);
-
-	f.writeByte(1);
-	f.writeUint32LE(info.size);
-	f.writeUint32LE(0);
-	f.writeUint32LE(0);
-
-	f.writeByte(info.type);
-}
-
-class CifExporter21 : public CifExporter20 {
-protected:
-	void writeCifInfo(Common::DumpFile &f, const ResourceManager::CifInfo &info) const override;
-	uint32 getVersion() const override { return 0x00020001; }
-};
-
-void CifExporter21::writeCifInfo(Common::DumpFile &f, const ResourceManager::CifInfo &info) const {
-	for (uint i = 0; i < 32; i++)
-		f.writeByte(0); // TODO
-
-	CifExporter20::writeCifInfo(f, info);
-}
-
-const CifExporter *CifExporter::create(uint32 version) {
-	const CifExporter *exp;
-
-	switch (version) {
-	case 0x00020000:
-		exp = new CifExporter20;
-		break;
-	case 0x00020001:
-		exp = new CifExporter21;
-		break;
-	default:
-		warning("Version %d.%d not supported by CifExporter", version >> 16, version & 0xffff);
-		return nullptr;
-	}
-
-	return exp;
-}
-
-ResourceManager::ResourceManager() {
-	_dec = new Decompressor;
-}
-
-ResourceManager::~ResourceManager() {
-	for (uint i = 0; i < _cifTrees.size(); i++)
-		delete _cifTrees[i];
-	delete _dec;
-}
-
-bool ResourceManager::loadCifTree(const Common::String &name, const Common::String &ext) {
-	const CifTree *cifTree = CifTree::load(name, ext);
-
-	if (!cifTree)
-		return false;
-
-	_cifTrees.push_back(cifTree);
-	return true;
-}
-
-const CifTree *ResourceManager::findCifTree(const Common::String &name) const {
-	for (uint i = 0; i < _cifTrees.size(); i++)
-		if (_cifTrees[i]->getName().equalsIgnoreCase(name))
-			return _cifTrees[i];
-
-	warning("CifTree '%s' not loaded", name.c_str());
-	return nullptr;
-}
-
-void ResourceManager::initialize() {
-	if (g_nancy->getGameType() != kGameTypeVampire) {
-		loadCifTree("ciftree", "dat");
-	}
-
-	if (g_nancy->getGameType() >= kGameTypeNancy7) {
-		loadCifTree("promotree", "dat");
-	}
-}
-
-bool ResourceManager::getCifInfo(const Common::String &name, CifInfo &info) const {
-	for (const auto &tree : _cifTrees) {
-		if (getCifInfo(tree->getName(), name, info)) {
-			return true;
+Common::String ResourceManager::getCifDescription(const Common::String &treeName, const Common::String &name) const {
+	const CifTree *tree = nullptr;
+	if (treeName.size()) {
+		tree = (const CifTree *)SearchMan.getArchive(treePrefix + treeName);
+	} else {
+		for (uint i = 0; i < _cifTreeNames.size(); ++i) {
+			// No provided tree name, check inside every loaded tree
+			if (SearchMan.getArchive(treePrefix + _cifTreeNames[i])->hasFile(name)) {
+				tree = (const CifTree *)SearchMan.getArchive(treePrefix + _cifTreeNames[i]);
+				break;
+			}
 		}
 	}
 
-	return false;
-}
-
-bool ResourceManager::getCifInfo(const Common::String &treeName, const Common::String &name, CifInfo &info) const {
-	const CifFile *cifFile = CifFile::load(name);
-
-	if (cifFile) {
-		cifFile->getCifInfo(info);
-		delete cifFile;
-		return true;
+	if (!tree) {
+		error("Couldn't find CifInfo struct inside loaded CifTrees");
 	}
 
-	const CifTree *cifTree = findCifTree(treeName);
+	const CifInfo &info = tree->getCifInfo(name);
 
-	if (!cifTree)
-		return false;
+	Common::String desc;
+	desc = Common::String::format("Name: %s\n", info.name.c_str());
+	desc += Common::String::format("Type: %i\n", info.type);
+	desc += Common::String::format("Compression: %i\n", info.comp);
+	desc += Common::String::format("Size: %i\n", info.size);
+	desc += Common::String::format("Compressed size: %i\n", info.compressedSize);
+	desc += Common::String::format("Width: %i\n", info.width);
+	desc += Common::String::format("Pitch: %i\n", info.pitch);
+	desc += Common::String::format("Height: %i\n", info.height);
+	desc += Common::String::format("Bit depth: %i\n", info.depth);
 
-	return cifTree->getCifInfo(name, info);
+	return desc;
 }
 
-byte *ResourceManager::getCifData(const Common::String &name, CifInfo &info, uint *size) const {
-	// Try to open name.cif
-	const CifFile *cifFile = CifFile::load(name);
-	byte *buf = nullptr;
-
-	// Look for cif inside cif tree
-	if (cifFile) {
-		buf = cifFile->getCifData(info, size);
-		delete cifFile;
-	} else {
-		for (auto &tree : _cifTrees) {
-			buf = tree->getCifData(name, info, size);
-			if (buf) {
-				break;
+void ResourceManager::list(const Common::String &treeName, Common::StringArray &outList, CifInfo::ResType type) const {
+	if (treeName.size()) {
+		const CifTree *tree = (const CifTree *)SearchMan.getArchive(treePrefix + treeName);
+		if (!tree) {
+			return;
+		}
+		for (auto &i : tree->_fileMap) {
+			if (type == CifInfo::kResTypeAny || i._value.type == type) {
+				outList.push_back(i._key);
 			}
 		}
-	}
-
-	if (buf && info.comp == kResCompression) {
-		Common::MemoryReadStream input(buf, info.compressedSize);
-		byte *raw = new byte[info.size];
-		Common::MemoryWriteStream output(raw, info.size);
-		if (!_dec->decompress(input, output)) {
-			warning("Failed to decompress '%s'", name.c_str());
-			delete[] buf;
-			delete[] raw;
-			return nullptr;
+	} else {
+		for (uint i = 0; i < _cifTreeNames.size(); ++i) {
+			// No provided tree name, check inside every loaded tree
+			const CifTree *tree = (const CifTree *)SearchMan.getArchive(treePrefix + _cifTreeNames[i]);
+			for (auto &it : tree->_fileMap) {
+				if (type == CifInfo::kResTypeAny || it._value.type == type) {
+					outList.push_back(it._key);
+				}
+			}
 		}
-		delete[] buf;
-		if (size)
-			*size = output.size();
-		return raw;
 	}
-
-	return buf;
-}
-
-byte *ResourceManager::loadCif(const Common::String &treeName, const Common::String &name, uint &size) {
-	CifInfo info;
-	return getCifData(treeName, name, info, &size);
 }
 
 bool ResourceManager::exportCif(const Common::String &treeName, const Common::String &name) {
-	CifInfo info;
-	uint size;
-	byte *buf = getCifData(name, info, &size);
-
-	if (!buf)
+	if (!SearchMan.hasFile(name)) {
 		return false;
-
-	// Find out what CIF version this game uses
-	uint32 version = 0;
-	if (_cifTrees.size() > 0)
-		version = _cifTrees[0]->getVersion();
-
-	bool retval = false;
-	const CifExporter *exp = CifExporter::create(version);
-	if (exp) {
-		retval = exp->dump(buf, size, info);
-		delete exp;
 	}
-	return retval;
-}
 
-byte *ResourceManager::loadData(const Common::String &name, uint &size) {
+	// First, look for a loose .cif file
 	CifInfo info;
-	byte *buf = getCifData(name, info, &size);
-
-	if (!buf) {
-		// Data was not found inside a cif tree or a cif file, try to open an .iff file
-		// This is used by The Vampire Diaries
-		Common::File f;
-		if (f.open(name.hasSuffixIgnoreCase(".iff") ? name : name + ".iff")) {
-			size = f.size();
-			buf = new byte[size];
-			f.read(buf, size);
+	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(name + ".cif");
+	if (stream) {
+		// .cifs are compressed, so we need to extract
+		CifFile cifFile(stream, name); // cifFile takes ownership of the current stream
+		stream = cifFile.createReadStreamRaw();
+		info = cifFile._info;
+	}
+
+	if (!stream) {
+		// Then, look for an external .iff. These are uncompressed
+		stream = SearchMan.createReadStreamForMember(name + ".iff");
+		if (stream) {
+			info.comp = CifInfo::kResCompressionNone;
+			info.type = CifInfo::kResTypeScript;
+			info.name = name;
+			info.compressedSize = info.size = stream->size();
 		} else {
-			return nullptr;
-		}
-	} else if (info.type != kResTypeScript) {
-		warning("Resource '%s' is not a script", name.c_str());
-		delete[] buf;
-		return nullptr;
-	}
-
-	return buf;
-}
-
-bool ResourceManager::loadImage(const Common::String &name, Graphics::Surface &surf, const Common::String treeName, Common::Rect *outSrc, Common::Rect *outDest) {
-	CifInfo info;
-	surf.free();
-
-	byte *buf = nullptr;
-	uint bufSize = 0;
-
-	if (treeName.size()) {
-		buf = getCifData(treeName, name, info, &bufSize);
-	} else {
-		buf = getCifData(name, info, &bufSize);
-	}
+			// Look inside ciftrees
+			const CifTree *tree = nullptr;
+			for (uint j = 0; j < _cifTreeNames.size(); ++j) {
+				if (SearchMan.getArchive(treePrefix + _cifTreeNames[j])->hasFile(name)) {
+					tree = (const CifTree *)SearchMan.getArchive(treePrefix + _cifTreeNames[j]);
+					break;
+				}
+			}
 
-	if (!buf && treeName.size() > 0) {
-		// Couldn't find image in a cif tree, try to open a .bmp file
-		// This is used by The Vampire Diaries
-		Common::File f;
-		if (f.open(name + ".bmp")) {
-			Image::BitmapDecoder dec;
-			if (dec.loadStream(f)) {
-				surf.copyFrom(*dec.getSurface());
-				return true;
+			if (tree) {
+				stream = tree->createReadStreamRaw(name);
+				info = tree->getCifInfo(name);
 			} else {
-				return false;
+				// Finally, use SearchMan to get a loose file. This is useful if we want to add files that
+				// would regularly not be in a ciftree (e.g. sounds)
+				stream = SearchMan.createReadStreamForMember(name);
+				if (!stream) {
+					warning("Couldn't open resource %s", name.c_str());
+					return false;
+				}
+
+				info.comp = CifInfo::kResCompressionNone;
+				info.type = CifInfo::kResTypeScript;
+				info.name = name;
+				info.compressedSize = info.size = stream->size();
 			}
-		} else {
-			return false;
-		}
-	} else {
-		if (info.type != kResTypeImage) {
-			warning("Resource '%s' is not an image", name.c_str());
-			delete[] buf;
-			return false;
-		}
-
-		if (info.depth != 16) {
-			warning("Image '%s' has unsupported depth %i", name.c_str(), info.depth);
-			delete[] buf;
-			return false;
 		}
 	}
 
-	if (outSrc) {
-		*outSrc = info.src;
-	}
+	CifFile file;
+	file._info = info;
 
-	if (outDest) {
-		*outDest = info.dest;
-	}
+	Common::DumpFile dump;
+	dump.open(name + ".cif");
 
-	surf.w = info.width;
-	surf.h = info.height;
-	surf.pitch = info.pitch;
-	surf.setPixels(buf);
-	surf.format = g_nancy->_graphicsManager->getInputPixelFormat();
-	#ifdef SCUMM_BIG_ENDIAN
-	if (info.depth == 16) {
-		for (uint i = 0; i < bufSize / 2; ++i) {
-			((uint16 *)buf)[i] = SWAP_BYTES_16(((uint16 *)buf)[i]);
-		}
-	}
-	#endif
+	Common::Serializer ser(nullptr, &dump);
+	file.sync(ser);
+
+	dump.writeStream(stream);
+	dump.close();
+
+	delete stream;
 	return true;
 }
 
-bool ResourceManager::loadImage(const Common::String &name, Graphics::ManagedSurface &surf, const Common::String treeName, Common::Rect *outSrc, Common::Rect *outDest) {
-	CifInfo info;
-	surf.free();
-
-	byte *buf = nullptr;
-	uint bufSize = 0;
+bool ResourceManager::exportCifTree(const Common::String &treeName, const Common::StringArray &names) {
+	Common::Array<Common::SeekableReadStream *> resStreams;
+	CifTree file;
 
-	if (treeName.size()) {
-		buf = getCifData(treeName, name, info, &bufSize);
+	uint32 headerSize = 1024 * 2;
+	uint32 infoSize = 0;
+	if (g_nancy->getGameType() <= kGameTypeNancy1) {
+		headerSize += 30;
+		infoSize = 38;
 	} else {
-		buf = getCifData(name, info, &bufSize);
-	}
-
-	if (!buf) {
-		// Couldn't find image in a cif tree, try to open a .bmp file
-		// This is used by The Vampire Diaries
-		Common::File f;
-		if (treeName.size() == 0 && f.open(name + ".bmp")) {
-			Image::BitmapDecoder dec;
-			if (dec.loadStream(f)) {
-				GraphicsManager::copyToManaged(*dec.getSurface(), surf);
-				surf.setPalette(dec.getPalette(), dec.getPaletteStartIndex(), MIN<uint>(256, dec.getPaletteColorCount())); // LOGO.BMP reports 257 colors
-				return true;
-			} else {
-				return false;
-			}
+		headerSize += 32;
+		if (g_nancy->getGameType() <= kGameTypeNancy2) {
+			// Format 1, short filenames
+			infoSize = 70;
 		} else {
-			return false;
-		}
-	} else {
-		if (info.type != kResTypeImage) {
-			warning("Resource '%s' is not an image", name.c_str());
-			delete[] buf;
-			return false;
-		}
-
-		if (info.depth != 16) {
-			warning("Image '%s' has unsupported depth %i", name.c_str(), info.depth);
-			delete[] buf;
-			return false;
-		}
-
-		if (outSrc) {
-			*outSrc = info.src;
+			// Format 1 or 2*, with long filenames
+			infoSize = 94;
 		}
+	}
 
-		if (outDest) {
-			*outDest = info.dest;
+	for (uint i = 0; i < names.size(); ++i) {
+		// First, look for loose .cif files
+		CifInfo info;
+		Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(names[i] + ".cif");
+		if (stream) {
+			// .cifs are compressed, so we need to extract
+			CifFile cifFile(stream, names[i]); // cifFile takes ownership of the current stream
+			stream = cifFile.createReadStreamRaw();
+			info = cifFile._info;
 		}
 
-		#ifdef SCUMM_BIG_ENDIAN
-		if (info.depth == 16) {
-			for (uint i = 0; i < bufSize / 2; ++i) {
-				((uint16 *)buf)[i] = SWAP_BYTES_16(((uint16 *)buf)[i]);
+		if (!stream) {
+			// Then, look for external .iff. These are uncompressed
+			stream = SearchMan.createReadStreamForMember(names[i] + ".iff");
+			if (stream) {
+				info.comp = CifInfo::kResCompressionNone;
+				info.type = CifInfo::kResTypeScript;
+				info.name = names[i];
+				info.compressedSize = info.size = stream->size();
+			} else {
+				// Look inside ciftrees
+				const CifTree *tree = nullptr;
+				for (uint j = 0; j < _cifTreeNames.size(); ++j) {
+					if (SearchMan.getArchive(treePrefix + _cifTreeNames[j])->hasFile(names[i])) {
+						tree = (const CifTree *)SearchMan.getArchive(treePrefix + _cifTreeNames[j]);
+						break;
+					}
+				}
+
+				if (tree) {
+					stream = tree->createReadStreamRaw(names[i]);
+					info = tree->getCifInfo(names[i]);
+				} else {
+					// Finally, use SearchMan to get a loose file. This is useful if we want to add files that
+					// would regularly not be in a ciftree (e.g. sounds)
+					stream = SearchMan.createReadStreamForMember(names[i]);
+					if (!stream) {
+						warning("Couldn't open resource %s", names[i].c_str());
+						continue;
+					}
+
+					info.comp = CifInfo::kResCompressionNone;
+					info.type = CifInfo::kResTypeScript;
+					info.name = names[i];
+					info.compressedSize = info.size = stream->size();
+				}
 			}
 		}
-		#endif
+		
+		info.dataOffset = headerSize + file._fileMap.size() * infoSize; // Initial offset after header/infos
+		for (uint j = 0; j < resStreams.size(); ++j) {
+			info.dataOffset += resStreams[j]->size(); // Final offset, following raw data of previous files
+		}
 
-		GraphicsManager::copyToManaged(buf, surf, info.width, info.height, g_nancy->_graphicsManager->getInputPixelFormat());
-		return true;
+		resStreams.push_back(stream);
+		file._writeFileMap.push_back(info);
 	}
-}
 
-void ResourceManager::list(const Common::String &treeName, Common::Array<Common::String> &nameList, uint type) const {
-	const CifTree *cifTree = findCifTree(treeName);
+	Common::DumpFile dump;
+	dump.open(treeName + ".dat");
 
-	if (!cifTree) {
-		Common::ArchiveMemberList list;
-		if (type == ResourceManager::kResTypeAny || type == ResourceManager::kResTypeImage) {
-			SearchMan.listMatchingMembers(list, Common::Path("*.bmp"));
-		}
-
-		if (type == ResourceManager::kResTypeAny || type == ResourceManager::kResTypeScript) {
-			SearchMan.listMatchingMembers(list, Common::Path("*.iff"));
-		}
+	Common::Serializer ser(nullptr, &dump);
+	file.sync(ser);
 
-		for (auto &i : list) {
-			nameList.push_back(i.get()->getDisplayName());
-		}
-	} else {
-		cifTree->list(nameList, type);
+	for (uint i = 0; i < resStreams.size(); ++i) {
+		dump.writeStream(resStreams[i]);
+		delete resStreams[i];
 	}
 
-	Common::sort(nameList.begin(), nameList.end());
-}
-
-Common::String ResourceManager::getCifDescription(const Common::String &treeName, const Common::String &name) const {
-	CifInfo info;
-	if (!getCifInfo(treeName, name, info))
-		return Common::String::format("Couldn't find '%s' in CifTree '%s'\n", name.c_str(), treeName.c_str());
-
-	Common::String desc;
-	desc = Common::String::format("Name: %s\n", info.name.c_str());
-	desc += Common::String::format("Type: %i\n", info.type);
-	desc += Common::String::format("Compression: %i\n", info.comp);
-	desc += Common::String::format("Size: %i\n", info.size);
-	desc += Common::String::format("Compressed size: %i\n", info.compressedSize);
-	desc += Common::String::format("Width: %i\n", info.width);
-	desc += Common::String::format("Pitch: %i\n", info.pitch);
-	desc += Common::String::format("Height: %i\n", info.height);
-	desc += Common::String::format("Bit depth: %i\n", info.depth);
-	return desc;
+	dump.close();
+	return true;
 }
 
 } // End of namespace Nancy
diff --git a/engines/nancy/resource.h b/engines/nancy/resource.h
index 322eb6a0098..671d9882c21 100644
--- a/engines/nancy/resource.h
+++ b/engines/nancy/resource.h
@@ -22,8 +22,7 @@
 #ifndef NANCY_RESOURCE_H
 #define NANCY_RESOURCE_H
 
-#include "common/array.h"
-#include "common/rect.h"
+#include "engines/nancy/cif.h"
 
 namespace Graphics {
 struct Surface;
@@ -32,58 +31,43 @@ class ManagedSurface;
 
 namespace Nancy {
 
-class NancyEngine;
-class CifTree;
-class Decompressor;
+class IFF;
 
 class ResourceManager {
+	friend class NancyConsole;
+	friend class NancyEngine;
 public:
-	enum ResType {
-		kResTypeAny,
-		// Type 1 seems to be obsolete
-		kResTypeImage = 2,
-		kResTypeScript
-	};
-
-	enum ResCompression {
-		kResCompressionNone = 1,
-		kResCompression
-	};
-
-	struct CifInfo {
-		Common::String name;
-		byte type; // ResType
-		byte comp; // ResCompression
-		uint16 width, pitch, height;
-		byte depth; // Bit depth
-		uint32 compressedSize, size;
-		Common::Rect src, dest; // Used when drawing conversation cels
-	};
-
-	ResourceManager();
+	ResourceManager() {}
 	~ResourceManager();
 
-	void initialize();
-	bool loadCifTree(const Common::String &name, const Common::String &ext);
-	bool loadImage(const Common::String &name, Graphics::Surface &surf, const Common::String treeName = "", Common::Rect *outSrc = nullptr, Common::Rect *outDest = nullptr);
+	// Load an image resource. Can be either external .bmp file, or raw image data embedded inside a ciftree
+	// Ciftree images may have additional data dictating how they need to be blitted on screen (see ConversationCel).
+	// This is accessed via the outSrc/outDest parameters.
 	bool loadImage(const Common::String &name, Graphics::ManagedSurface &surf, const Common::String treeName = "", Common::Rect *outSrc = nullptr, Common::Rect *outDest = nullptr);
-	byte *loadData(const Common::String &name, uint &size);
+	
+	// Loads a single IFF file. These can either be inside standalone .cif files, or embedded inside a ciftree
+	IFF *loadIFF(const Common::String &name);
 
-	// Debugger functions
-	void list(const Common::String &treeName, Common::Array<Common::String> &nameList, uint type) const;
-	byte *loadCif(const Common::String &treeName, const Common::String &name, uint &size);
-	bool exportCif(const Common::String &treeName, const Common::String &name);
+	// Load a new ciftree
+	bool readCifTree(const Common::String &name, const Common::String &ext, int priority);
+
+private:
+	// Debug functions
+
+	// Return a human-readable description of a single CIF file.
 	Common::String getCifDescription(const Common::String &treeName, const Common::String &name) const;
 
+	// Return a list of all resources of a certain type (does not list external files)
+	void list(const Common::String &treeName, Common::StringArray &outList, CifInfo::ResType type) const;
+
+	// Exports a single resource as a standalone .cif file
+	bool exportCif(const Common::String &treeName, const Common::String &name);
+
+	// Exports a collection of resources as a ciftree
+	bool exportCifTree(const Common::String &treeName, const Common::StringArray &names);
+
 private:
-	byte *getCifData(const Common::String &name, CifInfo &info, uint *size = nullptr) const;
-	byte *getCifData(const Common::String &treeName, const Common::String &name, CifInfo &info, uint *size = nullptr) const;
-	bool getCifInfo(const Common::String &name, CifInfo &info) const;
-	bool getCifInfo(const Common::String &treeName, const Common::String &name, CifInfo &info) const;
-	const CifTree *findCifTree(const Common::String &name) const;
-
-	Common::Array<const CifTree *> _cifTrees;
-	Decompressor *_dec;
+	Common::Array<Common::String> _cifTreeNames;
 };
 
 } // End of namespace Nancy
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 54b69c79f7e..53683a1d27c 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -30,6 +30,7 @@
 #include "engines/nancy/graphics.h"
 #include "engines/nancy/cursor.h"
 #include "engines/nancy/util.h"
+#include "engines/nancy/resource.h"
 
 #include "engines/nancy/state/scene.h"
 #include "engines/nancy/state/map.h"
@@ -839,17 +840,17 @@ void Scene::load(bool fromSaveFile) {
 
 	// Scene IDs are prefixed with S inside the cif tree; e.g 100 -> S100
 	Common::String sceneName = Common::String::format("S%u", _sceneState.nextScene.sceneID);
-	IFF sceneIFF(sceneName);
+	IFF *sceneIFF = g_nancy->_resource->loadIFF(sceneName);
 
-	if (!sceneIFF.load()) {
+	if (!sceneIFF) {
 		error("Faled to load IFF %s", sceneName.c_str());
 	}
 
-	Common::SeekableReadStream *sceneSummaryChunk = sceneIFF.getChunkStream("SSUM");
+	Common::SeekableReadStream *sceneSummaryChunk = sceneIFF->getChunkStream("SSUM");
 	if (sceneSummaryChunk) {
 		_sceneState.summary.read(*sceneSummaryChunk);
 	} else {
-		sceneSummaryChunk = sceneIFF.getChunkStream("TSUM");
+		sceneSummaryChunk = sceneIFF->getChunkStream("TSUM");
 		if (sceneSummaryChunk) {
 			_sceneState.summary.readTerse(*sceneSummaryChunk);
 		}
@@ -880,7 +881,7 @@ void Scene::load(bool fromSaveFile) {
 	Common::SeekableReadStream *actionRecordChunk = nullptr;
 
 	uint numRecords = 0;
-	while (actionRecordChunk = sceneIFF.getChunkStream("ACT", numRecords), actionRecordChunk != nullptr) {
+	while (actionRecordChunk = sceneIFF->getChunkStream("ACT", numRecords), actionRecordChunk != nullptr) {
 		_actionManager.addNewActionRecord(*actionRecordChunk);
 		delete actionRecordChunk;
 		++numRecords;
@@ -932,6 +933,7 @@ void Scene::load(bool fromSaveFile) {
 		_flags.sceneCounts.getOrCreateVal(_sceneState.currentScene.sceneID)++;
 	}
 
+	delete sceneIFF;
 	_state = kStartSound;
 }
 


Commit: 172984be7b870724577618d78d48b34c0f24e6e7
    https://github.com/scummvm/scummvm/commit/172984be7b870724577618d78d48b34c0f24e6e7
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-26T19:46:32+03:00

Commit Message:
NANCY: Fix nancy2 dialogue freeze

Fixed an issue in nancy2 where in the very first conversation
the scene wouldn't change after Nancy is done talking.

Changed paths:
    engines/nancy/action/conversation.cpp


diff --git a/engines/nancy/action/conversation.cpp b/engines/nancy/action/conversation.cpp
index e4271770cd6..e66f2d2c386 100644
--- a/engines/nancy/action/conversation.cpp
+++ b/engines/nancy/action/conversation.cpp
@@ -243,19 +243,19 @@ void ConversationSound::execute() {
 		}
 		break;
 	case kActionTrigger:
-		// process flags structs
-		for (auto &flags : _flagsStructs) {
-			if (flags.conditions.isSatisfied()) {
-				flags.flagToSet.set();
+		if (!g_nancy->_sound->isSoundPlaying(_responseGenericSound)) {
+			// process flags structs
+			for (auto &flags : _flagsStructs) {
+				if (flags.conditions.isSatisfied()) {
+					flags.flagToSet.set();
+				}
 			}
-		}
 
-		if (_pickedResponse != -1) {
-			// Set response's event flag, if any
-			NancySceneState.setEventFlag(_responses[_pickedResponse].flagDesc);
-		}
+			if (_pickedResponse != -1) {
+				// Set response's event flag, if any
+				NancySceneState.setEventFlag(_responses[_pickedResponse].flagDesc);
+			}
 
-		if (!g_nancy->_sound->isSoundPlaying(_responseGenericSound)) {
 			g_nancy->_sound->stopSound(_responseGenericSound);
 
 			if (_pickedResponse != -1) {


Commit: f0ba64cc0894de59e9b7046f07aff044d7c8ecd5
    https://github.com/scummvm/scummvm/commit/f0ba64cc0894de59e9b7046f07aff044d7c8ecd5
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-26T19:46:32+03:00

Commit Message:
NANCY: Add ciftree_export console command

Changed paths:
    engines/nancy/console.cpp
    engines/nancy/console.h


diff --git a/engines/nancy/console.cpp b/engines/nancy/console.cpp
index 5b14af25596..1795f3c46d9 100644
--- a/engines/nancy/console.cpp
+++ b/engines/nancy/console.cpp
@@ -45,6 +45,7 @@ namespace Nancy {
 NancyConsole::NancyConsole() : GUI::Debugger() {
 	registerCmd("load_cal", WRAP_METHOD(NancyConsole, Cmd_loadCal));
 	registerCmd("cif_export", WRAP_METHOD(NancyConsole, Cmd_cifExport));
+	registerCmd("ciftree_export", WRAP_METHOD(NancyConsole, Cmd_ciftreeExport));
 	registerCmd("cif_list", WRAP_METHOD(NancyConsole, Cmd_cifList));
 	registerCmd("cif_info", WRAP_METHOD(NancyConsole, Cmd_cifInfo));
 	registerCmd("chunk_export", WRAP_METHOD(NancyConsole, Cmd_chunkExport));
@@ -162,7 +163,26 @@ bool NancyConsole::Cmd_cifExport(int argc, const char **argv) {
 	if (!g_nancy->_resource->exportCif((argc == 2 ? "" : argv[2]), argv[1]))
 		debugPrintf("Failed to export '%s'\n", argv[1]);
 
-	return true;
+	return cmdExit(0, nullptr);
+}
+
+bool NancyConsole::Cmd_ciftreeExport(int argc, const char **argv) {
+	if (argc < 3) {
+		debugPrintf("Exports the specified resources to a ciftree\n");
+		debugPrintf("Usage: %s <tree name> <files...>\n", argv[0]);
+		return true;
+	}
+
+	Common::StringArray files;
+
+	for (int i = 2; i < argc; ++i) {
+		files.push_back(argv[i]);
+	}
+
+	if (!g_nancy->_resource->exportCifTree(argv[1], files))
+		debugPrintf("Failed to export '%s'\n", argv[1]);
+
+	return cmdExit(0, nullptr);
 }
 
 bool NancyConsole::Cmd_cifList(int argc, const char **argv) {
diff --git a/engines/nancy/console.h b/engines/nancy/console.h
index b5ca36af02b..268d5942c3c 100644
--- a/engines/nancy/console.h
+++ b/engines/nancy/console.h
@@ -43,6 +43,7 @@ public:
 private:
 	bool Cmd_loadCal(int argc, const char **argv);
 	bool Cmd_cifExport(int argc, const char **argv);
+	bool Cmd_ciftreeExport(int argc, const char **argv);
 	bool Cmd_cifList(int argc, const char **argv);
 	bool Cmd_cifInfo(int argc, const char **argv);
 	bool Cmd_chunkExport(int argc, const char **argv);


Commit: 97f4938bb2fc72bdc95421ed436f0235ae2972e2
    https://github.com/scummvm/scummvm/commit/97f4938bb2fc72bdc95421ed436f0235ae2972e2
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-26T19:46:32+03:00

Commit Message:
NANCY: Add keymap for displaying raycast maze map

It seems the raycast puzzle map wasn't actually a debug
feature in the original engine, and was actually toggleable
even outside god mode. This commit adds a keymap for
displaying it, and makes sure that keymap only appears in
nancy3 and nancy6 (since other games do not have
this puzzle)

Changed paths:
    engines/nancy/action/puzzle/raycastpuzzle.cpp
    engines/nancy/input.cpp
    engines/nancy/input.h
    engines/nancy/metaengine.cpp


diff --git a/engines/nancy/action/puzzle/raycastpuzzle.cpp b/engines/nancy/action/puzzle/raycastpuzzle.cpp
index e3c0af9044e..e7f637e6782 100644
--- a/engines/nancy/action/puzzle/raycastpuzzle.cpp
+++ b/engines/nancy/action/puzzle/raycastpuzzle.cpp
@@ -946,7 +946,6 @@ bool RaycastDeferredLoader::loadInner() {
 		break;
 	case kInitMap : {
 		_owner.drawMap();
-		// TODO: Add console command for setting debug features (map, player height, screen size, ghost mode, etc.)
 		_owner._map.setVisible(false);
 
 		_loadState = kInitTables1;
@@ -1131,6 +1130,10 @@ void RaycastPuzzle::handleInput(NancyInput &input) {
 	if (_state != kRun) {
 		return;
 	}
+
+	if (input.input & NancyInput::kRaycastMap) {
+		_map.setVisible(!_map.isVisible());
+	}
 	
 	uint32 time = g_nancy->getTotalPlayTime();
 	uint32 deltaTime = time - _lastMovementTime;
diff --git a/engines/nancy/input.cpp b/engines/nancy/input.cpp
index 9308d683f9c..b589ddfe375 100644
--- a/engines/nancy/input.cpp
+++ b/engines/nancy/input.cpp
@@ -34,7 +34,7 @@ void InputManager::processEvents() {
 	using namespace Common;
 	Common::Event event;
 
-	_inputs &= ~(NancyInput::kLeftMouseButtonDown | NancyInput::kLeftMouseButtonUp | NancyInput::kRightMouseButtonDown | NancyInput::kRightMouseButtonUp);
+	_inputs &= ~(NancyInput::kLeftMouseButtonDown | NancyInput::kLeftMouseButtonUp | NancyInput::kRightMouseButtonDown | NancyInput::kRightMouseButtonUp | NancyInput::kRaycastMap);
 	_otherKbdInput.clear();
 
 	while (g_nancy->getEventManager()->pollEvent(event)) {
@@ -74,6 +74,9 @@ void InputManager::processEvents() {
 			case kNancyActionOpenMainMenu:
 				_inputs |= NancyInput::kOpenMainMenu;
 				break;
+			case kNancyActionShowRaycastMap:
+				_inputs |= NancyInput::kRaycastMap;
+				break;
 			default:
 				break;
 			}
@@ -107,6 +110,9 @@ void InputManager::processEvents() {
 			case kNancyActionOpenMainMenu:
 				_inputs &= ~NancyInput::kOpenMainMenu;
 				break;
+			case kNancyActionShowRaycastMap:
+				_inputs &= ~NancyInput::kRaycastMap;
+				break;
 			default:
 				break;
 			}
@@ -149,7 +155,7 @@ void InputManager::forceCleanInput() {
 	_otherKbdInput.clear();
 }
 
-void InputManager::initKeymaps(Common::KeymapArray &keymaps) {
+void InputManager::initKeymaps(Common::KeymapArray &keymaps, const char *target) {
 	using namespace Common;
 	using namespace Nancy;
 
@@ -205,6 +211,15 @@ void InputManager::initKeymaps(Common::KeymapArray &keymaps) {
 	act->addDefaultInputMapping("ESCAPE");
 	act->addDefaultInputMapping("JOY_START");
 	mainKeymap->addAction(act);
+	
+	Common::String t(target);
+	if (t.hasPrefix("nancy3") || t.hasPrefix("nancy6")) {
+		act = new Action("RAYCM", _("Show/hide maze map"));
+		act->setCustomEngineActionEvent(kNancyActionShowRaycastMap);
+		act->addDefaultInputMapping("m");
+		act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
+		mainKeymap->addAction(act);
+	}
 
 	keymaps.push_back(mainKeymap);
 }
diff --git a/engines/nancy/input.h b/engines/nancy/input.h
index 92762b3089e..de816b4a597 100644
--- a/engines/nancy/input.h
+++ b/engines/nancy/input.h
@@ -52,6 +52,7 @@ struct NancyInput {
 		kMoveRight				= 1 << 9,
 		kMoveFastModifier		= 1 << 10,
 		kOpenMainMenu			= 1 << 11,
+		kRaycastMap				= 1 << 12,
 
 		kLeftMouseButton		= kLeftMouseButtonDown | kLeftMouseButtonHeld | kLeftMouseButtonUp,
 		kRightMouseButton		= kRightMouseButtonDown | kRightMouseButtonHeld | kRightMouseButtonUp
@@ -77,7 +78,8 @@ enum NancyAction {
 	kNancyActionMoveFast,
 	kNancyActionLeftClick,
 	kNancyActionRightClick,
-	kNancyActionOpenMainMenu
+	kNancyActionOpenMainMenu,
+	kNancyActionShowRaycastMap
 };
 
 public:
@@ -92,7 +94,7 @@ public:
 	void forceCleanInput();
 	void setMouseInputEnabled(bool enabled) { _mouseEnabled = enabled; }
 
-	static void initKeymaps(Common::KeymapArray &keymaps);
+	static void initKeymaps(Common::KeymapArray &keymaps, const char *target);
 
 private:
 	uint16 _inputs;
diff --git a/engines/nancy/metaengine.cpp b/engines/nancy/metaengine.cpp
index 890068c1909..b82898b64f6 100644
--- a/engines/nancy/metaengine.cpp
+++ b/engines/nancy/metaengine.cpp
@@ -91,7 +91,7 @@ public:
 
 Common::KeymapArray NancyMetaEngine::initKeymaps(const char *target) const {
 	Common::KeymapArray keymaps;
-	Nancy::InputManager::initKeymaps(keymaps);
+	Nancy::InputManager::initKeymaps(keymaps, target);
 	return keymaps;
 }
 


Commit: be0feffb55bdf1358100652469bbdd630e27d86a
    https://github.com/scummvm/scummvm/commit/be0feffb55bdf1358100652469bbdd630e27d86a
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-26T19:46:32+03:00

Commit Message:
NANCY: Load Autotext surfaces from ResourceManager

Added checks to loadImage() for the special strings used
to indicate an autotext surface instead of an image file

Changed paths:
    engines/nancy/action/overlay.cpp
    engines/nancy/action/puzzle/peepholepuzzle.cpp
    engines/nancy/resource.cpp


diff --git a/engines/nancy/action/overlay.cpp b/engines/nancy/action/overlay.cpp
index b737cbb55e1..73bef84bde2 100644
--- a/engines/nancy/action/overlay.cpp
+++ b/engines/nancy/action/overlay.cpp
@@ -37,21 +37,12 @@ namespace Nancy {
 namespace Action {
 
 void Overlay::init() {
-	// Check for special autotext strings, and use the requested surface as source
-	if (_imageName.hasPrefix("USE_AUTOTEXT")) {
-		uint surfID = _imageName[12] - '1';
-		Graphics::ManagedSurface &surf = g_nancy->_graphicsManager->getAutotextSurface(surfID);
-		_fullSurface.create(surf, surf.getBounds());
+	// Autotext overlays need special handling when blitting
+	if (_imageName.hasPrefix("USE_")) {
 		_usesAutotext = true;
-	} else if (_imageName.hasPrefix("USE_AUTOJOURNAL")) {
-		uint surfID = _imageName.substr(15).asUint64() + 2;
-		Graphics::ManagedSurface &surf = g_nancy->_graphicsManager->getAutotextSurface(surfID);
-		_fullSurface.create(surf, surf.getBounds());
-		_usesAutotext = true;
-	} else {
-		// No autotext, load image source
-		g_nancy->_resource->loadImage(_imageName, _fullSurface);
 	}
+	
+	g_nancy->_resource->loadImage(_imageName, _fullSurface);
 
 	setFrame(_firstFrame);
 
diff --git a/engines/nancy/action/puzzle/peepholepuzzle.cpp b/engines/nancy/action/puzzle/peepholepuzzle.cpp
index 58ff2c5c330..b4541a67ab7 100644
--- a/engines/nancy/action/puzzle/peepholepuzzle.cpp
+++ b/engines/nancy/action/puzzle/peepholepuzzle.cpp
@@ -38,19 +38,7 @@ void PeepholePuzzle::init() {
 	_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphicsManager->getInputPixelFormat());
 	moveTo(screenBounds);
 
-	// Check for special autotext strings, and use the requested surface as source
-	if (_innerImageName.hasPrefix("USE_AUTOTEXT")) {
-		uint surfID = _innerImageName[12] - '1';
-		Graphics::ManagedSurface &surf = g_nancy->_graphicsManager->getAutotextSurface(surfID);
-		_innerImage.create(surf, surf.getBounds());
-	} else if (_innerImageName.hasPrefix("USE_AUTOJOURNAL")) {
-		uint surfID = _innerImageName.substr(15).asUint64() + 2;
-		Graphics::ManagedSurface &surf = g_nancy->_graphicsManager->getAutotextSurface(surfID);
-		_innerImage.create(surf, surf.getBounds());
-	} else {
-		// No autotext, load image source
-		g_nancy->_resource->loadImage(_innerImageName, _innerImage);
-	}
+	g_nancy->_resource->loadImage(_innerImageName, _innerImage);
 
 	if (!_buttonsImageName.size()) {
 		// Empty image name for buttons, use other image as source
diff --git a/engines/nancy/resource.cpp b/engines/nancy/resource.cpp
index 3a672335db7..2da7c01d6d8 100644
--- a/engines/nancy/resource.cpp
+++ b/engines/nancy/resource.cpp
@@ -35,6 +35,24 @@ static char treePrefix[] = "_tree_";
 namespace Nancy {
 
 bool ResourceManager::loadImage(const Common::String &name, Graphics::ManagedSurface &surf, const Common::String treeName, Common::Rect *outSrc, Common::Rect *outDest) {
+	// Detect and load autotext surfaces
+	if (name.hasPrefixIgnoreCase("USE_")) {
+		int surfID = -1;
+
+		if (name.hasPrefixIgnoreCase("USE_AUTOTEXT")) {
+			surfID = name[12] - '1';
+		} else if (name.hasPrefixIgnoreCase("USE_AUTOJOURNAL")) { // nancy6/7
+			surfID = name.substr(15).asUint64() + 2;
+		} else if (name.hasPrefixIgnoreCase("USE_AUTOLIST")) { // nancy8
+			surfID = name.substr(12).asUint64() + 2;
+		}
+
+		if (surfID >= 0) {
+			surf.copyFrom(g_nancy->_graphicsManager->getAutotextSurface(surfID));
+			return true;
+		}
+	}
+	
 	CifInfo info;
 	bool external = false;
 


Commit: e6c915186dc871275f19d0e5edffd197f8d1c909
    https://github.com/scummvm/scummvm/commit/e6c915186dc871275f19d0e5edffd197f8d1c909
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-26T19:46:32+03:00

Commit Message:
NANCY: Add missing override in Console

Changed paths:
    engines/nancy/console.h


diff --git a/engines/nancy/console.h b/engines/nancy/console.h
index 268d5942c3c..0d8830d9653 100644
--- a/engines/nancy/console.h
+++ b/engines/nancy/console.h
@@ -38,7 +38,7 @@ public:
 	NancyConsole();
 	virtual ~NancyConsole(void);
 
-	void postEnter();
+	void postEnter() override;
 
 private:
 	bool Cmd_loadCal(int argc, const char **argv);


Commit: c86abc68a075547a29db1e3f6bba7388da4af971
    https://github.com/scummvm/scummvm/commit/c86abc68a075547a29db1e3f6bba7388da4af971
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-26T19:46:32+03:00

Commit Message:
NANCY: Implement nancy7 LIFO autotext entries

Nancy7 added last-in-first-out ordering to journal entries,
which is now implemented and no longer causes a crash.

Changed paths:
    engines/nancy/action/autotext.cpp


diff --git a/engines/nancy/action/autotext.cpp b/engines/nancy/action/autotext.cpp
index c2a86d2c12f..86670ded214 100644
--- a/engines/nancy/action/autotext.cpp
+++ b/engines/nancy/action/autotext.cpp
@@ -77,6 +77,11 @@ void Autotext::execute() {
 		const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
 		assert(autotext);
 
+		bool isLIFO = (g_nancy->getGameType() == kGameTypeNancy7) && _surfaceID > 5; // This is nancy7-specific, later games implement LIFO in a different way
+		if (isLIFO) {
+			_surfaceID -= 3;
+		}
+
 		Common::String stringToPush;
 		auto &entriesForSurface = journalData->journalEntries[_surfaceID];
 		bool foundThisKey = false;
@@ -89,8 +94,15 @@ void Autotext::execute() {
 
 		if (!foundThisKey) {
 			// Key inside this Autotext instance wasn't found inside existing list, push it back and add it to string to draw
-			entriesForSurface.push_back(_textKey);
-			stringToPush += autotext->texts[_textKey];
+			if (!isLIFO) {
+				// Push at end
+				entriesForSurface.push_back(_textKey);
+				stringToPush += autotext->texts[_textKey];
+			} else {
+				// Insert at front
+				entriesForSurface.insert_at(0, _textKey);
+				stringToPush = autotext->texts[_textKey] + stringToPush;
+			}
 		}
 
 		addTextLine(stringToPush);


Commit: bbd760ddc1c0d3f45762d9b15d9770b3a6cf949a
    https://github.com/scummvm/scummvm/commit/bbd760ddc1c0d3f45762d9b15d9770b3a6cf949a
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-26T19:46:32+03:00

Commit Message:
NANCY: Enum cleanup

Removed an unused enum from Scene and reordered another
in commontypes.h

Changed paths:
    engines/nancy/commontypes.h
    engines/nancy/state/scene.h


diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index a29b96904d8..5ba851f09d5 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -128,24 +128,15 @@ enum MovementDirection : byte { kUp = 1, kDown = 2, kLeft = 4, kRight = 8, kMove
 // Separate namespace to remove possible clashes
 namespace NancyState {
 enum NancyState {
-	kBoot,
-	kLogo,
-	kCredits,
-	kMap,
-	kMainMenu,
-	kLoadSave,
-	kSetup,
-	// unknown/invalid
-	kHelp,
-	kScene,
-	// CD change
-	// Cheat,
-	kQuit,
-	// regain focus
+	// Original engine states
+	kBoot, kLogo, kCredits, kMap,
+	kMainMenu, kLoadSave, kSetup,
+	kHelp, kScene, kSaveDialog,
+
+	// Not real states
 	kNone,
-	kSaveDialog,
+	kQuit,
 	kPause, // only used when the GMM is on screen
-	kReloadSave
 };
 }
 
@@ -272,7 +263,7 @@ struct SoundDescription {
 // Structs inside nancy.dat, which contains all the data that was
 // originally stored inside the executable
 
-enum class StaticDataConditionType : byte { kEvent = 0, kInventory = 1, kDifficulty = 2 };
+enum class StaticDataConditionType { kEvent = 0, kInventory = 1, kDifficulty = 2 };
 struct StaticDataFlag { byte type; int16 label; byte flag; };
 
 struct ConditionalDialogue {
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index 09def5277eb..42698b7ebcd 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -75,16 +75,6 @@ class Scene : public State, public Common::Singleton<Scene> {
 	friend class Nancy::NancyEngine;
 
 public:
-	enum GameStateChange : byte {
-		kHelpMenu = 1 << 0,
-		kMainMenu = 1 << 1,
-		kSaveLoad = 1 << 2,
-		kReloadSave = 1 << 3,
-		kSetupMenu = 1 << 4,
-		kCredits = 1 << 5,
-		kMap = 1 << 6
-	};
-
 	struct SceneSummary {
 		// SSUM and TSUM
 		// Default values set to match those applied when loading from a TSUM chunk


Commit: 898068b9518588953288c5bcfafb2679f780963f
    https://github.com/scummvm/scummvm/commit/898068b9518588953288c5bcfafb2679f780963f
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-26T19:46:32+03:00

Commit Message:
NANCY: Cursor dependency improvements

Implemented the correct logic for a cursor dependency
that specifies _not_ holding an item, though I'm still not
sure that functionality is actually ever used. The "can't"
sounds for such a dependency will now also play correctly.

The "standard" cursor dependency (which will activate
only if the player is not holding anything) will now actually
activate; previously it had no chance of doing so, due to
incorrect int signedness. Again, it is unclear if this ever
actually gets used.

Changed paths:
    engines/nancy/action/actionmanager.cpp
    engines/nancy/action/actionmanager.h
    engines/nancy/enginedata.cpp
    engines/nancy/enginedata.h
    engines/nancy/state/scene.cpp
    engines/nancy/state/scene.h


diff --git a/engines/nancy/action/actionmanager.cpp b/engines/nancy/action/actionmanager.cpp
index 61d3ed7e054..2d7f5e0765e 100644
--- a/engines/nancy/action/actionmanager.cpp
+++ b/engines/nancy/action/actionmanager.cpp
@@ -72,7 +72,9 @@ void ActionManager::handleInput(NancyInput &input) {
 
 				if (!rec->_dependencies.satisfied) {
 					if (rec->_cursorDependency != nullptr) {
-						NancySceneState.playItemCantSound(rec->_cursorDependency->label);
+						NancySceneState.playItemCantSound(
+							rec->_cursorDependency->label,
+							(g_nancy->getGameType() <= kGameTypeNancy2 && rec->_cursorDependency->condition == kCursInvNotHolding));
 					} else {
 						continue;
 					}
@@ -83,9 +85,6 @@ void ActionManager::handleInput(NancyInput &input) {
 
 					if (rec->_cursorDependency) {
 						int16 item = rec->_cursorDependency->label;
-						if (item > 100 && item <= (100 + g_nancy->getStaticData().numItems)) {
-							item -= 100;
-						}
 
 						// Re-add the object to the inventory unless it's marked as a one-time use
 						if (item == NancySceneState.getHeldItem() && item != -1) {
@@ -467,16 +466,19 @@ void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &recor
 			} else {
 				bool isSatisfied = false;
 				int heldItem = NancySceneState.getHeldItem();
-				if (heldItem == -1 && dep.label == -2) {
+				if (heldItem == -1 && dep.label == kCursStandard) {
 					isSatisfied = true;
 				} else {
-					if (dep.label <= 100) {
+					if (g_nancy->getGameType() <= kGameTypeNancy2 && dep.condition == kCursInvNotHolding) {
+						// Activate if _not_ holding the specified item. Dropped in nancy3
+						if (heldItem != dep.label) {
+							isSatisfied = true;
+						}
+					} else {
+						// Activate if holding the specified item.
 						if (heldItem == dep.label) {
 							isSatisfied = true;
 						}
-					} else if (dep.label - 100 != heldItem) {
-						// IDs above 100 mean the record will activate when the object is _not_ the specified one
-						isSatisfied = true;
 					}
 				}
 
diff --git a/engines/nancy/action/actionmanager.h b/engines/nancy/action/actionmanager.h
index da11ad68614..534e7665fb8 100644
--- a/engines/nancy/action/actionmanager.h
+++ b/engines/nancy/action/actionmanager.h
@@ -52,7 +52,7 @@ class ActionManager {
 public:
 	static const byte kCursInvHolding			= 0;
 	static const byte kCursInvNotHolding		= 1;
-	static const byte kCursInvNotHoldingOffset	= 100;
+	static const byte kCursStandard				= 254;
 
 	ActionManager() {}
 	virtual ~ActionManager() {}
diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index 4937982283d..eea5ee62bfb 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -189,20 +189,20 @@ INV::INV(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 		if (s.getVersion() == kGameTypeNancy2) {
 			s.syncBytes(textBuf, 60);
 			textBuf[59] = '\0';
-			assembleTextLine((char *)textBuf, item.specificCantText, 60);
+			assembleTextLine((char *)textBuf, item.cantText, 60);
 
 			s.syncBytes(textBuf, 60);
 			textBuf[59] = '\0';
-			assembleTextLine((char *)textBuf, item.generalCantText, 60);
+			assembleTextLine((char *)textBuf, item.cantTextNotHolding, 60);
 
-			item.specificCantSound.readNormal(*chunkStream);
-			item.generalCantSound.readNormal(*chunkStream);
+			item.cantSound.readNormal(*chunkStream);
+			item.cantSoundNotHolding.readNormal(*chunkStream);
 		} else if (s.getVersion() >= kGameTypeNancy3) {
 			s.syncBytes(textBuf, 60);
 			textBuf[59] = '\0';
-			assembleTextLine((char *)textBuf, item.specificCantText, 60);
+			assembleTextLine((char *)textBuf, item.cantText, 60);
 
-			item.specificCantSound.readNormal(*chunkStream);
+			item.cantSound.readNormal(*chunkStream);
 		}
 	}
 }
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index c292d11d547..c623a5c4e5b 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -111,10 +111,10 @@ struct INV : public EngineData {
 		Common::Rect sourceRect;
 		Common::Rect highlightedSourceRect;
 
-		Common::String specificCantText;
-		Common::String generalCantText;
-		SoundDescription specificCantSound;
-		SoundDescription generalCantSound;
+		Common::String cantText;
+		Common::String cantTextNotHolding; // nancy2 only
+		SoundDescription cantSound;
+		SoundDescription cantSoundNotHolding; // nancy2 only
 	};
 
 	INV(Common::SeekableReadStream *chunkStream);
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 53683a1d27c..6a25dd4dcc8 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -376,7 +376,7 @@ void Scene::installInventorySoundOverride(byte command, const SoundDescription &
 	}
 }
 
-void Scene::playItemCantSound(int16 itemID) {
+void Scene::playItemCantSound(int16 itemID, bool notHoldingSound) {
 	if (ConfMan.getBool("subtitles") && g_nancy->getGameType() >= kGameTypeNancy2) {
 		_textbox.clear();
 	}
@@ -415,13 +415,13 @@ void Scene::playItemCantSound(int16 itemID) {
 				// Play the default "I can't" sound
 				const INV::ItemDescription item = inventoryData->itemDescriptions[itemID];
 
-				if (item.generalCantSound.name.size()) {
+				if (notHoldingSound && item.cantSoundNotHolding.name.size()) {
 					// This field only exists in nancy2
-					g_nancy->_sound->loadSound(item.generalCantSound);
-					g_nancy->_sound->playSound(item.generalCantSound);
+					g_nancy->_sound->loadSound(item.cantSoundNotHolding);
+					g_nancy->_sound->playSound(item.cantSoundNotHolding);
 
 					if (ConfMan.getBool("subtitles")) {
-						_textbox.addTextLine(item.generalCantText, inventoryData->captionAutoClearTime);
+						_textbox.addTextLine(item.cantTextNotHolding, inventoryData->captionAutoClearTime);
 					}
 				} else if (inventoryData->cantSound.name.size()) {
 					g_nancy->_sound->loadSound(inventoryData->cantSound);
@@ -440,13 +440,13 @@ void Scene::playItemCantSound(int16 itemID) {
 		// No override installed
 		const INV::ItemDescription item = inventoryData->itemDescriptions[itemID];
 
-		if (item.specificCantSound.name.size()) {
+		if (item.cantSound.name.size()) {
 			// The inventory data contains a custom "can't" sound for this item
-			g_nancy->_sound->loadSound(item.specificCantSound);
-			g_nancy->_sound->playSound(item.specificCantSound);
+			g_nancy->_sound->loadSound(item.cantSound);
+			g_nancy->_sound->playSound(item.cantSound);
 
 			if (ConfMan.getBool("subtitles")) {
-				_textbox.addTextLine(item.specificCantText, inventoryData->captionAutoClearTime);
+				_textbox.addTextLine(item.cantText, inventoryData->captionAutoClearTime);
 			}
 		} else if (inventoryData->cantSound.name.size()) {
 			// No custom sound, play default "can't" inside inventory data. Should (?) be unreachable
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index 42698b7ebcd..c3a2365b162 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -135,7 +135,7 @@ public:
 	void setItemDisabledState(int16 id, byte state) { _flags.disabledItems[id] = state; }
 
 	void installInventorySoundOverride(byte command, const SoundDescription &sound, const Common::String &caption, uint16 itemID);
-	void playItemCantSound(int16 itemID = -1);
+	void playItemCantSound(int16 itemID = -1, bool notHoldingSound = false);
 
 	void setEventFlag(int16 label, byte flag);
 	void setEventFlag(FlagDescription eventFlag);




More information about the Scummvm-git-logs mailing list