[Scummvm-git-logs] scummvm master -> 0cc45aabf83754db24f46835ecb9997527ac7125

sev- noreply at scummvm.org
Sat Jan 11 13:21:07 UTC 2025


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

Summary:
78859312f8 COMMON: Make Archive listable
3c0a1312a0 COMMON: Allow specifying XMLParser stream name
d31e9e97bb COMMON: Add PS3 and Nintendo Switch Platforms
4ebf8329ee TETRAEDGE: Add detection entries for Android and Nintendo Switch releases
42cfbdac2e TETRAEDGE: Support rippletexture tag
f8be466549 TETRAEDGE: Declare platforms other than OSX
695d99e906 TETRAEDGE: Add Nintendo Switch Lua bind
bd80dd012a TETRAEDGE: Tolerate missing images on non-Amerzone
4e1cb6d624 TETRAEDGE: Extend filters to include PS3-named scripts
17d76a9721 TETRAEDGE: Introduce TetraedgeFSNode to refer inside archives
440cda29a4 TETRAEDGE: Mark Android as using UTF-8
56d21368bd TETRAEDGE: Support animations in animcached directory
c5b04b43a2 TETRAEDGE: workaround invalid XML in Nintendo Switch release
0fd5bc2090 TETRAEDGE: Mark Russian as available on Syberia2
8763d4383b TETRAEDGE: Support accessing files inside Android OBB archive
22666274a4 TETRAEDGE: Skip pausable attribute
d3e1834e96 TETRAEDGE: Fallback to English if selected language is not available
ede5b4b77e TETRAEDGE: Support UTF-8 Switch releases
0cc45aabf8 TETRAEDGE: Declare Polish and Japanese translations


Commit: 78859312f8e2d70002abe1001f10dbe99f6209f0
    https://github.com/scummvm/scummvm/commit/78859312f8e2d70002abe1001f10dbe99f6209f0
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
COMMON: Make Archive listable

This allows efficient listing of files in a subdir even if archive has
a large amount of unrelated files.

Changed paths:
    common/archive.cpp
    common/archive.h
    common/fs.cpp
    common/fs.h


diff --git a/common/archive.cpp b/common/archive.cpp
index 0c74d3b809c..db488ddb50a 100644
--- a/common/archive.cpp
+++ b/common/archive.cpp
@@ -90,7 +90,9 @@ void GenericArchiveMember::listChildren(ArchiveMemberList &childList, const char
 }
 
 bool Archive::isPathDirectory(const Path &path) const {
-	return false;
+	prepareMaps();
+	Common::Path pathNorm = path.normalize();
+	return _directoryMap.contains(pathNorm) || _fileMap.contains(pathNorm);
 }
 
 int Archive::listMatchingMembers(ArchiveMemberList &list, const Path &pattern, bool matchPathComponents) const {
@@ -176,6 +178,60 @@ char Archive::getPathSeparator() const {
 	return '/';
 }
 
+bool Archive::getChildren(const Common::Path &path, Common::Array<Common::String> &list, ListMode mode, bool hidden) const {
+	list.clear();
+	prepareMaps();
+	Common::Path pathNorm = path.normalize();
+	if (!_fileMap.contains(pathNorm) && !_directoryMap.contains(pathNorm))
+		return false;
+	if (mode == kListDirectoriesOnly || mode == kListAll)
+		for (SubfileSet::iterator its = _directoryMap[pathNorm].begin(); its != _directoryMap[pathNorm].end(); its++)
+		  if (hidden || its->_key.firstChar() != '.')
+				list.push_back(its->_key);
+	if (mode == kListFilesOnly || mode == kListAll)
+		for (SubfileSet::iterator its = _fileMap[pathNorm].begin(); its != _fileMap[pathNorm].end(); its++)
+			if (hidden || its->_key.firstChar() != '.')
+				list.push_back(its->_key);
+	return true;
+}
+
+void Archive::prepareMaps() const {
+	if (_mapsAreReady)
+		return;
+
+	/* In order to avoid call-loop we need to set this variable before calling isDirectory on any members as the
+	   default implementation uses maps.
+	 */
+	_mapsAreReady = true;
+
+	ArchiveMemberList list;
+	listMembers(list);
+
+	for (ArchiveMemberList::iterator it = list.begin(); it != list.end(); it++) {
+		Common::Path cur = (*it)->getPathInArchive().normalize();
+		if (!(*it)->isDirectory()) {
+			Common::Path parent = cur.getParent().normalize();
+			Common::String fname = cur.baseName();
+			_fileMap[parent][fname] = true;
+			cur = parent;
+		}
+
+		while (!cur.empty()) {
+			Common::Path parent = cur.getParent().normalize();
+			Common::String dname = cur.baseName();
+			_directoryMap[parent][dname] = true;
+			cur = parent;
+		}
+	}
+
+	for (AllfileMap::iterator itd = _directoryMap.begin();
+	     itd != _directoryMap.end(); itd++) {
+		for (SubfileSet::iterator its = itd->_value.begin(); its != itd->_value.end(); its++) {
+			_fileMap[itd->_key].erase(its->_key);
+		}
+	}
+}
+
 SeekableReadStream *MemcachingCaseInsensitiveArchive::createReadStreamForMember(const Path &path) const {
 	return createReadStreamForMemberImpl(path, false, Common::AltStreamType::Invalid);
 }
@@ -451,6 +507,21 @@ bool SearchSet::isPathDirectory(const Path &path) const {
 	return false;
 }
 
+bool SearchSet::getChildren(const Common::Path &path, Common::Array<Common::String> &list, ListMode mode, bool hidden) const {
+	bool hasAny = false;
+	ArchiveNodeList::const_iterator it = _list.begin();
+	list.clear();
+	for (; it != _list.end(); ++it) {
+		Common::Array<Common::String> tmpList;
+		if (it->_arc->getChildren(path, tmpList, mode, hidden)) {
+			list.push_back(tmpList);
+			hasAny = true;
+		}
+	}
+
+	return hasAny;
+}
+
 int SearchSet::listMatchingMembers(ArchiveMemberList &list, const Path &pattern, bool matchPathComponents) const {
 	int matches = 0;
 
diff --git a/common/archive.h b/common/archive.h
index f46686e545b..c479b10ccf8 100644
--- a/common/archive.h
+++ b/common/archive.h
@@ -140,6 +140,8 @@ private:
  */
 class Archive {
 public:
+	Archive() : _mapsAreReady(false) { }
+
 	virtual ~Archive() { }
 
 	/**
@@ -211,6 +213,22 @@ public:
 	 * Returns the separator used by internal paths in the archive
 	 */
 	virtual char getPathSeparator() const;
+
+	enum ListMode {
+		kListFilesOnly = 1,
+		kListDirectoriesOnly = 2,
+		kListAll = 3
+	};
+
+	virtual bool getChildren(const Common::Path &path, Common::Array<Common::String> &list, ListMode mode = kListDirectoriesOnly, bool hidden = true) const;
+
+private:
+	void prepareMaps() const;
+
+	mutable bool _mapsAreReady;
+	typedef HashMap<String, bool, IgnoreCase_Hash, IgnoreCase_EqualTo> SubfileSet;
+	typedef HashMap<Path, SubfileSet, Path::IgnoreCase_Hash, Path::IgnoreCase_EqualTo> AllfileMap;
+	mutable AllfileMap _directoryMap, _fileMap;
 };
 
 class MemcachingCaseInsensitiveArchive;
@@ -299,6 +317,7 @@ private:
 
 	mutable HashMap<CacheKey, SharedArchiveContents, CacheKey_Hash, CacheKey_EqualTo> _cache;
 	uint32 _maxStronglyCachedSize;
+	char _separator;
 };
 
 /**
@@ -333,6 +352,8 @@ public:
 	SearchSet() : _ignoreClashes(false) { }
 	virtual ~SearchSet() { clear(); }
 
+	char getPathSeparator() const override { return '/'; }
+
 	/**
 	 * Add a new archive to the searchable set.
 	 */
@@ -455,6 +476,8 @@ public:
 	 * in @ref FSDirectory documentation.
 	 */
 	void setIgnoreClashes(bool ignoreClashes) { _ignoreClashes = ignoreClashes; }
+
+	bool getChildren(const Common::Path &path, Common::Array<Common::String> &list, ListMode mode = kListDirectoriesOnly, bool hidden = true) const override;
 };
 
 
diff --git a/common/fs.cpp b/common/fs.cpp
index 5cf79e1c172..c8360bec17a 100644
--- a/common/fs.cpp
+++ b/common/fs.cpp
@@ -450,6 +450,7 @@ void FSDirectory::cacheDirectoryRecursive(FSNode node, int depth, const Path& pr
 				}
 				cacheDirectoryRecursive(*it, depth - 1, _flat ? prefix : name);
 				_subDirCache[name] = *it;
+				_dirMapCache[prefix].push_back(it->getRealName());
 			}
 		} else {
 			if (_fileCache.contains(name)) {
@@ -457,8 +458,10 @@ void FSDirectory::cacheDirectoryRecursive(FSNode node, int depth, const Path& pr
 					warning("FSDirectory::cacheDirectory: name clash when building cache, ignoring file '%s'",
 					        Common::toPrintable(name.toString(Common::Path::kNativeSeparator)).c_str());
 				}
-			} else
+			} else {
 				_fileCache[name] = *it;
+				_fileMapCache[prefix].push_back(it->getRealName());
+			}
 		}
 	}
 
@@ -471,6 +474,38 @@ void FSDirectory::ensureCached() const  {
 	_cached = true;
 }
 
+bool FSDirectory::getChildren(const Common::Path &path, Common::Array<Common::String> &list, ListMode mode, bool hidden) const {
+	if (!_node.isDirectory())
+		return 0;
+
+	// Cache dir data
+	ensureCached();
+
+	Common::Path pathNormalized = path.normalize();
+
+	int matches = 0;
+
+	if (mode == kListDirectoriesOnly || mode == kListAll) {
+		for (Array<String>::const_iterator it = _dirMapCache[pathNormalized].begin(); it != _dirMapCache[pathNormalized].end(); ++it) {
+			if (hidden || it->firstChar() != '.') {
+				list.push_back(*it);
+				matches++;
+			}
+		}
+	}
+
+	if (mode == kListFilesOnly || mode == kListAll) {
+		for (Array<String>::const_iterator it = _fileMapCache[pathNormalized].begin(); it != _fileMapCache[pathNormalized].end(); ++it) {
+			if (hidden || it->firstChar() != '.') {
+				list.push_back(*it);
+				matches++;
+			}
+		}
+	}
+
+	return matches;
+}
+
 int FSDirectory::listMatchingMembers(ArchiveMemberList &list, const Path &pattern, bool matchPathComponents) const {
 	if (!_node.isDirectory())
 		return 0;
diff --git a/common/fs.h b/common/fs.h
index b5c6f8d95a8..70081d8f0f7 100644
--- a/common/fs.h
+++ b/common/fs.h
@@ -351,7 +351,9 @@ class FSDirectory : public Archive {
 	// Caches are case insensitive, clashes are dealt with when creating
 	// Key is stored in lowercase.
 	typedef HashMap<Path, FSNode, Path::IgnoreCaseAndMac_Hash, Path::IgnoreCaseAndMac_EqualTo> NodeCache;
+	typedef HashMap<Path, Array<String>, Path::IgnoreCaseAndMac_Hash, Path::IgnoreCaseAndMac_EqualTo> NodeMapCache;
 	mutable NodeCache	_fileCache, _subDirCache;
+	mutable NodeMapCache	_fileMapCache, _dirMapCache;
 	mutable bool _cached;
 
 	// look for a match
@@ -443,6 +445,8 @@ public:
 	 */
 	SeekableReadStream *createReadStreamForMember(const Path &path) const override;
 
+	bool getChildren(const Common::Path &path, Common::Array<Common::String> &list, ListMode mode = kListDirectoriesOnly, bool hidden = true) const override;
+
 	/**
 	 * Open an alternate stream for a specified file. A full match of relative path and file name is needed
 	 * for success.


Commit: 3c0a1312a0531bab9469f80a76f88fd9e1794891
    https://github.com/scummvm/scummvm/commit/3c0a1312a0531bab9469f80a76f88fd9e1794891
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
COMMON: Allow specifying XMLParser stream name

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


diff --git a/common/formats/xmlparser.cpp b/common/formats/xmlparser.cpp
index 22a25764e39..3cca5a4ef76 100644
--- a/common/formats/xmlparser.cpp
+++ b/common/formats/xmlparser.cpp
@@ -65,9 +65,9 @@ bool XMLParser::loadBuffer(const byte *buffer, uint32 size, DisposeAfterUse::Fla
 	return true;
 }
 
-bool XMLParser::loadStream(SeekableReadStream *stream) {
+bool XMLParser::loadStream(SeekableReadStream *stream, const String &name) {
 	_stream = stream;
-	_fileName = "File Stream";
+	_fileName = name;
 	return _stream != nullptr;
 }
 
diff --git a/common/formats/xmlparser.h b/common/formats/xmlparser.h
index db68719145f..5676b231560 100644
--- a/common/formats/xmlparser.h
+++ b/common/formats/xmlparser.h
@@ -185,7 +185,7 @@ public:
 	 */
 	bool loadBuffer(const byte *buffer, uint32 size, DisposeAfterUse::Flag disposable = DisposeAfterUse::NO);
 
-	bool loadStream(SeekableReadStream *stream);
+	bool loadStream(SeekableReadStream *stream, const String &name = "File Stream");
 
 	void close();
 


Commit: d31e9e97bb2e53972004e892cedc1233bfaf5115
    https://github.com/scummvm/scummvm/commit/d31e9e97bb2e53972004e892cedc1233bfaf5115
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
COMMON: Add PS3 and Nintendo Switch Platforms

Changed paths:
    common/platform.cpp
    common/platform.h


diff --git a/common/platform.cpp b/common/platform.cpp
index 157b05c5835..1741dfba8b0 100644
--- a/common/platform.cpp
+++ b/common/platform.cpp
@@ -54,6 +54,7 @@ const PlatformDescription g_platforms[] = {
 	{ "windows", "win", "win", "Windows", kPlatformWindows },
 	{ "playstation", "psx", "psx", "Sony PlayStation", kPlatformPSX },
 	{ "playstation2", "ps2", "ps2", "Sony PlayStation 2", kPlatformPS2 },
+	{ "playstation3", "ps3", "ps3", "Sony PlayStation 3", kPlatformPS3 },
 	{ "xbox", "xbox", "xbox", "Microsoft Xbox", kPlatformXbox },
 	{ "cdi", "cdi", "cdi", "Philips CD-i", kPlatformCDi },
 	{ "ios", "ios", "ios", "Apple iOS", kPlatformIOS },
@@ -68,6 +69,7 @@ const PlatformDescription g_platforms[] = {
 	{ "shockwave", "shockwave", "shock", "Shockwave", kPlatformShockwave },
 	{ "zx", "zx", "zx", "ZX Spectrum", kPlatformZX },
 	{ "ti994", "ti994", "ti994", "TI-99/4A", kPlatformTI994 },
+	{ "switch", "switch", "switch", "Nintendo Switch", kPlatformNintendoSwitch },
 
 	{ nullptr, nullptr, nullptr, "Default", kPlatformUnknown }
 };
diff --git a/common/platform.h b/common/platform.h
index 54f159f1b7e..7b2b07b2016 100644
--- a/common/platform.h
+++ b/common/platform.h
@@ -67,6 +67,7 @@ enum Platform : int8 {
 	kPlatformWii,
 	kPlatformPSX,
 	kPlatformPS2,
+	kPlatformPS3,
 	kPlatformXbox,
 	kPlatformCDi,
 	kPlatformIOS,
@@ -81,6 +82,7 @@ enum Platform : int8 {
 	kPlatformShockwave,
 	kPlatformZX,
 	kPlatformTI994,
+	kPlatformNintendoSwitch,
 
 	kPlatformUnknown = -1
 };


Commit: 4ebf8329eeb084a500b6b0c265434375902b4658
    https://github.com/scummvm/scummvm/commit/4ebf8329eeb084a500b6b0c265434375902b4658
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Add detection entries for Android and Nintendo Switch releases

Changed paths:
    engines/tetraedge/detection_tables.h


diff --git a/engines/tetraedge/detection_tables.h b/engines/tetraedge/detection_tables.h
index 8442d8b92e3..4464ec7f81e 100644
--- a/engines/tetraedge/detection_tables.h
+++ b/engines/tetraedge/detection_tables.h
@@ -76,6 +76,153 @@ const ADGameDescription GAME_DESCRIPTIONS[] = {
 		GUIO2(GAMEOPTION_CORRECT_MOVIE_ASPECT, GAMEOPTION_RESTORE_SCENES)
 	},
 
+	// Nintendo Switch, from Syberia1-3 cartridge
+	{
+		"syberia",
+	        nullptr,
+		AD_ENTRY1s("InGame.lua", "acaf61504a12aebf3862648e04cf29aa", 3920),
+		Common::UNK_LANG,
+		Common::kPlatformNintendoSwitch,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+	{
+		"syberia2",
+		nullptr,
+		AD_ENTRY2s("Debug.lua", "a2ea493892e96bea64013819195c081e", 7024,
+			   "InGame.lua", "a7df110fe816cb342574150c6f992964", 4654),
+		Common::UNK_LANG,
+		Common::kPlatformNintendoSwitch,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
+	// Android v1.0.5
+	{
+		"syberia",
+		"Extracted",
+		AD_ENTRY1s("InGame.lua", "12ee6a8eade070b905136cd4cdfc3726", 4471),
+		Common::UNK_LANG,
+		Common::kPlatformAndroid,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
+	{
+		"syberia",
+		nullptr,
+		AD_ENTRY1s("main.12.com.microids.syberia.obb", "b82f9295c4bafe4af58450cbacfd261e", 1000659045),
+		Common::UNK_LANG,
+		Common::kPlatformAndroid,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
+	// Buka release v1.0.3
+	{
+		"syberia",
+		"Extracted",
+		AD_ENTRY1s("InGame.lua", "6577b0151ca4532e94a63a91c22a17c1", 2646),
+		Common::UNK_LANG,
+		Common::kPlatformAndroid,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+	{
+		"syberia",
+		nullptr,
+		AD_ENTRY1s("main.2.ru.buka.syberia1.obb", "7af875e74acfceee5d9b78c705da212e", 771058907),
+		Common::UNK_LANG,
+		Common::kPlatformAndroid,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
+	// v1.0.1
+	{
+		"syberia",
+		nullptr,
+		AD_ENTRY1s("main.5.com.microids.syberia.obb", "6a39b40edca885bb9508ec09675c1923", 1389534445),
+		Common::UNK_LANG,
+		Common::kPlatformAndroid,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+	{
+		"syberia",
+		"Extracted",
+		AD_ENTRY1s("InGame.lua", "8698770015e103725db60a65f3e21657", 2478),
+		Common::UNK_LANG,
+		Common::kPlatformAndroid,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
+	{
+		"syberia",
+	        nullptr,
+		AD_ENTRY1s("InGame.data", "5cb78f2c8aac837fe53596ecfe921b38", 2195),
+		Common::UNK_LANG,
+		Common::kPlatformPS3,
+		ADGF_UNSUPPORTED,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
+	// v1.0.2 Buka release
+	{
+		"syberia2",
+		nullptr,
+		AD_ENTRY1s("main.2.ru.buka.syberia2.obb", "e9d8516610d33f375a3f6800232e3224", 1038859725),
+		Common::UNK_LANG,
+		Common::kPlatformAndroid,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+	{
+		"syberia2",
+		"Extracted",
+		AD_ENTRY2s("Debug.lua", "a2ea493892e96bea64013819195c081e", 7024,
+			   "filelist.bin", "eb189789a74286c5023e102ec1c44fd4", 2099822),
+		Common::UNK_LANG,
+		Common::kPlatformAndroid,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
+	// v1.0.1 Android release
+	{
+		"syberia2",
+		nullptr,
+		AD_ENTRY1s("main.4.com.microids.syberia2.obb", "d8aa60562ffad83d3bcaa7b611fc4299", 1473221971),
+		Common::UNK_LANG,
+		Common::kPlatformAndroid,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+	{
+		"syberia2",
+		"Extracted",
+		AD_ENTRY2s("Debug.lua", "a2ea493892e96bea64013819195c081e", 7024,
+			   "filelist.bin", "dc40f150ee291a30e0bc6cd8a0127aab", 2100007),
+		Common::UNK_LANG,
+		Common::kPlatformAndroid,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
+	// v1.0.0 iOS release
+	{
+		"syberia2",
+		nullptr,
+		AD_ENTRY2s("Debug.lua", "a2ea493892e96bea64013819195c081e", 7024,
+			   "Info.plist", nullptr, (uint32_t)-1),
+		Common::UNK_LANG,
+		Common::kPlatformIOS,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
+
 	// GOG release
 	{
 		"syberia2",
@@ -98,6 +245,16 @@ const ADGameDescription GAME_DESCRIPTIONS[] = {
 		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
 	},
 
+	{
+		"syberia2",
+		nullptr,
+		AD_ENTRY1s("Debug.data", "d5cfcba9b725e746df39109e7e1b0564", 7024),
+		Common::UNK_LANG,
+		Common::kPlatformPS3,
+		ADGF_UNSUPPORTED,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
 	AD_TABLE_END_MARKER
 };
 


Commit: 42cfbdac2e4949675bd83dd14a5b1cf07fddd967
    https://github.com/scummvm/scummvm/commit/42cfbdac2e4949675bd83dd14a5b1cf07fddd967
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Support rippletexture tag

Changed paths:
    engines/tetraedge/game/character_settings_xml_parser.cpp
    engines/tetraedge/game/character_settings_xml_parser.h


diff --git a/engines/tetraedge/game/character_settings_xml_parser.cpp b/engines/tetraedge/game/character_settings_xml_parser.cpp
index 059bd659db6..3e96c5bf9cd 100644
--- a/engines/tetraedge/game/character_settings_xml_parser.cpp
+++ b/engines/tetraedge/game/character_settings_xml_parser.cpp
@@ -110,6 +110,11 @@ bool CharacterSettingsXmlParser::parserCallback_position(ParserNode *node) {
 	return true;
 }
 
+bool CharacterSettingsXmlParser::parserCallback_rippleTexture(ParserNode *node) {
+	// Ignored
+	return true;
+}
+
 bool CharacterSettingsXmlParser::parserCallback_face(ParserNode *node) {
 	// Handled in "face" and "eyes" callbacks.
 	return true;
diff --git a/engines/tetraedge/game/character_settings_xml_parser.h b/engines/tetraedge/game/character_settings_xml_parser.h
index 919135a8ea3..6b49b25f0a8 100644
--- a/engines/tetraedge/game/character_settings_xml_parser.h
+++ b/engines/tetraedge/game/character_settings_xml_parser.h
@@ -88,6 +88,9 @@ public:
 				XML_KEY(body)
 					XML_PROP(name, true)
 				KEY_END()
+				XML_KEY(rippleTexture)
+					XML_PROP(path, true)
+				KEY_END()
 			KEY_END()
 		KEY_END()
 	} PARSER_END()
@@ -112,6 +115,7 @@ public:
 	bool parserCallback_mouth(ParserNode *node);
 	bool parserCallback_body(ParserNode *node);
 	bool parserCallback_invertNormals(ParserNode *node);
+	bool parserCallback_rippleTexture(ParserNode *node);
 
 	bool textCallback(const Common::String &val) override;
 	bool handleUnknownKey(ParserNode *node) override;


Commit: f8be466549a18ee01660f65a9a67899ec24dca07
    https://github.com/scummvm/scummvm/commit/f8be466549a18ee01660f65a9a67899ec24dca07
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Declare platforms other than OSX

Changed paths:
    engines/tetraedge/game/application.cpp


diff --git a/engines/tetraedge/game/application.cpp b/engines/tetraedge/game/application.cpp
index 58ab79e1953..523277fa579 100644
--- a/engines/tetraedge/game/application.cpp
+++ b/engines/tetraedge/game/application.cpp
@@ -60,12 +60,35 @@ _permanentHelp(true), _musicOn(true) {
 
 	TeCore *core = g_engine->getCore();
 	core->_coreNotReady = true;
-	core->fileFlagSystemSetFlag("platform", "MacOSX");
+	const char *platform = "";
+	switch (g_engine->getGamePlatform()) {
+	case Common::Platform::kPlatformAndroid:
+		platform = "Android";
+		core->fileFlagSystemSetFlag("pad", "padDisabled");
+		break;
+	case Common::Platform::kPlatformMacintosh:
+		platform = "MacOSX";
+		break;
+	case Common::Platform::kPlatformIOS:
+		platform = "iPhone";
+		break;
+	case Common::Platform::kPlatformNintendoSwitch:
+		platform = "NX";
+		core->fileFlagSystemSetFlag("pad", "padDisabled");
+		break;
+	case Common::Platform::kPlatformPS3:
+		platform = "PS3";
+		break;
+	default:
+		error("Unsupported platform");
+	}
+	core->fileFlagSystemSetFlag("platform", platform);
 	//
 	// WORKAROUND: Syberia 2 A5_ValDomaine/54000/Logic54000.lua
 	// checks a typo of this flag..
 	//
-	core->fileFlagSystemSetFlag("plateform", "MacOSX");
+	core->fileFlagSystemSetFlag("plateform", platform);
+
 	core->fileFlagSystemSetFlag("part", "Full");
 	if (g_engine->isGameDemo())
 		core->fileFlagSystemSetFlag("distributor", "Freemium");


Commit: 695d99e9068f7fed824abf474c33161b5f3c64fa
    https://github.com/scummvm/scummvm/commit/695d99e9068f7fed824abf474c33161b5f3c64fa
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Add Nintendo Switch Lua bind

Changed paths:
    engines/tetraedge/game/lua_binds.cpp


diff --git a/engines/tetraedge/game/lua_binds.cpp b/engines/tetraedge/game/lua_binds.cpp
index f2516f0ef76..b3c3fa0952f 100644
--- a/engines/tetraedge/game/lua_binds.cpp
+++ b/engines/tetraedge/game/lua_binds.cpp
@@ -2651,6 +2651,12 @@ static int tolua_ExportedFunctions_PlayVerticalScrolling00(lua_State *L) {
 	error("#ferror in function 'SetObjectMoveTime': %d %d %s", err.index, err.array, err.type);
 }
 
+static int tolua_ExportedFunctions_GetNXPadType(lua_State *L) {
+	// TODO: Actually implement this
+	tolua_pushstring(L, "Handheld");
+	return 1;
+}
+
 // Not your imagination, the implementation of these two is quite different to the others.
 static int tolua_GetParticleIndex(lua_State *L) {
 	Common::String s1(tolua_tostring(L, 1, nullptr));
@@ -2849,6 +2855,7 @@ static void LuaOpenBinds_Syberia(lua_State *L) {
 	tolua_function(L, "PlayVerticalScrolling", tolua_ExportedFunctions_PlayVerticalScrolling00);
 	tolua_function(L, "GetParticleIndex", tolua_GetParticleIndex);
 	tolua_function(L, "EnableParticle", tolua_EnableParticle);
+	tolua_function(L, "GetNXPadType", tolua_ExportedFunctions_GetNXPadType);
 
 	tolua_endmodule(L);
 }


Commit: bd80dd012a30193941c1b30531995d5e77782ccd
    https://github.com/scummvm/scummvm/commit/bd80dd012a30193941c1b30531995d5e77782ccd
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Tolerate missing images on non-Amerzone

Changed paths:
    engines/tetraedge/game/notifier.cpp


diff --git a/engines/tetraedge/game/notifier.cpp b/engines/tetraedge/game/notifier.cpp
index 8b9dcb7ff71..a0a990f7e33 100644
--- a/engines/tetraedge/game/notifier.cpp
+++ b/engines/tetraedge/game/notifier.cpp
@@ -77,8 +77,8 @@ void Notifier::launchNextnotifier() {
 	colorAnim->_callbackObj = _gui.layoutChecked("sprite");
 	colorAnim->play();
 
-	if (!g_engine->gameIsAmerzone()) {
-		colorAnim = _gui.colorLinearAnimation("fadeInImage");
+	colorAnim = g_engine->gameIsAmerzone() ? nullptr : _gui.colorLinearAnimation("fadeInImage");
+	if (colorAnim) {
 		colorAnim->_callbackObj = _gui.layoutChecked("image");
 		colorAnim->play();
 	}
@@ -108,8 +108,8 @@ bool Notifier::onFadeInFinished() {
 	colorAnim->_callbackObj = _gui.layout("sprite");
 	colorAnim->play();
 
-	if (!g_engine->gameIsAmerzone()) {
-		colorAnim = _gui.colorLinearAnimation("visibleImage");
+	colorAnim = g_engine->gameIsAmerzone() ? nullptr : _gui.colorLinearAnimation("visibleImage");
+	if (colorAnim) {
 		colorAnim->_callbackObj = _gui.layout("image");
 		colorAnim->play();
 	}
@@ -129,8 +129,8 @@ bool Notifier::onVisibleFinished() {
 	colorAnim->_callbackObj = _gui.layout("sprite");
 	colorAnim->play();
 
-	if (!g_engine->gameIsAmerzone()) {
-		colorAnim = _gui.colorLinearAnimation("fadeOutImage");
+	colorAnim = g_engine->gameIsAmerzone() ? nullptr : _gui.colorLinearAnimation("fadeOutImage");
+	if (colorAnim) {
 		colorAnim->_callbackObj = _gui.layout("image");
 		colorAnim->play();
 	}


Commit: 4e1cb6d62491728d22e1630b4438af0fed29504f
    https://github.com/scummvm/scummvm/commit/4e1cb6d62491728d22e1630b4438af0fed29504f
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Extend filters to include PS3-named scripts

Changed paths:
    engines/tetraedge/game/application.cpp
    engines/tetraedge/game/question2.cpp
    engines/tetraedge/te/te_lua_thread.cpp


diff --git a/engines/tetraedge/game/application.cpp b/engines/tetraedge/game/application.cpp
index 523277fa579..05bd0c372e4 100644
--- a/engines/tetraedge/game/application.cpp
+++ b/engines/tetraedge/game/application.cpp
@@ -400,7 +400,7 @@ bool Application::run() {
 		if (_finishedGame) {
 			game->leave(false);
 			_mainMenu.enter();
-			if (Common::File::exists("finalURL.lua")) {
+			if (Common::File::exists("finalURL.lua") || Common::File::exists("finalURL.data")) {
 				TeLuaGUI finalGui;
 				finalGui.load("finalURL.lua");
 				/*TeVariant finalVal =*/ finalGui.value("finalURL");
diff --git a/engines/tetraedge/game/question2.cpp b/engines/tetraedge/game/question2.cpp
index c12e62676e1..cb8d139d285 100644
--- a/engines/tetraedge/game/question2.cpp
+++ b/engines/tetraedge/game/question2.cpp
@@ -108,7 +108,7 @@ void Question2::pushAnswer(const Common::String &name, const Common::String &loc
 	float xpos;
 	blayout->setSizeType(RELATIVE_TO_PARENT);
 	blayout->setPositionType(RELATIVE_TO_PARENT);
-	if (!path.baseName().contains("Cal_FIN.lua")) {
+	if (!path.baseName().contains("Cal_FIN.lua") && !path.baseName().contains("Cal_FIN.data")) {
 		blayout->setSize(TeVector3f32(0.45f, 0.065f, 1.0f));
 		xpos = 0.3f;
 	} else {
diff --git a/engines/tetraedge/te/te_lua_thread.cpp b/engines/tetraedge/te/te_lua_thread.cpp
index b2f7ebf9b5c..b0411d54bf8 100644
--- a/engines/tetraedge/te/te_lua_thread.cpp
+++ b/engines/tetraedge/te/te_lua_thread.cpp
@@ -161,9 +161,15 @@ void TeLuaThread::execute(const Common::String &fname, const TeVariant &p1, cons
 	}
 }
 
-void TeLuaThread::applyScriptWorkarounds(char *buf, const Common::String &fileName) {
+void TeLuaThread::applyScriptWorkarounds(char *buf, const Common::String &fileNameIn) {
 	char *fixline;
 
+	Common::String fileName(fileNameIn);
+
+	if (fileName.hasSuffix(".data")) {
+		fileName = fileName.substr(0, fileName.size() - 5) + ".lua";
+	}
+
 	//
 	// WORKAROUND: Some script files have rogue ";" lines in them with nothing
 	// else, and ScummVM common lua version doesn't like them. Clean those up.


Commit: 17d76a9721fe0a4b067d751ea3799aef0b402d67
    https://github.com/scummvm/scummvm/commit/17d76a9721fe0a4b067d751ea3799aef0b402d67
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Introduce TetraedgeFSNode to refer inside archives

Changed paths:
    engines/tetraedge/tetraedge.cpp
    engines/tetraedge/tetraedge.h


diff --git a/engines/tetraedge/tetraedge.cpp b/engines/tetraedge/tetraedge.cpp
index 8545cc69457..4851d470f61 100644
--- a/engines/tetraedge/tetraedge.cpp
+++ b/engines/tetraedge/tetraedge.cpp
@@ -361,5 +361,121 @@ void TetraedgeEngine::getSavegameThumbnail(Graphics::Surface &thumb) {
 	g_engine->getApplication()->getSavegameThumbnail(thumb);
 }
 
+bool TetraedgeFSNode::getChildren(TetraedgeFSList &fslist, Common::FSNode::ListMode mode, bool hidden) const {
+	if (!_archive)
+		return false;
+
+	Common::Array<Common::String> tmpsublist;
+	if(!_archive->getChildren(_archivePath, tmpsublist, (Common::Archive::ListMode)  mode, hidden))
+		return false;
+	fslist.clear();
+	for(Common::Array<Common::String>::iterator it = tmpsublist.begin(); it != tmpsublist.end(); it++) {
+		fslist.push_back(TetraedgeFSNode(_archive, _archivePath.join(*it)));
+	}
+	return true;
+}
+
+class SubPathArchive : public Common::Archive {
+public:
+	SubPathArchive(Common::Archive *archive, const Common::Path &prefix) : _archive(archive), _prefix(prefix) {}
+
+	bool hasFile(const Common::Path &path) const override {
+		return _archive && _archive->hasFile(_prefix.join(path));
+	}
+
+	bool isPathDirectory(const Common::Path &path) const override {
+		return _archive && _archive->isPathDirectory(_prefix.join(path));
+	}
+
+	bool getChildren(const Common::Path &path, Common::Array<Common::String> &list, ListMode mode, bool hidden) const override {
+		return _archive && getChildren(_prefix.join(path), list, mode, hidden);
+	}
+
+	int listMembers(Common::ArchiveMemberList &list) const override {
+		Common::ArchiveMemberList tmpList;
+		if (!_archive)
+			return 0;
+		_archive->listMembers(tmpList);
+		Common::String prefixStr = _prefix.toString();
+		if (!prefixStr.hasSuffix("/"))
+			prefixStr += "/";
+		int count = 0;
+		for (Common::ArchiveMemberList::iterator it = tmpList.begin(); it != tmpList.end(); it++) {
+			if ((*it)->getName().hasPrefix(prefixStr)) {
+				list.push_back(*it);
+				count++;
+			}
+		}
+		return count;
+	}
+
+	const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override {
+		return _archive ? _archive->getMember(_prefix.join(path)) : nullptr;
+	}
+
+	Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override {
+		return _archive ? _archive->createReadStreamForMember(_prefix.join(path)) : nullptr;
+	}
+
+	char getPathSeparator() const override {
+		return _archive ? _archive->getPathSeparator() : '/';
+	}
+
+private:
+	Common::Archive *_archive;
+	Common::Path _prefix;
+};
+
+void TetraedgeFSNode::maybeAddToSearchMan() const {
+	const Common::String path = getPath().toString(Common::Path::kNativeSeparator);
+	if (SearchMan.hasArchive(path))
+		return;
+	if (!_archivePath.empty())
+		SearchMan.add(path, new SubPathArchive(_archive, _archivePath));
+}
+
+Common::SeekableReadStream *TetraedgeFSNode::createReadStream() const {
+	return _archive ? _archive->createReadStreamForMember(_archivePath) : nullptr;
+}
+
+bool TetraedgeFSNode::isReadable() const {
+	return _archive && _archive->hasFile(_archivePath);
+}
+
+bool TetraedgeFSNode::isDirectory() const {
+	return _archive && _archive->isPathDirectory(_archivePath);
+}
+
+Common::Path TetraedgeFSNode::getPath() const {
+	return _archivePath;
+}
+
+Common::String TetraedgeFSNode::toString() const {
+	return _archivePath.toString(Common::Path::kNativeSeparator);
+}
+
+TetraedgeFSNode TetraedgeFSNode::getChild(const Common::Path &path) const {
+	return TetraedgeFSNode(_archive, _archivePath.join(path));
+}
+
+bool TetraedgeFSNode::exists() const {
+	return isDirectory() || isReadable();
+}
+
+bool TetraedgeFSNode::loadXML(Common::XMLParser &parser) const {
+	return parser.loadStream(createReadStream(), _archivePath.toString());
+}
+
+Common::String TetraedgeFSNode::getName() const {
+	return _archivePath.getLastComponent().toString();
+}
+
+bool TetraedgeFSNode::operator<(const TetraedgeFSNode& node) const {
+	return getPath() < node.getPath();
+}
+
+int TetraedgeFSNode::getDepth() const {
+	return _archivePath.splitComponents().size();
+}
 
 } // namespace Tetraedge
diff --git a/engines/tetraedge/tetraedge.h b/engines/tetraedge/tetraedge.h
index deab9e9e9c4..0ee7299f4c3 100644
--- a/engines/tetraedge/tetraedge.h
+++ b/engines/tetraedge/tetraedge.h
@@ -26,11 +26,13 @@
 #include "common/system.h"
 #include "common/error.h"
 #include "common/events.h"
+#include "common/file.h"
 #include "common/fs.h"
 #include "common/hash-str.h"
 #include "common/random.h"
 #include "common/serializer.h"
 #include "common/util.h"
+#include "common/formats/xmlparser.h"
 #include "engines/engine.h"
 #include "engines/savestate.h"
 #include "graphics/screen.h"
@@ -50,6 +52,33 @@ class TeRenderer;
 class TeResourceManager;
 class TeInputMgr;
 
+class TetraedgeFSNode;
+
+class TetraedgeFSList : public Common::Array<TetraedgeFSNode> {};
+class TetraedgeFSNode {
+public:
+	TetraedgeFSNode() : _archive(nullptr) {}
+	explicit TetraedgeFSNode(Common::Archive *archive) : _archive(archive) {}
+	TetraedgeFSNode(Common::Archive *archive, const Common::Path &archivePath) : _archive(archive), _archivePath(archivePath) {}
+
+	Common::SeekableReadStream *createReadStream() const;
+	bool isReadable() const;
+	bool isDirectory() const;
+	Common::Path getPath() const;
+	Common::String toString() const;
+	int getDepth() const;
+	bool exists() const;
+	bool loadXML(Common::XMLParser &parser) const;
+	Common::String getName() const;
+	TetraedgeFSNode getChild(const Common::Path &path) const;
+	bool getChildren(TetraedgeFSList &fslist, Common::FSNode::ListMode mode = Common::FSNode::kListDirectoriesOnly, bool hidden = true) const;
+	bool operator<(const TetraedgeFSNode& node) const;
+	void maybeAddToSearchMan() const;
+private:
+	Common::Archive *_archive;
+	Common::Path _archivePath;
+};
+
 class TetraedgeEngine : public Engine {
 public:
 	enum TetraedgeGameType {
@@ -70,6 +99,7 @@ private:
 	TeResourceManager *_resourceManager;
 	TeInputMgr *_inputMgr;
 	enum TetraedgeGameType _gameType;
+	Common::Array<Common::Archive *> _rootArchives;
 
 protected:
 	// Engine APIs
@@ -79,6 +109,8 @@ public:
 	TetraedgeEngine(OSystem *syst, const ADGameDescription *gameDesc);
 	~TetraedgeEngine() override;
 
+	const Common::Array<Common::Archive *>& getRootArchives() const { return _rootArchives; }
+
 	uint32 getFeatures() const;
 
 	void closeGameDialogs();


Commit: 440cda29a4c6b0878a7af19713313cfae5f3a6b2
    https://github.com/scummvm/scummvm/commit/440cda29a4c6b0878a7af19713313cfae5f3a6b2
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Mark Android as using UTF-8

Changed paths:
    engines/tetraedge/te/te_i_font.cpp


diff --git a/engines/tetraedge/te/te_i_font.cpp b/engines/tetraedge/te/te_i_font.cpp
index b0f30c93c16..65fb6034cbd 100644
--- a/engines/tetraedge/te/te_i_font.cpp
+++ b/engines/tetraedge/te/te_i_font.cpp
@@ -51,10 +51,12 @@ TeIFont::GlyphData TeIFont::glyph(uint pxSize, uint charcode) {
 
 Common::CodePage TeIFont::codePage() const {
 	Common::String lang = g_engine->getCore()->language();
-	if (lang == "he")
-		return Common::kWindows1255;
 	if (lang == "ru")
 		return Common::kISO8859_5;
+	if (g_engine->getGamePlatform() == Common::Platform::kPlatformAndroid)
+		return Common::CodePage::kUtf8;
+	if (lang == "he")
+		return Common::kWindows1255;
 	return Common::kLatin1;
 }
 


Commit: 56d21368bd882335454fe21dd998dc58c75dbcd2
    https://github.com/scummvm/scummvm/commit/56d21368bd882335454fe21dd998dc58c75dbcd2
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Support animations in animcached directory

Changed paths:
    engines/tetraedge/game/in_game_scene.cpp
    engines/tetraedge/te/te_images_sequence.cpp


diff --git a/engines/tetraedge/game/in_game_scene.cpp b/engines/tetraedge/game/in_game_scene.cpp
index 9b5ea0c4aa2..a5aee934a3b 100644
--- a/engines/tetraedge/game/in_game_scene.cpp
+++ b/engines/tetraedge/game/in_game_scene.cpp
@@ -115,7 +115,8 @@ bool InGameScene::addMarker(const Common::String &markerName, const Common::Path
 		// Note: game checks paths here but seems to just use the original?
 		markerSprite->setName(markerName);
 		markerSprite->setAnchor(TeVector3f32(anchorX, anchorY, 0.0f));
-		markerSprite->load(imgPath);
+		if (!markerSprite->load(imgPath) && imgPath.baseName().hasSuffix(".anim"))
+			markerSprite->load(imgPath.append("cached"));
 		markerSprite->setSizeType(TeILayout::RELATIVE_TO_PARENT);
 		markerSprite->setPositionType(TeILayout::RELATIVE_TO_PARENT);
 		TeVector3f32 newPos;
diff --git a/engines/tetraedge/te/te_images_sequence.cpp b/engines/tetraedge/te/te_images_sequence.cpp
index 0ec945e31b2..fb9dfddd047 100644
--- a/engines/tetraedge/te/te_images_sequence.cpp
+++ b/engines/tetraedge/te/te_images_sequence.cpp
@@ -42,7 +42,7 @@ TeImagesSequence::~TeImagesSequence() {
 
 /*static*/
 bool TeImagesSequence::matchExtension(const Common::String &extn) {
-	return extn == "anim";
+	return extn == "anim" || extn == "animcached";
 }
 
 static bool compareNodes(const Common::FSNode &left, const Common::FSNode &right) {


Commit: c5b04b43a2fa2d2485019682c4be09dd12b5182b
    https://github.com/scummvm/scummvm/commit/c5b04b43a2fa2d2485019682c4be09dd12b5182b
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: workaround invalid XML in Nintendo Switch release

Changed paths:
    engines/tetraedge/te/te_text_layout.cpp


diff --git a/engines/tetraedge/te/te_text_layout.cpp b/engines/tetraedge/te/te_text_layout.cpp
index 7ce5889110c..dba780a098c 100644
--- a/engines/tetraedge/te/te_text_layout.cpp
+++ b/engines/tetraedge/te/te_text_layout.cpp
@@ -87,7 +87,13 @@ void TeTextLayout::setText(const Common::String &val) {
 			break;
 		const Common::String *replacement = loc->text(replaced.substr(bstart + 2, bend - bstart - 2));
 		if (replacement) {
-			replaced.replace(bstart, bend - bstart + 1, *replacement);
+			/* Workaround: Syberia 2 on Switch has strings
+			   <Forward> and <Backwards> via replacement
+			   in xml embed in lua. Escape < and >. */
+			Common::String escaped = *replacement;
+			Common::replace(escaped, "<", "<");
+			Common::replace(escaped, ">", ">");
+			replaced.replace(bstart, bend - bstart + 1, escaped);
 		}
 		bstart = replaced.find("$(", bstart + 1);
 	}


Commit: 0fd5bc2090ac3bc03fff961ae8c5cef18ff406b0
    https://github.com/scummvm/scummvm/commit/0fd5bc2090ac3bc03fff961ae8c5cef18ff406b0
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Mark Russian as available on Syberia2

Changed paths:
    engines/tetraedge/detection.cpp


diff --git a/engines/tetraedge/detection.cpp b/engines/tetraedge/detection.cpp
index d47b9182d0b..d5edb2a4c3c 100644
--- a/engines/tetraedge/detection.cpp
+++ b/engines/tetraedge/detection.cpp
@@ -58,9 +58,6 @@ DetectedGame TetraedgeMetaEngineDetection::toDetectedGame(const ADDetectedGame &
 	// game language. All games support multiple languages.  Only Syberia 1
 	// supports RU.
 	for (const Common::Language *language = getGameLanguages(); *language != Common::UNK_LANG; language++) {
-		// "ru" only present on syberia 1
-		if (game.gameId != "syberia" && *language == Common::RU_RUS)
-			continue;
 		game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(*language));
 	}
 


Commit: 8763d4383b8f3dc2d23ddf69f819fce89117cfbd
    https://github.com/scummvm/scummvm/commit/8763d4383b8f3dc2d23ddf69f819fce89117cfbd
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Support accessing files inside Android OBB archive

Changed paths:
  A engines/tetraedge/obb_archive.cpp
  A engines/tetraedge/obb_archive.h
    engines/tetraedge/game/application.cpp
    engines/tetraedge/game/billboard.cpp
    engines/tetraedge/game/document.cpp
    engines/tetraedge/game/documents_browser.cpp
    engines/tetraedge/game/in_game_scene.cpp
    engines/tetraedge/game/in_game_scene.h
    engines/tetraedge/game/loc_file.cpp
    engines/tetraedge/game/loc_file.h
    engines/tetraedge/game/splash_screens.cpp
    engines/tetraedge/game/syberia_game.cpp
    engines/tetraedge/module.mk
    engines/tetraedge/te/te_3d_texture.cpp
    engines/tetraedge/te/te_3d_texture.h
    engines/tetraedge/te/te_bezier_curve.cpp
    engines/tetraedge/te/te_bezier_curve.h
    engines/tetraedge/te/te_camera.cpp
    engines/tetraedge/te/te_core.cpp
    engines/tetraedge/te/te_core.h
    engines/tetraedge/te/te_font2.cpp
    engines/tetraedge/te/te_font2.h
    engines/tetraedge/te/te_font3.cpp
    engines/tetraedge/te/te_font3.h
    engines/tetraedge/te/te_free_move_zone.cpp
    engines/tetraedge/te/te_i_codec.h
    engines/tetraedge/te/te_image.cpp
    engines/tetraedge/te/te_image.h
    engines/tetraedge/te/te_images_sequence.cpp
    engines/tetraedge/te/te_images_sequence.h
    engines/tetraedge/te/te_interpolation.cpp
    engines/tetraedge/te/te_interpolation.h
    engines/tetraedge/te/te_lua_gui.cpp
    engines/tetraedge/te/te_lua_gui.h
    engines/tetraedge/te/te_lua_script.cpp
    engines/tetraedge/te/te_lua_script.h
    engines/tetraedge/te/te_lua_thread.cpp
    engines/tetraedge/te/te_lua_thread.h
    engines/tetraedge/te/te_material.cpp
    engines/tetraedge/te/te_model_animation.cpp
    engines/tetraedge/te/te_music.cpp
    engines/tetraedge/te/te_music.h
    engines/tetraedge/te/te_png.cpp
    engines/tetraedge/te/te_png.h
    engines/tetraedge/te/te_resource_manager.h
    engines/tetraedge/te/te_scene.h
    engines/tetraedge/te/te_scene_warp.cpp
    engines/tetraedge/te/te_scummvm_codec.cpp
    engines/tetraedge/te/te_scummvm_codec.h
    engines/tetraedge/te/te_sound_manager.cpp
    engines/tetraedge/te/te_sprite_layout.cpp
    engines/tetraedge/te/te_text_layout.cpp
    engines/tetraedge/te/te_theora.cpp
    engines/tetraedge/te/te_theora.h
    engines/tetraedge/te/te_tiled_surface.cpp
    engines/tetraedge/te/te_tiled_surface.h
    engines/tetraedge/te/te_warp.cpp
    engines/tetraedge/te/te_warp.h
    engines/tetraedge/te/te_warp_bloc.cpp
    engines/tetraedge/te/te_warp_bloc.h
    engines/tetraedge/tetraedge.cpp


diff --git a/engines/tetraedge/game/application.cpp b/engines/tetraedge/game/application.cpp
index 05bd0c372e4..9dcc69a8a4b 100644
--- a/engines/tetraedge/game/application.cpp
+++ b/engines/tetraedge/game/application.cpp
@@ -191,10 +191,10 @@ void Application::create() {
 
 	// Try alternate langs..
 	int i = 0;
-	Common::Path textFilePath;
+	TetraedgeFSNode textFileNode;
 	while (i < ARRAYSIZE(allLangs)) {
-		textFilePath = core->findFile(textsPath.join(core->language() + ".xml"));
-		if (Common::File::exists(textFilePath))
+		textFileNode = core->findFile(textsPath.join(core->language() + ".xml"));
+		if (textFileNode.exists())
 			break;
 		core->language(allLangs[i]);
 		i++;
@@ -203,7 +203,7 @@ void Application::create() {
 		error("Couldn't find texts/[lang].xml for any language.");
 	}
 
-	_loc.load(textFilePath);
+	_loc.load(textFileNode);
 	core->addLoc(&_loc);
 
 	if (!g_engine->gameIsAmerzone()) {
diff --git a/engines/tetraedge/game/billboard.cpp b/engines/tetraedge/game/billboard.cpp
index bf76f704bcc..1676baa0b63 100644
--- a/engines/tetraedge/game/billboard.cpp
+++ b/engines/tetraedge/game/billboard.cpp
@@ -37,8 +37,8 @@ bool Billboard::load(const Common::Path &path) {
 	TeIntrusivePtr<Te3DTexture> texture = Te3DTexture::makeInstance();
 	SyberiaGame *game = dynamic_cast<SyberiaGame *>(g_engine->getGame());
 	TeCore *core = g_engine->getCore();
-	Common::Path texPath = core->findFile(game->sceneZonePath().join(path));
-	texture->load(texPath);
+	TetraedgeFSNode texnode = core->findFile(game->sceneZonePath().join(path));
+	texture->load(texnode);
 	_model->setName(path.toString('/'));
 	Common::Array<TeVector3f32> quad;
 	quad.resize(4);
diff --git a/engines/tetraedge/game/document.cpp b/engines/tetraedge/game/document.cpp
index e7e62fbd060..a0457cb393c 100644
--- a/engines/tetraedge/game/document.cpp
+++ b/engines/tetraedge/game/document.cpp
@@ -38,7 +38,7 @@ void Document::load(const Common::String &name) {
 	addChild(_gui.layoutChecked("object"));
 	setName(name);
 	const Common::Path sprPath = spritePath();
-	_gui.spriteLayoutChecked("upLayout")->load(g_engine->getCore()->findFile(sprPath));
+	_gui.spriteLayoutChecked("upLayout")->load(sprPath);
 	_gui.buttonLayoutChecked("object")->onMouseClickValidated().add(this, &Document::onButtonDown);
 	TeITextLayout *txtLayout = _gui.textLayout("text");
 	if (!txtLayout)
diff --git a/engines/tetraedge/game/documents_browser.cpp b/engines/tetraedge/game/documents_browser.cpp
index 2d17a420b3c..91a9f1ce139 100644
--- a/engines/tetraedge/game/documents_browser.cpp
+++ b/engines/tetraedge/game/documents_browser.cpp
@@ -123,16 +123,15 @@ void DocumentsBrowser::loadZoomed() {
 }
 
 void DocumentsBrowser::loadXMLFile(const Common::Path &path) {
-	Common::Path xmlPath = g_engine->getCore()->findFile(path);
-	Common::File xmlfile;
-	xmlfile.open(xmlPath);
-	int64 fileLen = xmlfile.size();
+	TetraedgeFSNode node = g_engine->getCore()->findFile(path);
+	Common::ScopedPtr<Common::SeekableReadStream> xmlfile(node.createReadStream());
+	int64 fileLen = xmlfile->size();
 	char *buf = new char[fileLen + 1];
 	buf[fileLen] = '\0';
-	xmlfile.read(buf, fileLen);
+	xmlfile->read(buf, fileLen);
 	const Common::String xmlContents = Common::String::format("<?xml version=\"1.0\" encoding=\"UTF-8\"?><document>%s</document>", buf);
 	delete [] buf;
-	xmlfile.close();
+	xmlfile.reset();
 
 	DocumentsBrowserXmlParser parser;
 	if (!parser.loadBuffer((const byte *)xmlContents.c_str(), xmlContents.size()))
@@ -306,10 +305,12 @@ void DocumentsBrowser::showDocument(const Common::String &docName, int startPage
 	TeCore *core = g_engine->getCore();
 	const char *pathPattern = g_engine->gameIsAmerzone() ? "DocumentsBrowser/Documents/%s_zoomed_%d" : "DocumentsBrowser/Documents/Documents/%s_zoomed_%d";
 	const Common::Path docPathBase(Common::String::format(pathPattern, docName.c_str(), (int)startPage));
-	Common::Path docPath = core->findFile(docPathBase.append(".png"));
-	if (!Common::File::exists(docPath)) {
-		docPath = core->findFile(docPathBase.append(".jpg"));
-		if (!Common::File::exists(docPath)) {
+	Common::Path docPath = docPathBase.append(".png");
+	TetraedgeFSNode docNode = core->findFile(docPath);
+	if (!docNode.exists()) {
+		docPath = docPathBase.append(".jpg");
+		docNode = core->findFile(docPath);
+		if (!docNode.exists()) {
 			// Probably the end of the doc
 			if (startPage == 0)
 				warning("Can't find first page of doc named %s", docName.c_str());
@@ -323,9 +324,9 @@ void DocumentsBrowser::showDocument(const Common::String &docName, int startPage
 	sprite->load(docPath);
 	TeVector2s32 spriteSize = sprite->_tiledSurfacePtr->tiledTexture()->totalSize();
 
-	Common::Path luaPath = core->findFile(docPathBase.append(".lua"));
-	if (Common::File::exists(luaPath)) {
-		_zoomedDocGui.load(luaPath);
+	TetraedgeFSNode luaNode = core->findFile(docPathBase.append(".lua"));
+	if (luaNode.exists()) {
+		_zoomedDocGui.load(luaNode);
 		sprite->addChild(_zoomedDocGui.layoutChecked("root"));
 
 		TeButtonLayout *btn;
diff --git a/engines/tetraedge/game/in_game_scene.cpp b/engines/tetraedge/game/in_game_scene.cpp
index a5aee934a3b..a219003d6e9 100644
--- a/engines/tetraedge/game/in_game_scene.cpp
+++ b/engines/tetraedge/game/in_game_scene.cpp
@@ -206,9 +206,9 @@ Billboard *InGameScene::billboard(const Common::String &name) {
 }
 
 bool InGameScene::changeBackground(const Common::Path &name) {
-	Common::Path path = g_engine->getCore()->findFile(name);
-	if (Common::File::exists(path)) {
-		_bgGui.spriteLayoutChecked("root")->load(path);
+	TetraedgeFSNode node = g_engine->getCore()->findFile(name);
+	if (node.isReadable()) {
+		_bgGui.spriteLayoutChecked("root")->load(name);
 		if (g_engine->gameType() == TetraedgeEngine::kSyberia2)
 			_bgGui.spriteLayoutChecked("root")->play();
 		return true;
@@ -666,7 +666,7 @@ TeVector2f32 InGameScene::layerSize() {
 	return TeVector2f32(sz.x(), sz.y());
 }
 
-bool InGameScene::load(const Common::Path &scenePath) {
+bool InGameScene::load(const TetraedgeFSNode &sceneNode) {
 	// Syberia 1 has loadActZones function contents inline.
 	loadActZones();
 
@@ -680,38 +680,38 @@ bool InGameScene::load(const Common::Path &scenePath) {
 	_shadowLightNo = -1;
 
 	TeCore *core = g_engine->getCore();
-	const Common::Path lightsPath = core->findFile(getLightsFileName());
-	if (Common::File::exists(lightsPath))
-		loadLights(lightsPath);
+	const TetraedgeFSNode lightsNode(core->findFile(getLightsFileName()));
+	if (lightsNode.isReadable())
+		loadLights(lightsNode);
 
-	if (!Common::File::exists(scenePath))
+	if (!sceneNode.exists())
 		return false;
 
 	close();
-	_loadedPath = scenePath.getParent();
-	Common::File scenefile;
-	if (!scenefile.open(scenePath))
+	_loadedPath = sceneNode.getPath();
+	Common::ScopedPtr<Common::SeekableReadStream> scenefile(sceneNode.createReadStream());
+	if (!scenefile)
 		return false;
 
-	uint32 ncameras = scenefile.readUint32LE();
+	uint32 ncameras = scenefile->readUint32LE();
 	if (ncameras > 1024)
 		error("Improbable number of cameras %d", ncameras);
 	for (uint i = 0; i < ncameras; i++) {
 		TeIntrusivePtr<TeCamera> cam = new TeCamera();
-		deserializeCam(scenefile, cam);
+		deserializeCam(*scenefile, cam);
 		cameras().push_back(cam);
 	}
 
-	uint32 nobjects = scenefile.readUint32LE();
+	uint32 nobjects = scenefile->readUint32LE();
 	if (nobjects > 1024)
 		error("Improbable number of objects %d", nobjects);
 	for (uint i = 0; i < nobjects; i++) {
 		TeIntrusivePtr<TeModel> model = new TeModel();
-		const Common::String modelname = Te3DObject2::deserializeString(scenefile);
+		const Common::String modelname = Te3DObject2::deserializeString(*scenefile);
 		model->setName(modelname);
-		const Common::String objname = Te3DObject2::deserializeString(scenefile);
+		const Common::String objname = Te3DObject2::deserializeString(*scenefile);
 		TePickMesh2 *pickmesh = new TePickMesh2();
-		deserializeModel(scenefile, model, pickmesh);
+		deserializeModel(*scenefile, model, pickmesh);
 		if (modelname.contains("Clic")) {
 			//debug("Loaded clickMesh %s", modelname.c_str());
 			_hitObjects.push_back(model);
@@ -737,39 +737,39 @@ bool InGameScene::load(const Common::Path &scenePath) {
 		}
 	}
 
-	uint32 nfreemovezones = scenefile.readUint32LE();
+	uint32 nfreemovezones = scenefile->readUint32LE();
 	if (nfreemovezones > 1024)
 		error("Improbable number of free move zones %d", nfreemovezones);
 	for (uint i = 0; i < nfreemovezones; i++) {
 		TeFreeMoveZone *zone = new TeFreeMoveZone();
-		TeFreeMoveZone::deserialize(scenefile, *zone, &_blockers, &_rectBlockers, &_actZones);
+		TeFreeMoveZone::deserialize(*scenefile, *zone, &_blockers, &_rectBlockers, &_actZones);
 		_freeMoveZones.push_back(zone);
 		zone->setVisible(false);
 	}
 
-	uint32 ncurves = scenefile.readUint32LE();
+	uint32 ncurves = scenefile->readUint32LE();
 	if (ncurves > 1024)
 		error("Improbable number of curves %d", ncurves);
 	for (uint i = 0; i < ncurves; i++) {
 		TeIntrusivePtr<TeBezierCurve> curve = new TeBezierCurve();
-		TeBezierCurve::deserialize(scenefile, *curve);
+		TeBezierCurve::deserialize(*scenefile, *curve);
 		curve->setVisible(true);
 		_bezierCurves.push_back(curve);
 	}
 
-	uint32 ndummies = scenefile.readUint32LE();
+	uint32 ndummies = scenefile->readUint32LE();
 	if (ndummies > 1024)
 		error("Improbable number of dummies %d", ndummies);
 	for (uint i = 0; i < ndummies; i++) {
 		InGameScene::Dummy dummy;
 		TeVector3f32 vec;
 		TeQuaternion rot;
-		dummy._name = Te3DObject2::deserializeString(scenefile);
-		TeVector3f32::deserialize(scenefile, vec);
+		dummy._name = Te3DObject2::deserializeString(*scenefile);
+		TeVector3f32::deserialize(*scenefile, vec);
 		dummy._position = vec;
-		TeQuaternion::deserialize(scenefile, rot);
+		TeQuaternion::deserialize(*scenefile, rot);
 		dummy._rotation = rot;
-		TeVector3f32::deserialize(scenefile, vec);
+		TeVector3f32::deserialize(*scenefile, vec);
 		dummy._scale = vec;
 		_dummies.push_back(dummy);
 	}
@@ -806,7 +806,7 @@ bool InGameScene::loadXml(const Common::String &zone, const Common::String &scen
 
 	Common::Path xmlpath = _sceneFileNameBase(zone, scene).joinInPlace("Scene")
 												.appendInPlace(scene).appendInPlace(".xml");
-	Common::Path path = g_engine->getCore()->findFile(xmlpath);
+	TetraedgeFSNode node = g_engine->getCore()->findFile(xmlpath);
 	InGameSceneXmlParser parser(this);
 	parser.setAllowText();
 
@@ -817,13 +817,13 @@ bool InGameScene::loadXml(const Common::String &zone, const Common::String &scen
 		// in Syberia 2 has an embedded comment, which is invalid XML.
 		// Patch the contents of the file before loading.
 		//
-		Common::File xmlFile;
-		if (!xmlFile.open(path))
-			error("InGameScene::loadXml: Can't open %s", path.toString(Common::Path::kNativeSeparator).c_str());
-		const int64 bufsize = xmlFile.size();
+		Common::ScopedPtr<Common::SeekableReadStream> xmlFile(node.createReadStream());
+		if (!xmlFile)
+			error("InGameScene::loadXml: Can't open %s", node.toString().c_str());
+		const int64 bufsize = xmlFile->size();
 		char *buf = new char[bufsize+1];
 		buf[bufsize] = '\0';
-		xmlFile.read(buf, bufsize);
+		xmlFile->read(buf, bufsize);
 		fixedbuf = Common::String(buf);
 		delete [] buf;
 		size_t offset = fixedbuf.find("<!-- <rippleMask");
@@ -838,12 +838,12 @@ bool InGameScene::loadXml(const Common::String &zone, const Common::String &scen
 		parser.loadBuffer((const byte *)fixedbuf.c_str(), bufsize);
 	} else {
 		// Regular loading.
-		if (!parser.loadFile(path))
-			error("InGameScene::loadXml: Can't load %s", path.toString(Common::Path::kNativeSeparator).c_str());
+		if (!node.loadXML(parser))
+			error("InGameScene::loadXml: Can't load %s", node.toString().c_str());
 	}
 
 	if (!parser.parse())
-		error("InGameScene::loadXml: Can't parse %s", path.toString(Common::Path::kNativeSeparator).c_str());
+		error("InGameScene::loadXml: Can't parse %s", node.toString().c_str());
 
 	// loadFlamme and loadSnowCustom are handled by the above.
 
@@ -856,16 +856,17 @@ bool InGameScene::loadXml(const Common::String &zone, const Common::String &scen
 	_shadowLightNo = -1;
 
 	TeCore *core = g_engine->getCore();
-	const Common::Path lightsPath = core->findFile(getLightsFileName());
-	if (Common::File::exists(lightsPath))
-		loadLights(lightsPath);
+	const TetraedgeFSNode lightsNode(core->findFile(getLightsFileName()));
+	if (lightsNode.isReadable())
+		loadLights(lightsNode);
 
-	Common::Path pxmlpath = core->findFile(_sceneFileNameBase(zone, scene).joinInPlace("particles.xml"));
-	if (Common::File::exists(pxmlpath)) {
+	Common::Path pxmlpath = _sceneFileNameBase(zone, scene).joinInPlace("particles.xml");
+	TetraedgeFSNode pnode = g_engine->getCore()->findFile(pxmlpath);
+	if (pnode.isReadable()) {
 		ParticleXmlParser pparser;
 		pparser._scene = this;
-		if (!pparser.loadFile(pxmlpath))
-			error("InGameScene::loadXml: Can't load %s", pxmlpath.toString(Common::Path::kNativeSeparator).c_str());
+		if (!pnode.loadXML(pparser))
+			error("InGameScene::loadXml: Can't load %s", pnode.toString().c_str());
 		if (!pparser.parse())
 			error("InGameScene::loadXml: Can't parse %s", pxmlpath.toString(Common::Path::kNativeSeparator).c_str());
 	}
@@ -945,13 +946,13 @@ bool InGameScene::loadFreeMoveZone(const Common::String &name, TeVector2f32 &gri
 	return true;
 }
 
-bool InGameScene::loadLights(const Common::Path &path) {
+bool InGameScene::loadLights(const TetraedgeFSNode &node) {
 	SceneLightsXmlParser parser(&_lights);
 
-	if (!parser.loadFile(path))
-		error("InGameScene::loadLights: Can't load %s", path.toString(Common::Path::kNativeSeparator).c_str());
+	if (!node.loadXML(parser))
+		error("InGameScene::loadLights: Can't load %s", node.toString().c_str());
 	if (!parser.parse())
-		error("InGameScene::loadLights: Can't parse %s", path.toString(Common::Path::kNativeSeparator).c_str());
+		error("InGameScene::loadLights: Can't parse %s", node.toString().c_str());
 
 	_shadowColor = parser.getShadowColor();
 	_shadowLightNo = parser.getShadowLightNo();
@@ -989,8 +990,8 @@ bool InGameScene::loadLights(const Common::Path &path) {
 	return true;
 }
 
-void InGameScene::loadMarkers(const Common::Path &path) {
-	_markerGui.load(path);
+void InGameScene::loadMarkers(const TetraedgeFSNode &node) {
+	_markerGui.load(node);
 	TeLayout *bg = _bgGui.layoutChecked("background");
 	TeSpriteLayout *root = Game::findSpriteLayoutByName(bg, "root");
 	bg->setRatioMode(TeILayout::RATIO_MODE_NONE);
@@ -1080,13 +1081,13 @@ bool InGameScene::loadPlayerCharacter(const Common::String &name) {
 
 bool InGameScene::loadCurve(const Common::String &name) {
 	TeCore *core = g_engine->getCore();
-	const Common::Path path = core->findFile(_sceneFileNameBase().joinInPlace(name).appendInPlace(".bin"));
-	if (!Common::File::exists(path)) {
-		warning("[InGameScene::loadCurve] Can't open file : %s.", path.toString(Common::Path::kNativeSeparator).c_str());
+	TetraedgeFSNode node = core->findFile(_sceneFileNameBase().joinInPlace(name).appendInPlace(".bin"));
+	if (!node.isReadable()) {
+		warning("[InGameScene::loadCurve] Can't open file : %s.", node.toString().c_str());
 		return false;
 	}
 	TeIntrusivePtr<TeBezierCurve> curve = new TeBezierCurve();
-	curve->loadBin(path);
+	curve->loadBin(node);
 	_bezierCurves.push_back(curve);
 	return true;
 }
@@ -1094,25 +1095,24 @@ bool InGameScene::loadCurve(const Common::String &name) {
 bool InGameScene::loadDynamicLightBloc(const Common::String &name, const Common::String &texture, const Common::String &zone, const Common::String &scene) {
 	const Common::Path pdat = _sceneFileNameBase(zone, scene).joinInPlace(name).appendInPlace(".bin");
 	const Common::Path ptex = _sceneFileNameBase(zone, scene).joinInPlace(texture);
-	Common::Path datPath = g_engine->getCore()->findFile(pdat);
-	Common::Path texPath = g_engine->getCore()->findFile(ptex);
-	if (!Common::File::exists(datPath)) {
-		warning("[InGameScene::loadDynamicLightBloc] Can't open file : %s.", pdat.toString(Common::Path::kNativeSeparator).c_str());
+	TetraedgeFSNode datNode = g_engine->getCore()->findFile(pdat);
+	TetraedgeFSNode texNode = g_engine->getCore()->findFile(ptex);
+	if (!datNode.isReadable()) {
+		warning("[InGameScene::loadDynamicLightBloc] Can't open file : %s.", pdat.toString('/').c_str());
 		return false;
 	}
 
-	Common::File file;
-	file.open(datPath);
+	Common::ScopedPtr<Common::SeekableReadStream> file(datNode.createReadStream());
 
 	TeModel *model = new TeModel();
 	model->setMeshCount(1);
-	model->setName(datPath.baseName());
+	model->setName(datNode.getPath().baseName());
 
 	// Read position/rotation/scale.
-	model->deserialize(file, *model);
+	model->deserialize(*file, *model);
 
-	uint32 verts = file.readUint32LE();
-	uint32 tricount = file.readUint32LE();
+	uint32 verts = file->readUint32LE();
+	uint32 tricount = file->readUint32LE();
 	if (verts > 100000 || tricount > 10000)
 		error("Improbable number of verts (%d) or triangles (%d)", verts, tricount);
 
@@ -1121,25 +1121,25 @@ bool InGameScene::loadDynamicLightBloc(const Common::String &name, const Common:
 
 	for (uint i = 0; i < verts; i++) {
 		TeVector3f32 vec;
-		TeVector3f32::deserialize(file, vec);
+		TeVector3f32::deserialize(*file, vec);
 		mesh->setVertex(i, vec);
 		mesh->setNormal(i, TeVector3f32(0, 0, 1));
 	}
 	for (uint i = 0; i < verts; i++) {
 		TeVector2f32 vec2;
-		TeVector2f32::deserialize(file, vec2);
+		TeVector2f32::deserialize(*file, vec2);
 		vec2.setY(1.0 - vec2.getY());
 		mesh->setTextureUV(i, vec2);
 	}
 
 	for (uint i = 0; i < tricount * 3; i++)
-		mesh->setIndex(i, file.readUint16LE());
+		mesh->setIndex(i, file->readUint16LE());
 
-	file.close();
+	file.reset();
 
-	if (Common::File::exists(texPath)) {
+	if (texNode.exists()) {
 		TeIntrusivePtr<Te3DTexture> tex = Te3DTexture::makeInstance();
-		tex->load2(texPath, false);
+		tex->load2(texNode, false);
 		mesh->defaultMaterial(tex);
 	} else if (texture.size()) {
 		warning("loadDynamicLightBloc: Failed to load texture %s", texture.c_str());
@@ -1152,20 +1152,20 @@ bool InGameScene::loadDynamicLightBloc(const Common::String &name, const Common:
 }
 
 bool InGameScene::loadLight(const Common::String &name, const Common::String &zone, const Common::String &scene) {
-	Common::Path datPath = g_engine->getCore()->findFile(_sceneFileNameBase(zone, scene).joinInPlace(name).appendInPlace(".bin"));
-	if (!Common::File::exists(datPath)) {
-		warning("[InGameScene::loadLight] Can't open file : %s.", datPath.toString(Common::Path::kNativeSeparator).c_str());
+	Common::Path datpath = _sceneFileNameBase(zone, scene).joinInPlace(name).appendInPlace(".bin");
+	TetraedgeFSNode datnode = g_engine->getCore()->findFile(datpath);
+	if (!datnode.isReadable()) {
+		warning("[InGameScene::loadLight] Can't open file : %s.", datpath.toString(Common::Path::kNativeSeparator).c_str());
 		return false;
 	}
 
-	Common::File file;
-	file.open(datPath);
+	Common::ScopedPtr<Common::SeekableReadStream> file(datnode.createReadStream());
 	SceneLight light;
 	light._name = name;
-	TeVector3f32::deserialize(file, light._v1);
-	TeVector3f32::deserialize(file, light._v2);
-	light._color.deserialize(file);
-	light._f = file.readFloatLE();
+	TeVector3f32::deserialize(*file, light._v1);
+	TeVector3f32::deserialize(*file, light._v2);
+	light._color.deserialize(*file);
+	light._f = file->readFloatLE();
 
 	_sceneLights.push_back(light);
 	return true;
@@ -1173,24 +1173,23 @@ bool InGameScene::loadLight(const Common::String &name, const Common::String &zo
 
 bool InGameScene::loadMask(const Common::String &name, const Common::String &texture, const Common::String &zone, const Common::String &scene) {
 	TeCore *core = g_engine->getCore();
-	Common::Path datPath = core->findFile(_sceneFileNameBase(zone, scene).joinInPlace(name).appendInPlace(".bin"));
-	Common::Path texPath = core->findFile(_sceneFileNameBase(zone, scene).joinInPlace(texture));
-	if (!Common::File::exists(datPath)) {
-		warning("[InGameScene::loadMask] Can't open file : %s.", datPath.toString(Common::Path::kNativeSeparator).c_str());
+	TetraedgeFSNode texnode = core->findFile(_sceneFileNameBase(zone, scene).joinInPlace(texture));
+	TetraedgeFSNode datnode = core->findFile(_sceneFileNameBase(zone, scene).joinInPlace(name).appendInPlace(".bin"));
+	if (!datnode.isReadable()) {
+		warning("[InGameScene::loadMask] Can't open file : %s.", datnode.toString().c_str());
 		return false;
 	}
 	TeModel *model = new TeModel();
 	model->setMeshCount(1);
 	model->setName(name);
 
-	Common::File file;
-	file.open(datPath);
+	Common::ScopedPtr<Common::SeekableReadStream> file(datnode.createReadStream());
 
 	// Load position, rotation, size.
-	Te3DObject2::deserialize(file, *model, false);
+	Te3DObject2::deserialize(*file, *model, false);
 
-	uint32 verts = file.readUint32LE();
-	uint32 tricount = file.readUint32LE();
+	uint32 verts = file->readUint32LE();
+	uint32 tricount = file->readUint32LE();
 	if (verts > 100000 || tricount > 10000)
 		error("Improbable number of verts (%d) or triangles (%d)", verts, tricount);
 
@@ -1199,7 +1198,7 @@ bool InGameScene::loadMask(const Common::String &name, const Common::String &tex
 
 	for (uint i = 0; i < verts; i++) {
 		TeVector3f32 vec;
-		TeVector3f32::deserialize(file, vec);
+		TeVector3f32::deserialize(*file, vec);
 		mesh->setVertex(i, vec);
 		mesh->setNormal(i, TeVector3f32(0, 0, 1));
 		if (_maskAlpha) {
@@ -1209,20 +1208,19 @@ bool InGameScene::loadMask(const Common::String &name, const Common::String &tex
 
 	for (uint i = 0; i < verts; i++) {
 		TeVector2f32 vec2;
-		TeVector2f32::deserialize(file, vec2);
+		TeVector2f32::deserialize(*file, vec2);
 		vec2.setY(1.0 - vec2.getY());
 		mesh->setTextureUV(i, vec2);
 	}
 
 	// For some reason this one has the indexes in reverse order :(
 	for (uint i = 0; i < tricount * 3; i += 3) {
-		mesh->setIndex(i + 2, file.readUint16LE());
-		mesh->setIndex(i + 1, file.readUint16LE());
-		mesh->setIndex(i, file.readUint16LE());
+		mesh->setIndex(i + 2, file->readUint16LE());
+		mesh->setIndex(i + 1, file->readUint16LE());
+		mesh->setIndex(i, file->readUint16LE());
 	}
 
-	file.close();
-	Common::Path texnode = core->findFile(texPath);
+	file.reset();
 	TeIntrusivePtr<Te3DTexture> tex = Te3DTexture::load2(texnode, !_maskAlpha);
 
 	if (tex) {
@@ -1260,23 +1258,22 @@ bool InGameScene::loadShadowMask(const Common::String &name, const Common::Strin
 }
 
 bool InGameScene::loadShadowReceivingObject(const Common::String &name, const Common::String &zone, const Common::String &scene) {
-	Common::Path datPath = g_engine->getCore()->findFile(_sceneFileNameBase(zone, scene).joinInPlace(name).appendInPlace(".bin"));
-	if (!Common::File::exists(datPath)) {
-		warning("[InGameScene::loadShadowReceivingObject] Can't open file : %s.", datPath.toString(Common::Path::kNativeSeparator).c_str());
+	TetraedgeFSNode datnode = g_engine->getCore()->findFile(_sceneFileNameBase(zone, scene).joinInPlace(name).appendInPlace(".bin"));
+	if (!datnode.isReadable()) {
+		warning("[InGameScene::loadShadowReceivingObject] Can't open file : %s.", datnode.toString().c_str());
 		return false;
 	}
 	TeModel *model = new TeModel();
 	model->setMeshCount(1);
 	model->setName(name);
 
-	Common::File file;
-	file.open(datPath);
+	Common::ScopedPtr<Common::SeekableReadStream> file(datnode.createReadStream());
 
 	// Load position, rotation, size.
-	Te3DObject2::deserialize(file, *model, false);
+	Te3DObject2::deserialize(*file, *model, false);
 
-	uint32 verts = file.readUint32LE();
-	uint32 tricount = file.readUint32LE();
+	uint32 verts = file->readUint32LE();
+	uint32 tricount = file->readUint32LE();
 	if (verts > 100000 || tricount > 10000)
 		error("Improbable number of verts (%d) or triangles (%d)", verts, tricount);
 
@@ -1285,42 +1282,41 @@ bool InGameScene::loadShadowReceivingObject(const Common::String &name, const Co
 
 	for (uint i = 0; i < verts; i++) {
 		TeVector3f32 vec;
-		TeVector3f32::deserialize(file, vec);
+		TeVector3f32::deserialize(*file, vec);
 		mesh->setVertex(i, vec);
 		mesh->setNormal(i, TeVector3f32(0, 0, 1));
 	}
 
 	// Indexes in reverse order :(
 	for (uint i = 0; i < tricount * 3; i += 3) {
-		mesh->setIndex(i + 2, file.readUint16LE());
-		mesh->setIndex(i + 1, file.readUint16LE());
-		mesh->setIndex(i, file.readUint16LE());
+		mesh->setIndex(i + 2, file->readUint16LE());
+		mesh->setIndex(i + 1, file->readUint16LE());
+		mesh->setIndex(i, file->readUint16LE());
 	}
 
-	file.close();
+	file.reset();
 
 	_shadowReceivingObjects.push_back(model);
 	return true;
 }
 
 bool InGameScene::loadZBufferObject(const Common::String &name, const Common::String &zone, const Common::String &scene) {
-	Common::Path datPath = g_engine->getCore()->findFile(_sceneFileNameBase(zone, scene).joinInPlace(name).appendInPlace(".bin"));
-	if (!Common::File::exists(datPath)) {
-		warning("[InGameScene::loadZBufferObject] Can't open file : %s.", datPath.toString(Common::Path::kNativeSeparator).c_str());
+	TetraedgeFSNode datnode = g_engine->getCore()->findFile(_sceneFileNameBase(zone, scene).joinInPlace(name).appendInPlace(".bin"));
+	if (!datnode.isReadable()) {
+		warning("[InGameScene::loadZBufferObject] Can't open file : %s.", datnode.toString().c_str());
 		return false;
 	}
 	TeModel *model = new TeModel();
 	model->setMeshCount(1);
 	model->setName(name);
 
-	Common::File file;
-	file.open(datPath);
+	Common::ScopedPtr<Common::SeekableReadStream> file(datnode.createReadStream());
 
 	// Load position, rotation, size.
-	Te3DObject2::deserialize(file, *model, false);
+	Te3DObject2::deserialize(*file, *model, false);
 
-	uint32 verts = file.readUint32LE();
-	uint32 tricount = file.readUint32LE();
+	uint32 verts = file->readUint32LE();
+	uint32 tricount = file->readUint32LE();
 	if (verts > 100000 || tricount > 10000)
 		error("Improbable number of verts (%d) or triangles (%d)", verts, tricount);
 
@@ -1329,14 +1325,14 @@ bool InGameScene::loadZBufferObject(const Common::String &name, const Common::St
 
 	for (uint i = 0; i < verts; i++) {
 		TeVector3f32 vec;
-		TeVector3f32::deserialize(file, vec);
+		TeVector3f32::deserialize(*file, vec);
 		mesh->setVertex(i, vec);
 		mesh->setNormal(i, TeVector3f32(0, 0, 1));
 		mesh->setColor(i, TeColor(128, 0, 255, 128));
 	}
 
 	for (uint i = 0; i < tricount * 3; i++) {
-		mesh->setIndex(i, file.readUint16LE());
+		mesh->setIndex(i, file->readUint16LE());
 	}
 
 	_zoneModels.push_back(model);
@@ -1398,9 +1394,9 @@ void InGameScene::loadBlockers() {
 	}
 }
 
-void InGameScene::loadBackground(const Common::Path &path) {
+void InGameScene::loadBackground(const TetraedgeFSNode &node) {
 	_youkiManager.reset();
-	_bgGui.load(path);
+	_bgGui.load(node);
 	TeLayout *bg = _bgGui.layout("background");
 	TeLayout *root = _bgGui.layout("root");
 	bg->setRatioMode(TeILayout::RATIO_MODE_NONE);
@@ -1434,8 +1430,8 @@ bool InGameScene::loadBillboard(const Common::String &name) {
 	}
 }
 
-void InGameScene::loadInteractions(const Common::Path &path) {
-	_hitObjectGui.load(path);
+void InGameScene::loadInteractions(const TetraedgeFSNode &node) {
+	_hitObjectGui.load(node);
 	TeLayout *bgbackground = _bgGui.layoutChecked("background");
 	Game *game = g_engine->getGame();
 	TeSpriteLayout *root = game->findSpriteLayoutByName(bgbackground, "root");
diff --git a/engines/tetraedge/game/in_game_scene.h b/engines/tetraedge/game/in_game_scene.h
index 33da6b0d667..ade0e5b49aa 100644
--- a/engines/tetraedge/game/in_game_scene.h
+++ b/engines/tetraedge/game/in_game_scene.h
@@ -181,14 +181,14 @@ public:
 	bool isObjectBlocking(const Common::String &name);
 	TeVector2f32 layerSize();
 
-	virtual bool load(const Common::Path &path) override;
-	void loadBackground(const Common::Path &node);
+	virtual bool load(const TetraedgeFSNode &node) override;
+	void loadBackground(const TetraedgeFSNode &node);
 	bool loadBillboard(const Common::String &name);
 	void loadBlockers();
 	bool loadCharacter(const Common::String &name);
-	void loadInteractions(const Common::Path &path);
-	bool loadLights(const Common::Path &path);
-	void loadMarkers(const Common::Path &path);
+	void loadInteractions(const TetraedgeFSNode &node);
+	bool loadLights(const TetraedgeFSNode &node);
+	void loadMarkers(const TetraedgeFSNode &node);
 	bool loadObject(const Common::String &oname);
 	bool loadObjectMaterials(const Common::String &name);
 	bool loadObjectMaterials(const Common::Path &path, const Common::String &name);
diff --git a/engines/tetraedge/game/loc_file.cpp b/engines/tetraedge/game/loc_file.cpp
index 0baca5b360e..09e078ca96b 100644
--- a/engines/tetraedge/game/loc_file.cpp
+++ b/engines/tetraedge/game/loc_file.cpp
@@ -31,25 +31,26 @@ namespace Tetraedge {
 LocFile::LocFile() {
 }
 
-void LocFile::load(const Common::Path &path) {
+void LocFile::load(const TetraedgeFSNode &fsnode) {
 	TeNameValXmlParser parser;
 	const Common::String xmlHeader("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-	Common::File locFile;
-	if (!locFile.open(path))
-		error("LocFile::load: failed to open %s.", path.baseName().c_str());
+	Common::ScopedPtr<Common::SeekableReadStream> locFile(fsnode.createReadStream());
+	const Common::String path = fsnode.getName();
+	if (!locFile)
+		error("LocFile::load: failed to open %s.", path.c_str());
 
-	int64 fileLen = locFile.size();
+	int64 fileLen = locFile->size();
 	char *buf = new char[fileLen + 1];
 	buf[fileLen] = '\0';
-	locFile.read(buf, fileLen);
+	locFile->read(buf, fileLen);
 	const Common::String xmlContents = xmlHeader + buf;
 	delete [] buf;
-	locFile.close();
+	locFile.reset();
 	if (!parser.loadBuffer((const byte *)xmlContents.c_str(), xmlContents.size()))
-		error("LocFile::load: failed to load %s.", path.baseName().c_str());
+		error("LocFile::load: failed to load %s.", path.c_str());
 
 	if (!parser.parse())
-		error("LocFile::load: failed to parse %s.", path.baseName().c_str());
+		error("LocFile::load: failed to parse %s.", path.c_str());
 
 	_map = parser.getMap();
 }
diff --git a/engines/tetraedge/game/loc_file.h b/engines/tetraedge/game/loc_file.h
index 1c033ab000f..b54d81c13bf 100644
--- a/engines/tetraedge/game/loc_file.h
+++ b/engines/tetraedge/game/loc_file.h
@@ -26,6 +26,7 @@
 #include "common/fs.h"
 
 #include "tetraedge/te/te_i_loc.h"
+#include "tetraedge/tetraedge.h"
 
 namespace Tetraedge {
 
@@ -33,7 +34,7 @@ class LocFile : public TeILoc {
 public:
 	LocFile();
 
-	void load(const Common::Path &path);
+	void load(const TetraedgeFSNode &fsnode);
 	const Common::String *value(const Common::String &key) const;
 
 };
diff --git a/engines/tetraedge/game/splash_screens.cpp b/engines/tetraedge/game/splash_screens.cpp
index c8eab33aa5c..ab749745c59 100644
--- a/engines/tetraedge/game/splash_screens.cpp
+++ b/engines/tetraedge/game/splash_screens.cpp
@@ -40,9 +40,9 @@ void SplashScreens::enter()	{
 		_entered = true;
 		_splashNo = 0;
 		const char *scriptStr = g_engine->gameIsAmerzone() ? "GUI/PC-MacOSX/Splash0.lua" : "menus/splashes/splash0.lua";
-		Common::Path path = g_engine->getCore()->findFile(scriptStr);
-		if (Common::File::exists(path)) {
-			load(path);
+		TetraedgeFSNode node = g_engine->getCore()->findFile(scriptStr);
+		if (node.exists()) {
+			load(node);
 			Application *app = g_engine->getApplication();
 			TeLayout *splash = layoutChecked("splash");
 
@@ -72,11 +72,11 @@ bool SplashScreens::onAlarm() {
 		return true;
 	}
 
-	Common::Path path = g_engine->getCore()->findFile(scriptName);
-	if (!Common::File::exists(path)) {
+	TetraedgeFSNode node = g_engine->getCore()->findFile(scriptName);
+	if (!node.exists()) {
 		onQuitSplash();
 	} else {
-		load(path);
+		load(node);
 
 		TeButtonLayout *splash = buttonLayoutChecked("splash");
 		splash->onMouseClickValidated().add(this, &SplashScreens::onQuitSplash);
diff --git a/engines/tetraedge/game/syberia_game.cpp b/engines/tetraedge/game/syberia_game.cpp
index 9ca07453fdb..fc753400072 100644
--- a/engines/tetraedge/game/syberia_game.cpp
+++ b/engines/tetraedge/game/syberia_game.cpp
@@ -224,7 +224,7 @@ bool SyberiaGame::changeWarp2(const Common::String &zone, const Common::String &
 	luapath.appendInPlace(scene);
 	luapath.appendInPlace(".lua");
 
-	if (Common::File::exists(g_engine->getCore()->findFile(luapath))) {
+	if (g_engine->getCore()->findFile(luapath).exists()) {
 		_luaScript.execute("OnLeave");
 		_luaContext.removeGlobal("On");
 		_luaContext.removeGlobal("OnEnter");
@@ -462,17 +462,17 @@ bool SyberiaGame::initWarp(const Common::String &zone, const Common::String &sce
 
 	TeCore *core = g_engine->getCore();
 
-	const Common::Path intLuaPath = core->findFile(scenePath.join(Common::String::format("Int%s.lua", scene.c_str())));
-	const Common::Path logicLuaPath = core->findFile(scenePath.join(Common::String::format("Logic%s.lua", scene.c_str())));
-	const Common::Path setLuaPath = core->findFile(scenePath.join(Common::String::format("Set%s.lua", scene.c_str())));
-	const Common::Path forLuaPath = core->findFile(scenePath.join(Common::String::format("For%s.lua", scene.c_str())));
-	const Common::Path markerLuaPath = core->findFile(scenePath.join(Common::String::format("Marker%s.lua", scene.c_str())));
+	const TetraedgeFSNode intLuaPath = core->findFile(scenePath.join(Common::String::format("Int%s.lua", scene.c_str())));
+	const TetraedgeFSNode logicLuaPath = core->findFile(scenePath.join(Common::String::format("Logic%s.lua", scene.c_str())));
+	const TetraedgeFSNode setLuaPath = core->findFile(scenePath.join(Common::String::format("Set%s.lua", scene.c_str())));
+	const TetraedgeFSNode forLuaPath = core->findFile(scenePath.join(Common::String::format("For%s.lua", scene.c_str())));
+	const TetraedgeFSNode markerLuaPath = core->findFile(scenePath.join(Common::String::format("Marker%s.lua", scene.c_str())));
 
-	bool intLuaExists = Common::File::exists(intLuaPath);
-	bool logicLuaExists = Common::File::exists(logicLuaPath);
-	bool setLuaExists = Common::File::exists(setLuaPath);
-	bool forLuaExists = Common::File::exists(forLuaPath);
-	bool markerLuaExists = Common::File::exists(markerLuaPath);
+	bool intLuaExists = intLuaPath.exists();
+	bool logicLuaExists = logicLuaPath.exists();
+	bool setLuaExists = setLuaPath.exists();
+	bool forLuaExists = forLuaPath.exists();
+	bool markerLuaExists = markerLuaPath.exists();
 
 	if (!intLuaExists && !logicLuaExists && !setLuaExists && !forLuaExists && !markerLuaExists) {
 		debug("No lua scripts for scene %s zone %s", scene.c_str(), zone.c_str());
@@ -500,10 +500,10 @@ bool SyberiaGame::initWarp(const Common::String &zone, const Common::String &sce
 	_scene.hitObjectGui().unload();
 	Common::Path geomPath(Common::String::format("scenes/%s/Geometry%s.bin",
 												 zone.c_str(), zone.c_str()));
-	geomPath = core->findFile(geomPath);
-	if (Common::File::exists(geomPath)) {
+	TetraedgeFSNode geomFile = core->findFile(geomPath);
+	if (geomFile.isReadable()) {
 		// Syberia 1, load geom bin
-		_scene.load(geomPath);
+		_scene.load(geomFile);
 	} else {
 		// Syberia 2, load from xml
 		_scene.loadXml(zone, scene);
diff --git a/engines/tetraedge/module.mk b/engines/tetraedge/module.mk
index 30ac79e4496..4ad639e9a97 100644
--- a/engines/tetraedge/module.mk
+++ b/engines/tetraedge/module.mk
@@ -142,7 +142,8 @@ MODULE_OBJS := \
 	te/te_xml_parser.o \
 	te/te_zlib_jpeg.o \
 	te/te_xml_gui.o \
-	metaengine.o
+	metaengine.o \
+	obb_archive.o
 
 ifdef USE_TINYGL
 MODULE_OBJS += \
@@ -171,4 +172,4 @@ endif
 include $(srcdir)/rules.mk
 
 # Detection objects
-DETECT_OBJS += $(MODULE)/detection.o
+DETECT_OBJS += $(MODULE)/detection.o $(MODULE)/obb_archive.o
diff --git a/engines/tetraedge/obb_archive.cpp b/engines/tetraedge/obb_archive.cpp
new file mode 100644
index 00000000000..5869f48e0b0
--- /dev/null
+++ b/engines/tetraedge/obb_archive.cpp
@@ -0,0 +1,116 @@
+/* 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 "common/endian.h"
+#include "common/debug.h"
+#include "common/str.h"
+#include "common/file.h"
+#include "common/util.h"
+#include "common/substream.h"
+#include "common/compression/dcl.h"
+#include "tetraedge/obb_archive.h"
+
+namespace Tetraedge {
+
+bool ObbArchive::readFileMap(Common::SeekableReadStream &indexFile, FileMap &files) {
+	byte ver = indexFile.readByte();
+	if (ver != 1) {
+		warning("Unsupported tetraedge OBB version %d", ver);
+		return false;
+	}
+
+	uint32 filecnt = indexFile.readUint32LE();
+
+	for (uint i = 0; i < filecnt; i++) {
+		uint32 namelen = indexFile.readUint32LE();
+
+		if (namelen > 0x8000) {
+			warning("ObbArchive::readFileMap: Name too long: 0x%x",
+				namelen);
+			return false;
+		}
+
+		char *buf = (char *)malloc(namelen + 1);
+		assert(buf);
+		indexFile.read(buf, namelen);
+		buf[namelen] = 0;
+		uint32 offset = indexFile.readUint32LE();
+		uint32 sz = indexFile.readUint32LE();
+		if (offset > indexFile.size() || sz > indexFile.size()
+		    || offset + sz > indexFile.size()) {
+			warning("ObbArchive::readFileMap: File outside of container: 0x%x+0x%x > 0x%llx",
+				offset, sz, (unsigned long long) indexFile.size());
+			return false;
+		}
+		files[buf] = FileDescriptor(offset, sz);
+
+		free(buf);
+	}
+
+	return true;
+}
+
+ObbArchive* ObbArchive::open(const Common::Path& obbName) {
+	Common::File indexFile;
+	FileMap files;
+
+	if (!indexFile.open(obbName))
+		return nullptr;
+
+	if (!readFileMap(indexFile, files))
+		return nullptr;
+
+	return new ObbArchive(files, obbName);
+}
+
+bool ObbArchive::hasFile(const Common::Path &path) const {
+	return _files.contains(path.toString('/'));
+}
+
+int ObbArchive::listMembers(Common::ArchiveMemberList &list) const {
+	for (FileMap::const_iterator i = _files.begin(), end = _files.end(); i != end; ++i) {
+		list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(i->_key, *this)));
+	}
+
+	return _files.size();
+}
+
+const Common::ArchiveMemberPtr ObbArchive::getMember(const Common::Path &path) const {
+	FileMap::const_iterator i = _files.find(path.toString('/'));
+	if (i == _files.end())
+		return nullptr;
+
+	return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(i->_key, *this));
+}
+
+Common::SeekableReadStream *ObbArchive::createReadStreamForMember(const Common::Path &path) const {
+	Common::String translated = path.toString('/');
+	if (!_files.contains(translated))
+		return nullptr;
+	FileDescriptor desc = _files.getVal(translated);
+
+	Common::File *f = new Common::File();
+	if (!f->open(_obbName))
+		return nullptr;
+
+	return new Common::SeekableSubReadStream(f, desc._fileOffset, desc._fileOffset + desc._fileSize, DisposeAfterUse::YES);
+}
+}
diff --git a/engines/tetraedge/obb_archive.h b/engines/tetraedge/obb_archive.h
new file mode 100644
index 00000000000..b68813a4f90
--- /dev/null
+++ b/engines/tetraedge/obb_archive.h
@@ -0,0 +1,64 @@
+/* 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 TETRAEDGE_OBB_ARCHIVE_H
+#define TETRAEDGE_OBB_ARCHIVE_H
+
+#include "common/archive.h"
+#include "common/ptr.h"
+#include "common/stream.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+
+namespace Tetraedge {
+
+class ObbArchive : public Common::Archive {
+public:
+	bool hasFile(const Common::Path &path) const override;
+	int listMembers(Common::ArchiveMemberList&) const override;
+	const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
+	Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
+
+	// Similar to FileDescriptionBin but in native-endian and native strings.
+	struct FileDescriptor {
+		FileDescriptor() : _fileOffset(0), _fileSize(0) {}
+		FileDescriptor(uint32 offset,
+			       uint32 size) : _fileOffset(offset), _fileSize(size) {}
+		
+		uint32 _fileOffset;
+		uint32 _fileSize;
+	};
+
+    	typedef Common::HashMap<Common::String, FileDescriptor, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FileMap;
+
+	static bool readFileMap(Common::SeekableReadStream &indexFile, FileMap &files);
+	static ObbArchive* open(const Common::Path& obbName);
+
+private:
+	ObbArchive(const FileMap& files,
+		   const Common::Path& obbName) : _files(files), _obbName(obbName) {}
+
+	FileMap _files;
+	Common::Path _obbName;
+};
+}
+
+#endif
diff --git a/engines/tetraedge/te/te_3d_texture.cpp b/engines/tetraedge/te/te_3d_texture.cpp
index e06e1763a63..aa15686e1b3 100644
--- a/engines/tetraedge/te/te_3d_texture.cpp
+++ b/engines/tetraedge/te/te_3d_texture.cpp
@@ -44,20 +44,20 @@ bool Te3DTexture::hasAlpha() const {
 }
 
 /*static*/
-TeIntrusivePtr<Te3DTexture> Te3DTexture::load2(const Common::Path &path, bool alphaOnly) {
-	const Common::Path fullPath = path.append(".3dtex");
+TeIntrusivePtr<Te3DTexture> Te3DTexture::load2(const TetraedgeFSNode &node, bool alphaOnly) {
+	const Common::Path fullPath = node.getPath().append(".3dtex");
 
 	TeResourceManager *resMgr = g_engine->getResourceManager();
 	if (!resMgr->exists(fullPath)) {
 		TeIntrusivePtr<Te3DTexture> retval(makeInstance());
-		if (!Common::File::exists(path))
-			warning("Request to load unreadable texture %s", path.toString(Common::Path::kNativeSeparator).c_str());
+		if (!node.exists())
+			warning("Request to load unreadable texture %s", node.toString().c_str());
 		if (alphaOnly)
 			retval->setLoadAlphaOnly();
 
-		bool result = retval->load(path);
+		bool result = retval->load(node);
 		if (!result)
-			warning("Failed loading texture %s", path.toString(Common::Path::kNativeSeparator).c_str());
+			warning("Failed loading texture %s", node.toString().c_str());
 
 		retval->setAccessName(fullPath);
 		resMgr->addResource(retval.get());
@@ -67,11 +67,11 @@ TeIntrusivePtr<Te3DTexture> Te3DTexture::load2(const Common::Path &path, bool al
 	}
 }
 
-bool Te3DTexture::load(const Common::Path &path) {
+bool Te3DTexture::load(const TetraedgeFSNode &node) {
 	TeResourceManager *resmgr = g_engine->getResourceManager();
-	TeIntrusivePtr<TeImage> img = resmgr->getResource<TeImage>(path);
+	TeIntrusivePtr<TeImage> img = resmgr->getResource<TeImage>(node);
 	bool result = load(*img);
-	setAccessName(path.append(".3dtex"));
+	setAccessName(node.getPath().append(".3dtex"));
 	return result;
 }
 
diff --git a/engines/tetraedge/te/te_3d_texture.h b/engines/tetraedge/te/te_3d_texture.h
index 22e0cddd6ac..1ef73fc6f47 100644
--- a/engines/tetraedge/te/te_3d_texture.h
+++ b/engines/tetraedge/te/te_3d_texture.h
@@ -48,11 +48,11 @@ public:
 	TeImage::Format getFormat() const { return _format; }
 	bool hasAlpha() const;
 
-	bool load(const Common::Path &path);
+	bool load(const TetraedgeFSNode &path);
 	virtual bool load(const TeImage &img) = 0;
 	// The original passes a GL enum param, but it's only ever GL_INVALID or GL_ALPHA.
 	// Simplify to avoid leaking gl types.
-	static TeIntrusivePtr<Te3DTexture> load2(const Common::Path &path, bool alphaOnly);
+	static TeIntrusivePtr<Te3DTexture> load2(const TetraedgeFSNode &node, bool alphaOnly);
 
 	static TeVector2s32 optimisedSize(const TeVector2s32 &size);
 
diff --git a/engines/tetraedge/te/te_bezier_curve.cpp b/engines/tetraedge/te/te_bezier_curve.cpp
index 18e5065012f..fb8a221ebba 100644
--- a/engines/tetraedge/te/te_bezier_curve.cpp
+++ b/engines/tetraedge/te/te_bezier_curve.cpp
@@ -208,29 +208,28 @@ void TeBezierCurve::deserialize(Common::ReadStream &stream, TeBezierCurve &curve
 	}
 }
 
-void TeBezierCurve::loadBin(const Common::Path &path) {
-	Common::File file;
-	file.open(path);
-	Common::String fname = path.baseName();
+void TeBezierCurve::loadBin(TetraedgeFSNode &node) {
+	Common::ScopedPtr<Common::SeekableReadStream> file(node.createReadStream());
+	Common::String fname = node.getPath().baseName();
 	if (fname.size() < 4)
 		error("TeBezierCurve::loadBin fname %s is too short", fname.c_str());
 	setName(fname.substr(0, fname.size() - 4));
 
 	// Load position / rotation / size
-	Te3DObject2::deserialize(file, *this, false);
+	Te3DObject2::deserialize(*file, *this, false);
 	// Then it resets them?
 	setPosition(TeVector3f32());
 	setRotation(TeQuaternion());
 	setSize(TeVector3f32(1, 1, 1));
 
 	_lengthNeedsUpdate = true;
-	uint32 npoints = file.readUint32LE();
+	uint32 npoints = file->readUint32LE();
 	if (npoints > 1000000)
 		error("TeBezierCurve::loadBin improbable number of control ponts %d", npoints);
 
 	for (uint i = 0; i < npoints; i++) {
 		TeVector3f32 vec;
-		TeVector3f32::deserialize(file, vec);
+		TeVector3f32::deserialize(*file, vec);
 		_controlPoints.push_back(vec);
 	}
 }
diff --git a/engines/tetraedge/te/te_bezier_curve.h b/engines/tetraedge/te/te_bezier_curve.h
index 704d33f513c..d703e470970 100644
--- a/engines/tetraedge/te/te_bezier_curve.h
+++ b/engines/tetraedge/te/te_bezier_curve.h
@@ -26,6 +26,7 @@
 
 #include "tetraedge/te/te_3d_object2.h"
 #include "tetraedge/te/te_references_counter.h"
+#include "tetraedge/tetraedge.h"
 
 namespace Tetraedge {
 
@@ -50,7 +51,7 @@ public:
 
 	static void serialize(Common::WriteStream &stream, const TeBezierCurve &curve);
 	static void deserialize(Common::ReadStream &stream, TeBezierCurve &curve);
-	void loadBin(const Common::Path &path);
+	void loadBin(TetraedgeFSNode &node);
 
 	const Common::Array<TeVector3f32> &controlPoints() { return _controlPoints; }
 	uint numIterations() const { return _numIterations; }
diff --git a/engines/tetraedge/te/te_camera.cpp b/engines/tetraedge/te/te_camera.cpp
index 35fe552b54e..11b82cfe8c0 100644
--- a/engines/tetraedge/te/te_camera.cpp
+++ b/engines/tetraedge/te/te_camera.cpp
@@ -169,8 +169,8 @@ void TeCamera::loadXml(const Common::Path &path) {
 	setName(path.baseName());
 	_projectionMatrixType = 3;
 	TeCore *core = g_engine->getCore();
-	Common::Path cameraPath = core->findFile(path);
-	if (!Common::File::exists(cameraPath)) {
+	TetraedgeFSNode cameraNode = core->findFile(path);
+	if (!cameraNode.isReadable()) {
 		//
 		// WORKAROUND: scenes/A3_Village/34015 has Camera34010, not 34015
 		//
@@ -179,17 +179,17 @@ void TeCamera::loadXml(const Common::Path &path) {
 		if (pos != Common::String::npos) {
 			spath.replace(pos + 4, 1, "0");
 		}
-		cameraPath = core->findFile(Common::Path(spath, '/'));
+		cameraNode = core->findFile(Common::Path(spath, '/'));
 	}
-	if (!Common::File::exists(cameraPath)) {
-		warning("Can't open camera data %s", path.toString(Common::Path::kNativeSeparator).c_str());
+	if (!cameraNode.isReadable()) {
+		warning("Can't open camera data %s", cameraNode.toString().c_str());
 	}
 	TeCameraXmlParser parser;
 	parser._cam = this;
-	if (!parser.loadFile(cameraPath))
-		error("TeCamera::loadXml: can't load file %s", cameraPath.toString(Common::Path::kNativeSeparator).c_str());
+	if (!cameraNode.loadXML(parser))
+		error("TeCamera::loadXml: can't load file %s", cameraNode.toString().c_str());
 	if (!parser.parse())
-		error("TeCamera::loadXml: error parsing %s", cameraPath.toString(Common::Path::kNativeSeparator).c_str());
+		error("TeCamera::loadXml: error parsing %s", cameraNode.toString().c_str());
 }
 
 void TeCamera::orthogonalParams(float left, float right, float top, float bottom) {
diff --git a/engines/tetraedge/te/te_core.cpp b/engines/tetraedge/te/te_core.cpp
index 6f002c05c57..ffbad1ede04 100644
--- a/engines/tetraedge/te/te_core.cpp
+++ b/engines/tetraedge/te/te_core.cpp
@@ -24,6 +24,7 @@
 #include "common/debug.h"
 #include "common/config-manager.h"
 #include "common/language.h"
+#include "common/tokenizer.h"
 
 #include "tetraedge/te/te_core.h"
 
@@ -74,10 +75,6 @@ void TeCore::create() {
 	_resourcesRoot = Common::FSDirectory(resNode, 5, false, false, true);
 }
 
-Common::FSNode TeCore::getFSNode(const Common::Path &path) const {
-	return Common::FSNode(Common::Path(_resourcesRoot.getFSNode().getPath()).join(path));
-}
-
 TeICodec *TeCore::createVideoCodec(const Common::String &extn) {
 	// The original engine has more formats and even checks for alpha maps,
 	// but it never uses them.
@@ -98,8 +95,8 @@ TeICodec *TeCore::createVideoCodec(const Common::String &extn) {
 	return nullptr;
 }
 
-TeICodec *TeCore::createVideoCodec(const Common::Path &path) {
-	const Common::String filename = path.baseName();
+TeICodec *TeCore::createVideoCodec(const TetraedgeFSNode &node) {
+	const Common::String filename = node.getPath().baseName();
 	if (!filename.contains('.'))
 		return nullptr;
 	Common::String extn = filename.substr(filename.findLastOf('.') + 1);
@@ -146,109 +143,92 @@ bool TeCore::onActivityTrackingAlarm() {
 	error("TODO: Implement TeCore::onActivityTrackingAlarm");
 }
 
-Common::Path TeCore::findFile(const Common::Path &path) const {
-	if (Common::File::exists(path))
-		return path;
+static bool _checkFileFlag(const Common::String &fname, const Common::HashMap<Common::String, bool, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> &activeTags) {
+	Common::StringTokenizer tokenizer(fname, "-");
+	while(!tokenizer.empty())
+		if (activeTags.getValOrDefault(tokenizer.nextToken(), false))
+			return true;
+	return false;
+}
 
-	Common::String fname = path.baseName();
+static void _findFileRecursively(const TetraedgeFSNode &parent,
+				 const Common::HashMap<Common::String, bool, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> &activeTags,
+				 const Common::String &fname,
+				 Common::Array<TetraedgeFSNode> &foundFiles,
+				 int maxDepth) {
+	if (parent.getChild(Common::Path(fname, '/')).exists()) {
+		foundFiles.push_back(parent.getChild(Common::Path(fname, '/')));
+	}
+
+	if (maxDepth <= 0)
+		return;
+
+	TetraedgeFSList list;
+	if (!parent.getChildren(list))
+		return;
+
+	for (TetraedgeFSList::const_iterator it = list.begin(); it != list.end(); it++)
+		if (_checkFileFlag(it->getName(), activeTags))
+			_findFileRecursively(*it, activeTags, fname, foundFiles, maxDepth - 1);
+}
+
+TetraedgeFSNode TeCore::findFile(const Common::Path &path, bool quiet) const {
+	Common::Array<TetraedgeFSNode> dirNodes;
+	const Common::Path dir = path.getParent();
+
+	TetraedgeFSNode node;
+
+	const Common::Array<Common::Archive *> &roots = g_engine->getRootArchives();
+	for (Common::Archive *const archive : roots) {
+		TetraedgeFSNode archiveNode(archive);
+		node = archiveNode.getChild(path);
+		if (node.exists())
+			return node;
+		dirNodes.push_back(archiveNode.getChild(dir));
+	}
+
+	Common::String fname = path.getLastComponent().toString();
 
 	// Slight HACK: Remove 'comments' used to specify animated pngs
 	if (fname.contains('#'))
 		fname = fname.substr(0, fname.find('#'));
-	const Common::Path dir = path.getParent();
 
-	static const Common::Path pathSuffixes[] = {
-		"", // no suffix
-		"PC-MacOSX",
-		"PC-PS3-Android-MacOSX",
-		"PC-MacOSX-Android-iPhone-iPad",
-		"PC-MacOSX-Android-iPhone-iPad/HD",
-		"PC-Android-MacOSX-iPhone-iPad",
-		"PC-MacOSX-Xbox360-PS3",
-		"PC-MacOSX-PS3-Xbox360",
-		"PC-MacOSX-Xbox360-PS3/PC-MacOSX",
-		"PC-MacOSX-MacOSXAppStore-Android-iPhone-iPad",
-		"PC-MacOSX-MacOSXAppStore-Xbox360-Android-iPad-iPhone",
-		"Android-iPhone-iPad-PC-MacOSX",
-		"Android-MacOSX",
-		"Full",
-		"Part1-Full",
-		"Part2-Full-Part1",
-		"Part3-Full-Part1",
-		"HD",
-		"HD/PC-MacOSX-Xbox360-PS3",
-		"PC-PS3-Android-MacOSX-iPhone-iPad",	// iOS Syb 1
-		"Android-iPhone-iPad",					// iOS Syb 1
-		"Android-iPhone-iPad/HD",				// iOS Syb 1
-		"HD/Android-iPhone-iPad",				// iOS Syb 1
-		"iPhone-iPad",							// iOS Syb 1
-		"iPhone-iPad/HD",						// iOS Syb 1
-		"iPhone-iPad/HD/Freemium",				// iOS Syb 1
-		"Android-MacOSX-iPhone-iPad",			// iOS Syb 1
-		"Freemium-BUKAFree/HD",					// iOS Syb 1
-		"Part3-Full",							// iOS Syb 1 paid
-		"DefaultDistributor-Freemium",			// iOS Syb 1 paid
-		"iPhone-iPad/DefaultDistributor",		// iOS Syb 1 paid
-		"Android-iPhone-iPad/iPhone-iPad",		// iOS Syb 2
-		"PC-MacOSX-Android-iPhone-iPad",		// iOS Syb 2
-		"Part2-Full",							// Amerzone
-		"Part3-Full",							// Amerzone
-		"Full/HD",								// Amerzone
-		"Part1-Full/PC-MacOSX/DefaultDistributor", // Amerzone
-		"Part2-Full/PC-MacOSX/DefaultDistributor", // Amerzone
-		"Part3-Full/PC-MacOSX/DefaultDistributor", // Amerzone
-		"Part1-Full/iPhone-iPad-Android", // Amerzone
-		"Part2-Full/iPhone-iPad-Android", // Amerzone
-		"Part3-Full/iPhone-iPad-Android", // Amerzone
-		"Part1-Part2-Part3-Full/HD",			// Amerzone
-		"Part1-Part2-Part3-Full",				// Amerzone
-		"Part1-Full/HD",						// Amerzone
-		"Part2-Full/HD",						// Amerzone
-		"Part3-Full/HD",						// Amerzone
-	};
-
-	static const Common::Path langs[] = {
-		Common::Path(language()),
-		"en",
-		"de-es-fr-it-en",
-		"en-es-fr-de-it",
-		"es-en-fr-de-it",
-		"de-en-es-fr-it",
-		""
-	};
-
-	// Note: the audio files for a few videos have a weird path
-	// structure where the language is first, followed by some other
-	// part names, followed by the file.
-	// Dialogs have part stuff followed by lang, so we have to try
-	// adding language before *and* after the suffix.
-
-	for (int langtype = 0; langtype < ARRAYSIZE(langs); langtype++) {
-		const Common::Path &lang = langs[langtype];
-		for (int i = 0; i < ARRAYSIZE(pathSuffixes); i++) {
-			Common::Path suffix = pathSuffixes[i];
-
-			Common::Path testPath = dir;
-			if (!suffix.empty())
-				testPath.joinInPlace(suffix);
-			if (!lang.empty())
-				testPath.joinInPlace(lang);
-			testPath.joinInPlace(fname);
-			if (Common::File::exists(testPath))
-				return testPath;
-
-			// also try the other way around
-			if (!lang.empty() && !suffix.empty()) {
-				testPath = dir.join(lang).joinInPlace(suffix).join(fname);
-				if (Common::File::exists(testPath))
-					return testPath;
+	Common::HashMap<Common::String, bool, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> activeFlags;
+
+	for (Common::HashMap<Common::String, Common::String, Common::CaseSensitiveString_Hash, Common::CaseSensitiveString_EqualTo>::const_iterator it = _fileSystemFlags.begin();
+	     it != _fileSystemFlags.end(); it++)
+		activeFlags[it->_value] = true;
+
+	// This is to keep behivour changes small when we migrated from old system.
+	// I'm not sure if it's needed
+	// TODO: Figure out what to do with this. Right now we set flag
+	// to "SD" but use assets from "HD". This seems to give the best
+	// results but is fundamentally wrong.
+	activeFlags.erase("SD");
+	activeFlags["HD"] = true;
+
+	for (uint dirNode = 0; dirNode < dirNodes.size(); dirNode++) {
+		Common::Array<TetraedgeFSNode> foundFiles;
+		_findFileRecursively(dirNodes[dirNode], activeFlags, fname, foundFiles, 5);
+		if (foundFiles.empty())
+			continue;
+		TetraedgeFSNode best = foundFiles[0];
+		int bestDepth = best.getDepth();
+		for (uint i = 1; i < foundFiles.size(); i++) {
+			int depth = foundFiles[i].getDepth();
+			if (depth > bestDepth) {
+				bestDepth = depth;
+				best = foundFiles[i];
 			}
 		}
+
+		return best;
 	}
 
 	// Didn't find it at all..
-	debug("TeCore::findFile Searched but didn't find %s", path.toString(Common::Path::kNativeSeparator).c_str());
-	return path;
+	if (!quiet)
+		debug("TeCore::findFile Searched but didn't find %s", path.toString().c_str());
+	return TetraedgeFSNode(nullptr, path);
 }
 
 } // end namespace Tetraedge
diff --git a/engines/tetraedge/te/te_core.h b/engines/tetraedge/te/te_core.h
index 360b333b913..1008b4e06c8 100644
--- a/engines/tetraedge/te/te_core.h
+++ b/engines/tetraedge/te/te_core.h
@@ -40,7 +40,7 @@ public:
 
 	void addLoc(TeILoc *loc);
 	void create();
-	TeICodec *createVideoCodec(const Common::Path &path);
+	TeICodec *createVideoCodec(const TetraedgeFSNode &node);
 	TeICodec *createVideoCodec(const Common::String &extn);
 	const Common::String &fileFlagSystemFlag(const Common::String &name) const;
 	bool fileFlagSystemFlagsContains(const Common::String &name) const;
@@ -58,8 +58,8 @@ public:
 	// Note: this is not in the original, but it's not clear how the original
 	// adds things like "PC-MacOSX" to the path, and there is not clear logic
 	// to them, so here we are.
-	Common::Path findFile(const Common::Path &path) const;
-	Common::FSNode getFSNode(const Common::Path &path) const;
+	TetraedgeFSNode findFile(const Common::Path &path, bool quiet = false) const;
+	TetraedgeFSNode getFSNode(const Common::Path &path) const;
 
 	bool _coreNotReady;
 
diff --git a/engines/tetraedge/te/te_font2.cpp b/engines/tetraedge/te/te_font2.cpp
index 2df962d1051..68c8cbcb650 100644
--- a/engines/tetraedge/te/te_font2.cpp
+++ b/engines/tetraedge/te/te_font2.cpp
@@ -42,42 +42,46 @@ bool TeFont2::load(const Common::Path &path) {
 		return true; // already open
 
 	TeCore *core = g_engine->getCore();
-	Common::Path fontPath = core->findFile(path);
+	TetraedgeFSNode node = core->findFile(path);
+	return load(node);
+}
+
+bool TeFont2::load(const TetraedgeFSNode &node) {
+	const Common::Path path = node.getPath();
 
 	unload();
 	setAccessName(path);
 	_loadedPath = path;
 
-	if (!Common::File::exists(fontPath)) {
-		warning("TeFont2::load: Can't read from %s", path.toString(Common::Path::kNativeSeparator).c_str());
+	if (!node.exists()) {
+		warning("TeFont2::load: Can't read from %s", node.toString().c_str());
 		return false;
 	}
 
-	Common::File file;
-	file.open(fontPath);
+	Common::ScopedPtr<Common::SeekableReadStream> file(node.createReadStream());
 
-	if (!Te3DObject2::loadAndCheckFourCC(file, "TESF")) {
-		warning("TeFont2::load: Invalid magic in %s", path.toString(Common::Path::kNativeSeparator).c_str());
+	if (!Te3DObject2::loadAndCheckFourCC(*file, "TESF")) {
+		warning("TeFont2::load: Invalid magic in %s", node.toString().c_str());
 		return false;
 	}
 
-	_numChars = file.readUint32LE();
+	_numChars = file->readUint32LE();
 	if (_numChars > 65535)
 		error("TeFont2::load: improbable number of char points %d", _numChars);
-	TeVector2s32::deserialize(file, _somePt);
-	TeVector3f32::deserialize(file, _someVec);
-	_hasKernData = (file.readByte() != 0);
+	TeVector2s32::deserialize(*file, _somePt);
+	TeVector3f32::deserialize(*file, _someVec);
+	_hasKernData = (file->readByte() != 0);
 	if (_hasKernData) {
-		uint32 numKernData = file.readUint32LE();
+		uint32 numKernData = file->readUint32LE();
 		if (numKernData > 10000)
 			error("TeFont2::load: improbable number of kerning points %d", numKernData);
 		for (uint32 i = 0; i < numKernData; i++) {
 			KernChars kc;
 			TeVector3f32 vec;
-			kc._c1 = file.readUint32LE();
-			kc._c2 = file.readUint32LE();
-			vec.x() = file.readFloatLE();
-			vec.y() = file.readFloatLE();
+			kc._c1 = file->readUint32LE();
+			kc._c2 = file->readUint32LE();
+			vec.x() = file->readFloatLE();
+			vec.y() = file->readFloatLE();
 			_kernData[kc] = vec;
 			//debug("KernChars: '%c'-'%c' (%.2f, %.2f)", (char)kc._c1, (char)kc._c2, vec.x(), vec.y());
 		}
@@ -85,19 +89,19 @@ bool TeFont2::load(const Common::Path &path) {
 
 	for (uint32 i = 0; i < _numChars; i++) {
 		GlyphData2 g;
-		g._xSz = file.readFloatLE();
-		g._ySz = file.readFloatLE();
+		g._xSz = file->readFloatLE();
+		g._ySz = file->readFloatLE();
 		_maxHeight = MAX(_maxHeight, g._ySz);
-		g._xOff = file.readFloatLE();
-		g._yOff = file.readFloatLE();
-		g._xAdvance = file.readFloatLE();
+		g._xOff = file->readFloatLE();
+		g._yOff = file->readFloatLE();
+		g._xAdvance = file->readFloatLE();
 		// TODO: What are these other floats?
 		for (uint j = 0; j < 3; j++)
-			g._floats[j] = file.readFloatLE();
-		g._vec.x() = file.readFloatLE();
-		g._vec.y() = file.readFloatLE();
+			g._floats[j] = file->readFloatLE();
+		g._vec.x() = file->readFloatLE();
+		g._vec.y() = file->readFloatLE();
 		_glyphs.push_back(g);
-		uint32 charNo = file.readUint32LE();
+		uint32 charNo = file->readUint32LE();
 		_uintArray.push_back(charNo);
 		/*
 		if (i >= 35 && i <= 127)
@@ -107,8 +111,8 @@ bool TeFont2::load(const Common::Path &path) {
 		*/
 	}
 
-	if (!_texture.load(file, "png")) {
-		warning("Invalid png data in %s", path.toString(Common::Path::kNativeSeparator).c_str());
+	if (!_texture.load(*file, "png")) {
+		warning("Invalid png data in %s", node.toString().c_str());
 		return false;
 	}
 
diff --git a/engines/tetraedge/te/te_font2.h b/engines/tetraedge/te/te_font2.h
index 7a8343d47a3..eb395f5fd6b 100644
--- a/engines/tetraedge/te/te_font2.h
+++ b/engines/tetraedge/te/te_font2.h
@@ -71,6 +71,7 @@ public:
 	virtual ~TeFont2();
 
 	bool load(const Common::Path &path);
+	bool load(const TetraedgeFSNode &node);
 	void unload();
 
 	Graphics::Font *getAtSize(uint size) override;
diff --git a/engines/tetraedge/te/te_font3.cpp b/engines/tetraedge/te/te_font3.cpp
index fb796f25ad7..0a95c1b6edc 100644
--- a/engines/tetraedge/te/te_font3.cpp
+++ b/engines/tetraedge/te/te_font3.cpp
@@ -39,14 +39,14 @@ Graphics::Font *TeFont3::getAtSize(uint size) {
 	if (_fonts.contains(size))
 		return _fonts.getVal(size);
 
-	if (!_fontFile.isOpen())
+	if (!_fontFile)
 		load(getAccessName());
 
-	if (!_fontFile.isOpen())
+	if (!_fontFile)
 		error("TeFont3::: Couldn't open font file %s.", getAccessName().toString(Common::Path::kNativeSeparator).c_str());
 
-	_fontFile.seek(0);
-	Graphics::Font *newFont = Graphics::loadTTFFont(&_fontFile, DisposeAfterUse::NO, size, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+	_fontFile->seek(0);
+	Graphics::Font *newFont = Graphics::loadTTFFont(_fontFile.get(), DisposeAfterUse::NO, size, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
 	if (!newFont) {
 		error("TeFont3::: Couldn't load font %s at size %d.", _loadedPath.toString(Common::Path::kNativeSeparator).c_str(), size);
 	}
@@ -55,24 +55,30 @@ Graphics::Font *TeFont3::getAtSize(uint size) {
 }
 
 bool TeFont3::load(const Common::Path &path) {
+	if (_loadedPath == path && _fontFile)
+		return true; // already open
+
 	TeCore *core = g_engine->getCore();
-	const Common::Path fontPath = core->findFile(path);
-	if (_loadedPath == fontPath && _fontFile.isOpen())
+	TetraedgeFSNode node = core->findFile(path);
+	return load(node);
+}
+
+bool TeFont3::load(const TetraedgeFSNode &node) {
+	const Common::Path fontPath = node.getPath();
+	if (_loadedPath == fontPath && _fontFile)
 		return true; // already open
 
 	setAccessName(fontPath);
 	_loadedPath = fontPath;
 
-	if (!Common::File::exists(fontPath)) {
-		warning("TeFont3::load: Can't find %s", path.toString(Common::Path::kNativeSeparator).c_str());
+	if (!node.exists()) {
+		warning("TeFont3::load: Can't find %s", node.toString().c_str());
 		return false;
 	}
 
-	if (_fontFile.isOpen())
-		_fontFile.close();
-
-	if (!_fontFile.open(fontPath)) {
-		warning("TeFont3::load: can't open %s", path.toString(Common::Path::kNativeSeparator).c_str());
+	_fontFile.reset(node.createReadStream());
+	if (!_fontFile) {
+		warning("TeFont3::load: can't open %s", node.toString().c_str());
 		return false;
 	}
 	return true;
@@ -83,7 +89,7 @@ void TeFont3::unload() {
 		delete entry._value;
 	}
 	_fonts.clear();
-	_fontFile.close();
+	_fontFile.reset();
 }
 
 } // end namespace Tetraedge
diff --git a/engines/tetraedge/te/te_font3.h b/engines/tetraedge/te/te_font3.h
index 980278d0569..39551596dd2 100644
--- a/engines/tetraedge/te/te_font3.h
+++ b/engines/tetraedge/te/te_font3.h
@@ -52,6 +52,7 @@ public:
 	virtual ~TeFont3();
 
 	bool load(const Common::Path &path);
+	bool load(const TetraedgeFSNode &node);
 	void unload();
 
 private:
@@ -61,7 +62,7 @@ private:
 	}
 
 	Graphics::Font *getAtSize(uint size) override;
-	Common::File _fontFile;
+	Common::ScopedPtr<Common::SeekableReadStream> _fontFile;
 	Common::HashMap<uint, Graphics::Font *> _fonts;
 	Common::Path _loadedPath;
 	Common::HashMap<uint, TeIntrusivePtr<Te3DTexture>> _fontSizeData;
diff --git a/engines/tetraedge/te/te_free_move_zone.cpp b/engines/tetraedge/te/te_free_move_zone.cpp
index be8885e4970..007c828856b 100644
--- a/engines/tetraedge/te/te_free_move_zone.cpp
+++ b/engines/tetraedge/te/te_free_move_zone.cpp
@@ -182,26 +182,31 @@ void TeFreeMoveZone::buildAStar() {
 }
 
 bool TeFreeMoveZone::loadAStar(const Common::Path &path, const TeVector2s32 &size) {
-	Common::Path aStarPath = g_engine->getCore()->findFile(path);
-	Common::File file;
-	if (!file.open(aStarPath)) {
-		warning("[TeFreeMoveZone::loadAStar] Can't open file : %s.", path.toString(Common::Path::kNativeSeparator).c_str());
+	TetraedgeFSNode node = g_engine->getCore()->findFile(path);
+	Common::ScopedPtr<Common::SeekableReadStream> file;
+	if (!node.isReadable()) {
+		warning("[TeFreeMoveZone::loadAStar] Can't open file : %s.", path.toString().c_str());
+		return false;
+	}
+	file.reset(node.createReadStream());
+	if (!file) {
+		warning("[TeFreeMoveZone::loadAStar] Can't open file : %s.", path.toString().c_str());
 		return false;
 	}
 	TeVector2s32 readSize;
-	readSize.deserialize(file, readSize);
+	readSize.deserialize(*file, readSize);
 	if (size != readSize) {
 		warning("[TeFreeMoveZone::loadAStar] Wrong file : %s.", path.toString(Common::Path::kNativeSeparator).c_str());
 		return false;
 	}
-	uint32 bytes = file.readUint32LE();
+	uint32 bytes = file->readUint32LE();
 	if (bytes > 100000)
 		error("Improbable size %d for compressed astar data", bytes);
 
 	unsigned long decompBytes = size._x * size._y;
 	byte *buf = new byte[bytes];
 	byte *outBuf = new byte[decompBytes];
-	file.read(buf, bytes);
+	file->read(buf, bytes);
 	bool result = Common::inflateZlib(outBuf, &decompBytes, buf, bytes);
 	delete [] buf;
 	if (result) {
@@ -608,15 +613,14 @@ TeActZone *TeFreeMoveZone::isInZone(const TeVector3f32 &pt) {
 bool TeFreeMoveZone::loadBin(const Common::Path &path, const Common::Array<TeBlocker> *blockers,
 		const Common::Array<TeRectBlocker> *rectblockers, const Common::Array<TeActZone> *actzones,
 		const TeVector2f32 &gridSize) {
-	Common::Path binPath = g_engine->getCore()->findFile(path);
-	if (!Common::File::exists(binPath)) {
-		warning("[TeFreeMoveZone::loadBin] Can't open file : %s.", binPath.baseName().c_str());
+	TetraedgeFSNode node = g_engine->getCore()->findFile(path);
+	if (!node.isReadable()) {
+		warning("[TeFreeMoveZone::loadBin] Can't open file : %s.", node.getName().c_str());
 		return false;
 	}
 	_aszGridPath = path.append(".aszgrid");
-	Common::File file;
-	file.open(binPath);
-	return loadBin(file, blockers, rectblockers, actzones, gridSize);
+	Common::ScopedPtr<Common::SeekableReadStream> file(node.createReadStream());
+	return loadBin(*file, blockers, rectblockers, actzones, gridSize);
 }
 
 bool TeFreeMoveZone::loadBin(Common::ReadStream &stream, const Common::Array<TeBlocker> *blockers,
diff --git a/engines/tetraedge/te/te_i_codec.h b/engines/tetraedge/te/te_i_codec.h
index 29909d34290..bb2f9bb9afe 100644
--- a/engines/tetraedge/te/te_i_codec.h
+++ b/engines/tetraedge/te/te_i_codec.h
@@ -36,7 +36,7 @@ public:
 	TeICodec() {};
 
 	virtual ~TeICodec() {};
-	virtual bool load(const Common::Path &path) = 0;
+	virtual bool load(const TetraedgeFSNode &node) = 0;
 	virtual uint width() = 0;
 	virtual uint height() = 0;
 	virtual int nbFrames() = 0;
diff --git a/engines/tetraedge/te/te_image.cpp b/engines/tetraedge/te/te_image.cpp
index 95be9a7b860..f3a77f789cd 100644
--- a/engines/tetraedge/te/te_image.cpp
+++ b/engines/tetraedge/te/te_image.cpp
@@ -101,11 +101,11 @@ bool TeImage::isExtensionSupported(const Common::Path &path) {
 	error("TODO: Implement TeImage::isExtensionSupported");
 }
 
-bool TeImage::load(const Common::Path &path) {
+bool TeImage::load(const TetraedgeFSNode &node) {
 	TeCore *core = g_engine->getCore();
-	TeICodec *codec = core->createVideoCodec(path);
-	if (!Common::File::exists(path) || !codec->load(path)) {
-		warning("TeImage::load: Failed to load %s.", path.toString(Common::Path::kNativeSeparator).c_str());
+	TeICodec *codec = core->createVideoCodec(node);
+	if (!node.exists() || !codec->load(node)) {
+		warning("TeImage::load: Failed to load %s.", node.toString().c_str());
 		delete codec;
 		return false;
 	}
@@ -114,7 +114,7 @@ bool TeImage::load(const Common::Path &path) {
 	createImg(codec->width(), codec->height(), nullpal, codec->imageFormat(), codec->width(), codec->height());
 
 	if (!codec->update(0, *this)) {
-		error("TeImage::load: Failed to update from %s.", path.toString(Common::Path::kNativeSeparator).c_str());
+		error("TeImage::load: Failed to update from %s.", node.toString().c_str());
 	}
 	delete codec;
 	return true;
diff --git a/engines/tetraedge/te/te_image.h b/engines/tetraedge/te/te_image.h
index 8266d410244..c4332384389 100644
--- a/engines/tetraedge/te/te_image.h
+++ b/engines/tetraedge/te/te_image.h
@@ -35,6 +35,8 @@
 #include "tetraedge/te/te_vector2s32.h"
 #include "tetraedge/te/te_resource.h"
 
+#include "tetraedge/tetraedge.h"
+
 namespace Tetraedge {
 
 class TeImage : public TeResource, public Graphics::ManagedSurface {
@@ -75,7 +77,7 @@ public:
 	void fill(byte r, byte g, byte b, byte a);
 	void getBuff(uint x, uint y, byte *pout, uint w, uint h);
 	bool isExtensionSupported(const Common::Path &path);
-	bool load(const Common::Path &path);
+	bool load(const TetraedgeFSNode &node);
 	bool load(Common::SeekableReadStream &stream, const Common::String &type);
 	bool save(const Common::Path &path, enum SaveType type);
 	int serialize(Common::WriteStream &stream);
diff --git a/engines/tetraedge/te/te_images_sequence.cpp b/engines/tetraedge/te/te_images_sequence.cpp
index fb9dfddd047..8b2fe6b641a 100644
--- a/engines/tetraedge/te/te_images_sequence.cpp
+++ b/engines/tetraedge/te/te_images_sequence.cpp
@@ -45,30 +45,26 @@ bool TeImagesSequence::matchExtension(const Common::String &extn) {
 	return extn == "anim" || extn == "animcached";
 }
 
-static bool compareNodes(const Common::FSNode &left, const Common::FSNode &right) {
+static bool compareNodes(const TetraedgeFSNode &left, const TetraedgeFSNode &right) {
 	return left.getPath().toString('/') < right.getPath().toString('/');
 }
 
-bool TeImagesSequence::load(const Common::Path &directory) {
-	Common::FSNode dir = g_engine->getCore()->getFSNode(directory);
-
-	const Common::String path = directory.toString('/');
+bool TeImagesSequence::load(const TetraedgeFSNode &dir) {
 	if (!dir.isDirectory()) {
-		warning("TeImagesSequence::load:: not a directory %s", directory.toString(Common::Path::kNativeSeparator).c_str());
+		warning("TeImagesSequence::load:: not a directory %s", dir.toString().c_str());
 		return false;
 	}
 
-	Common::FSList children;
+	TetraedgeFSList children;
 	if (!dir.getChildren(children, Common::FSNode::kListFilesOnly) || children.empty()) {
-		warning("TeImagesSequence::load:: couldn't get children of %s", directory.toString(Common::Path::kNativeSeparator).c_str());
+		warning("TeImagesSequence::load:: couldn't get children of %s", dir.toString().c_str());
 		return false;
 	}
 
 	Common::sort(children.begin(), children.end(), compareNodes);
-	if (!SearchMan.hasArchive(path))
-		SearchMan.addDirectory(path, directory);
+	dir.maybeAddToSearchMan();
 
-	for (Common::FSNode &child : children) {
+	for (TetraedgeFSNode &child : children) {
 		const Common::String fileName = child.getName();
 
 		if (fileName.size() <= 10 || fileName.substr(fileName.size() - 7) != "fps.png")
@@ -84,7 +80,7 @@ bool TeImagesSequence::load(const Common::Path &directory) {
 
 		Common::SeekableReadStream *stream = child.createReadStream();
 		if (!stream) {
-			warning("TeImagesSequence::load can't open %s", child.getPath().toString(Common::Path::kNativeSeparator).c_str());
+			warning("TeImagesSequence::load can't open %s", child.toString().c_str());
 			continue;
 		}
 
@@ -93,7 +89,7 @@ bool TeImagesSequence::load(const Common::Path &directory) {
 		if (!_width || (_width < 100 && _height < 100)) {
 			Image::PNGDecoder png;
 			if (!png.loadStream(*stream)) {
-				warning("Image sequence failed to load png %s", child.getPath().toString(Common::Path::kNativeSeparator).c_str());
+				warning("Image sequence failed to load png %s", child.toString().c_str());
 				delete stream;
 				return false;
 			}
diff --git a/engines/tetraedge/te/te_images_sequence.h b/engines/tetraedge/te/te_images_sequence.h
index 92b004c1f12..0f9f1f1a402 100644
--- a/engines/tetraedge/te/te_images_sequence.h
+++ b/engines/tetraedge/te/te_images_sequence.h
@@ -37,7 +37,7 @@ public:
 	TeImagesSequence();
 	virtual ~TeImagesSequence();
 
-	virtual bool load(const Common::Path &path) override;
+	virtual bool load(const TetraedgeFSNode &node) override;
 	virtual uint width() override { return _width; }
 	virtual uint height() override { return _height; }
 	virtual int nbFrames() override { return _files.size(); }
@@ -63,7 +63,7 @@ private:
 	float _frameRate;
 	uint _width;
 	uint _height;
-	Common::Array<Common::FSNode> _files;
+	Common::Array<TetraedgeFSNode> _files;
 	Common::Array<Graphics::ManagedSurface *> _cachedSurfaces;
 	uint _curFrame;
 };
diff --git a/engines/tetraedge/te/te_interpolation.cpp b/engines/tetraedge/te/te_interpolation.cpp
index 52a5db2c839..8cc916953c3 100644
--- a/engines/tetraedge/te/te_interpolation.cpp
+++ b/engines/tetraedge/te/te_interpolation.cpp
@@ -37,12 +37,12 @@ void TeInterpolation::load(Common::ReadStream &stream) {
 		_array[i] = stream.readFloatLE();
 }
 
-void TeInterpolation::load(Common::Path &path) {
-	Common::File f;
-	if (!f.open(path))
-		error("Couldn't open %s", path.toString(Common::Path::kNativeSeparator).c_str());
+void TeInterpolation::load(TetraedgeFSNode &node) {
+	Common::ScopedPtr<Common::SeekableReadStream> f(node.createReadStream());
+	if (!f)
+		error("Couldn't open %s", node.toString().c_str());
 
-	load(f);
+	load(*f);
 }
 
 
diff --git a/engines/tetraedge/te/te_interpolation.h b/engines/tetraedge/te/te_interpolation.h
index a9b154ca7c8..4916a27dc5b 100644
--- a/engines/tetraedge/te/te_interpolation.h
+++ b/engines/tetraedge/te/te_interpolation.h
@@ -26,6 +26,8 @@
 #include "common/stream.h"
 #include "common/fs.h"
 
+#include "tetraedge/tetraedge.h"
+
 namespace Tetraedge {
 
 class TeInterpolation {
@@ -33,7 +35,7 @@ public:
 	TeInterpolation();
 
 	void load(Common::ReadStream &stream);
-	void load(Common::Path &path);
+	void load(TetraedgeFSNode &node);
 
 	// Note: this function is not in the original but simplifies
 	// the code for TeCurveAnim2 a lot.
diff --git a/engines/tetraedge/te/te_lua_gui.cpp b/engines/tetraedge/te/te_lua_gui.cpp
index 7fce15a0d61..58d6f9e5dfb 100644
--- a/engines/tetraedge/te/te_lua_gui.cpp
+++ b/engines/tetraedge/te/te_lua_gui.cpp
@@ -179,9 +179,12 @@ TeSpriteLayout *TeLuaGUI::spriteLayoutChecked(const Common::String &name) {
 
 bool TeLuaGUI::load(const Common::Path &subPath) {
 	TeCore *core = g_engine->getCore();
+	return load(core->findFile(subPath));
+}
 
+bool TeLuaGUI::load(const TetraedgeFSNode &node) {
 	unload();
-	_scriptPath = core->findFile(subPath);
+	_scriptPath = node.getPath();
 	// Not the same as original, we abstract the search logic a bit.
 	_luaContext.setGlobal("Pixel", 0);
 	_luaContext.setGlobal("Percent", 1);
@@ -210,7 +213,7 @@ bool TeLuaGUI::load(const Common::Path &subPath) {
 	_luaContext.registerCFunction("TeVideoLayout", spriteLayoutBindings);
 	_luaContext.setInRegistry("__TeLuaGUIThis", this);
 	_luaScript.attachToContext(&_luaContext);
-	_luaScript.load(_scriptPath);
+	_luaScript.load(node);
 	_luaScript.execute();
 	_luaScript.unload();
 	_loaded = true;
diff --git a/engines/tetraedge/te/te_lua_gui.h b/engines/tetraedge/te/te_lua_gui.h
index d55b1fce1e2..0163cd23f6f 100644
--- a/engines/tetraedge/te/te_lua_gui.h
+++ b/engines/tetraedge/te/te_lua_gui.h
@@ -78,6 +78,7 @@ public:
 	TeSpriteLayout *spriteLayoutChecked(const Common::String &name);
 
 	bool load(const Common::Path &subPath);
+	bool load(const TetraedgeFSNode &node);
 	void unload();
 
 	TeVariant value(const Common::String &key);
diff --git a/engines/tetraedge/te/te_lua_script.cpp b/engines/tetraedge/te/te_lua_script.cpp
index 8987164a269..e79a4d7abc5 100644
--- a/engines/tetraedge/te/te_lua_script.cpp
+++ b/engines/tetraedge/te/te_lua_script.cpp
@@ -37,7 +37,7 @@ void TeLuaScript::attachToContext(TeLuaContext *context) {
 
 void TeLuaScript::execute() {
 	if (_luaContext) {
-		//debug("TeLuaScript::execute %s", _scriptNode.getPath().c_str());
+		//debug("TeLuaScript::execute %s", _scriptNode.toString().c_str());
 		lua_State *state = _luaContext->luaState();
 		if (state) {
 			TeLuaThread *thread = TeLuaThread::create(_luaContext);
@@ -50,7 +50,7 @@ void TeLuaScript::execute() {
 
 void TeLuaScript::execute(const Common::String &fname) {
 	if (_luaContext) {
-		//debug("TeLuaScript::execute %s %s", _scriptNode.getPath().c_str(), fname.c_str());
+		//debug("TeLuaScript::execute %s %s", _scriptNode.toString().c_str(), fname.c_str());
 		TeLuaThread *thread = TeLuaThread::create(_luaContext);
 		thread->execute(fname);
 		thread->release();
@@ -59,7 +59,7 @@ void TeLuaScript::execute(const Common::String &fname) {
 
 void TeLuaScript::execute(const Common::String &fname, const TeVariant &p1) {
 	if (_luaContext) {
-		//debug("TeLuaScript::execute %s %s(%s)", _scriptNode.getPath().c_str(), fname.c_str(), p1.toString().c_str());
+		//debug("TeLuaScript::execute %s %s(%s)", _scriptNode.toString().c_str(), fname.c_str(), p1.toString().c_str());
 		TeLuaThread *thread = TeLuaThread::create(_luaContext);
 		thread->execute(fname, p1);
 		thread->release();
@@ -82,13 +82,13 @@ void TeLuaScript::execute(const Common::String &fname, const TeVariant &p1, cons
 	}
 }
 
-void TeLuaScript::load(const Common::Path &node) {
+void TeLuaScript::load(const TetraedgeFSNode &node) {
 	_started = false;
 	_scriptNode = node;
 }
 
 void TeLuaScript::unload() {
-	_scriptNode = Common::Path();
+	_scriptNode = TetraedgeFSNode();
 	_started = false;
 }
 
diff --git a/engines/tetraedge/te/te_lua_script.h b/engines/tetraedge/te/te_lua_script.h
index 9a878a00cbd..19cfe511e3e 100644
--- a/engines/tetraedge/te/te_lua_script.h
+++ b/engines/tetraedge/te/te_lua_script.h
@@ -25,6 +25,7 @@
 #include "common/str.h"
 #include "common/path.h"
 #include "tetraedge/te/te_variant.h"
+#include "tetraedge/tetraedge.h"
 
 namespace Tetraedge {
 
@@ -42,13 +43,13 @@ public:
 	void execute(const Common::String &fname, const TeVariant &p1, const TeVariant &p2);
 	void execute(const Common::String &fname, const TeVariant &p1, const TeVariant &p2, const TeVariant &p3);
 
-	void load(const Common::Path &node);
+	void load(const TetraedgeFSNode &node);
 	void unload();
 
 private:
 	TeLuaContext *_luaContext;
 
-	Common::Path _scriptNode;
+	TetraedgeFSNode _scriptNode;
 	bool _started;
 };
 
diff --git a/engines/tetraedge/te/te_lua_thread.cpp b/engines/tetraedge/te/te_lua_thread.cpp
index b0411d54bf8..e30cc4c9cf0 100644
--- a/engines/tetraedge/te/te_lua_thread.cpp
+++ b/engines/tetraedge/te/te_lua_thread.cpp
@@ -260,10 +260,10 @@ void TeLuaThread::applyScriptWorkarounds(char *buf, const Common::String &fileNa
 	}
 }
 
-void TeLuaThread::executeFile(const Common::Path &node) {
-	Common::File scriptFile;
-	if (!scriptFile.open(node)) {
-		warning("TeLuaThread::executeFile: File %s can't be opened", node.baseName().c_str());
+void TeLuaThread::executeFile(const TetraedgeFSNode &node) {
+	Common::ScopedPtr<Common::SeekableReadStream> scriptFile(node.createReadStream());
+	if (!scriptFile) {
+		warning("TeLuaThread::executeFile: File %s can't be opened", node.getName().c_str());
 		return;
 	}
 
@@ -271,15 +271,15 @@ void TeLuaThread::executeFile(const Common::Path &node) {
 	debug("TeLuaThread::executeFile: %s", node.getName().c_str());
 #endif
 
-	int64 fileLen = scriptFile.size();
+	int64 fileLen = scriptFile->size();
 	char *buf = new char[fileLen + 1];
-	scriptFile.read(buf, fileLen);
+	scriptFile->read(buf, fileLen);
 	buf[fileLen] = 0;
-	scriptFile.close();
+	scriptFile.reset();
 
-	applyScriptWorkarounds(buf, node.baseName());
+	applyScriptWorkarounds(buf, node.getPath().baseName());
 
-	_lastResumeResult = luaL_loadbuffer(_luaThread, buf, fileLen, node.toString(Common::Path::kNativeSeparator).c_str());
+	_lastResumeResult = luaL_loadbuffer(_luaThread, buf, fileLen, node.toString().c_str());
 	if (_lastResumeResult) {
 		const char *msg = lua_tostring(_luaThread, -1);
 		warning("TeLuaThread::executeFile: %s", msg);
diff --git a/engines/tetraedge/te/te_lua_thread.h b/engines/tetraedge/te/te_lua_thread.h
index 4e77f0e5bea..e925c082161 100644
--- a/engines/tetraedge/te/te_lua_thread.h
+++ b/engines/tetraedge/te/te_lua_thread.h
@@ -46,7 +46,7 @@ public:
 	void execute(const Common::String &str, const TeVariant &p1, const TeVariant &p2);
 	void execute(const Common::String &str, const TeVariant &p1, const TeVariant &p2, const TeVariant &p3);
 
-	void executeFile(const Common::Path &node);
+	void executeFile(const TetraedgeFSNode &node);
 	void pushValue(const TeVariant &val);
 
 	void release();
diff --git a/engines/tetraedge/te/te_material.cpp b/engines/tetraedge/te/te_material.cpp
index 734dfb970d5..848a2cde80b 100644
--- a/engines/tetraedge/te/te_material.cpp
+++ b/engines/tetraedge/te/te_material.cpp
@@ -97,10 +97,10 @@ void TeMaterial::deserialize(Common::SeekableReadStream &stream, TeMaterial &mat
 
 	if (nameStr.size()) {
 		TeCore *core = g_engine->getCore();
-		Common::Path matPath = core->findFile(texPath.join(nameStr));
+		TetraedgeFSNode matPath = core->findFile(Common::Path(texPath).join(nameStr));
 		material._texture = Te3DTexture::load2(matPath, false);
 		if (!material._texture)
-			warning("failed to load texture %s (texpath %s)", nameStr.c_str(), matPath.toString(Common::Path::kNativeSeparator).c_str());
+			warning("failed to load texture %s (texpath %s)", nameStr.c_str(), matPath.toString().c_str());
 	}
 
 	material._ambientColor.deserialize(stream);
diff --git a/engines/tetraedge/te/te_model_animation.cpp b/engines/tetraedge/te/te_model_animation.cpp
index 25d8e4de1aa..979b463954c 100644
--- a/engines/tetraedge/te/te_model_animation.cpp
+++ b/engines/tetraedge/te/te_model_animation.cpp
@@ -181,22 +181,22 @@ int TeModelAnimation::lastFrame() const {
 }
 
 bool TeModelAnimation::load(const Common::Path &path) {
-	Common::Path foundFile = g_engine->getCore()->findFile(path);
-	Common::File modelFile;
-	if (!modelFile.open(foundFile)) {
-		warning("[TeModel::load] Can't open file : %s.", path.toString(Common::Path::kNativeSeparator).c_str());
+	TetraedgeFSNode foundFile = g_engine->getCore()->findFile(path);
+	Common::ScopedPtr<Common::SeekableReadStream> modelFile(foundFile.createReadStream());
+	if (!modelFile) {
+		warning("[TeModel::load] Can't open file : %s.", path.toString().c_str());
 		return false;
 	}
 	bool retval;
-	if (Te3DObject2::loadAndCheckFourCC(modelFile, "TEZ0")) {
-		Common::SeekableReadStream *zlibStream = TeModel::tryLoadZlibStream(modelFile);
+	if (Te3DObject2::loadAndCheckFourCC(*modelFile, "TEZ0")) {
+		Common::SeekableReadStream *zlibStream = TeModel::tryLoadZlibStream(*modelFile);
 		if (!zlibStream)
 			return false;
 		retval = load(*zlibStream);
 		delete zlibStream;
 	} else {
-		modelFile.seek(0);
-		retval = load(modelFile);
+		modelFile->seek(0);
+		retval = load(*modelFile);
 	}
 	_loadedPath = path;
 	return retval;
diff --git a/engines/tetraedge/te/te_music.cpp b/engines/tetraedge/te/te_music.cpp
index dc4aab77639..12292afc7ef 100644
--- a/engines/tetraedge/te/te_music.cpp
+++ b/engines/tetraedge/te/te_music.cpp
@@ -60,12 +60,11 @@ void TeMusic::pause() {
 bool TeMusic::play() {
 	if (isPlaying())
 		return true;
-	if (!Common::File::exists(_filePath))
+	if (!_fileNode.exists())
 		return false;
 
-	Common::File *streamfile = new Common::File();
-	if (!streamfile->open(_filePath)) {
-		delete streamfile;
+	Common::SeekableReadStream *streamfile = _fileNode.createReadStream();
+	if (!streamfile) {
 		return false;
 	}
 	Audio::AudioStream *stream = Audio::makeVorbisStream(streamfile, DisposeAfterUse::YES);
@@ -82,7 +81,7 @@ bool TeMusic::play() {
 		soundType = Audio::Mixer::kMusicSoundType;
 	}
 
-	//debug("playing %s on channel %s at vol %d", _fileNode.getPath().c_str(), _channelName.c_str(), vol);
+	//debug("playing %s on channel %s at vol %d", _fileNode.toString().c_str(), _channelName.c_str(), vol);
 	mixer->playStream(soundType, &_sndHandle, stream, -1, vol);
 	_sndHandleValid = true;
 	_isPaused = false;
@@ -191,7 +190,7 @@ void TeMusic::setFilePath(const Common::Path &name) {
 	_rawPath = name;
 	TeCore *core = g_engine->getCore();
 	// Note: original search logic here abstracted away in our version..
-	_filePath = core->findFile(name);
+	_fileNode = core->findFile(name);
 }
 
 void TeMusic::update() {
diff --git a/engines/tetraedge/te/te_music.h b/engines/tetraedge/te/te_music.h
index ab5256008e9..ea27e12e469 100644
--- a/engines/tetraedge/te/te_music.h
+++ b/engines/tetraedge/te/te_music.h
@@ -29,6 +29,7 @@
 
 #include "tetraedge/te/te_resource.h"
 #include "tetraedge/te/te_signal.h"
+#include "tetraedge/tetraedge.h"
 
 namespace Tetraedge {
 
@@ -74,7 +75,7 @@ public:
 
 private:
 	Common::Path _rawPath; // Plain name of file requested
-	Common::Path _filePath; // file after finding it
+	TetraedgeFSNode _fileNode; // file after finding it
 	Common::String _channelName;
 
 	bool _repeat;
diff --git a/engines/tetraedge/te/te_png.cpp b/engines/tetraedge/te/te_png.cpp
index 545e04213ec..02277db1ec9 100644
--- a/engines/tetraedge/te/te_png.cpp
+++ b/engines/tetraedge/te/te_png.cpp
@@ -44,8 +44,8 @@ bool TePng::matchExtension(const Common::String &extn) {
 	return extn == "png" || extn == "png#anim";
 }
 
-bool TePng::load(const Common::Path &path) {
-	if (!TeScummvmCodec::load(path))
+bool TePng::load(const TetraedgeFSNode &node) {
+	if (!TeScummvmCodec::load(node))
 		return false;
 
 	_height = _loadedSurface->h / _nbFrames;
diff --git a/engines/tetraedge/te/te_png.h b/engines/tetraedge/te/te_png.h
index 31a6114b5d7..b110e9e94e2 100644
--- a/engines/tetraedge/te/te_png.h
+++ b/engines/tetraedge/te/te_png.h
@@ -36,7 +36,7 @@ public:
 	TePng(const Common::String &extn);
 	virtual ~TePng();
 
-	virtual bool load(const Common::Path &path) override;
+	virtual bool load(const TetraedgeFSNode &node) override;
 	virtual bool load(Common::SeekableReadStream &stream) override;
 
 	TeImage::Format imageFormat() override;
diff --git a/engines/tetraedge/te/te_resource_manager.h b/engines/tetraedge/te/te_resource_manager.h
index 27d2afb14c9..a67da4169b0 100644
--- a/engines/tetraedge/te/te_resource_manager.h
+++ b/engines/tetraedge/te/te_resource_manager.h
@@ -60,7 +60,8 @@ public:
 	}
 
 	template<class T>
-	TeIntrusivePtr<T> getResource(const Common::Path &path) {
+	TeIntrusivePtr<T> getResource(const TetraedgeFSNode &node) {
+		Common::Path path = node.getPath();
 		for (TeIntrusivePtr<TeResource> &resource : this->_resources) {
 			if (resource->getAccessName() == path) {
 				return TeIntrusivePtr<T>(dynamic_cast<T *>(resource.get()));
@@ -70,9 +71,9 @@ public:
 		TeIntrusivePtr<T> retval = new T();
 
 		if (retval.get()) {
-			if (!Common::File::exists(path))
-				warning("getResource: asked to fetch unreadable resource %s", path.toString(Common::Path::kNativeSeparator).c_str());
-			retval->load(path);
+			if (!node.isReadable())
+				warning("getResource: asked to fetch unreadable resource %s", node.toString().c_str());
+			retval->load(node);
 			addResource(retval.get());
 		}
 		return retval;
diff --git a/engines/tetraedge/te/te_scene.h b/engines/tetraedge/te/te_scene.h
index 8a5174ae7f8..de6f7a3b1c7 100644
--- a/engines/tetraedge/te/te_scene.h
+++ b/engines/tetraedge/te/te_scene.h
@@ -47,7 +47,7 @@ public:
 	Common::String currentCameraName() const;
 
 	virtual void draw();
-	virtual bool load(const Common::Path &path) { return false; };
+	virtual bool load(const TetraedgeFSNode &node) { return false; };
 
 	void removeModel(const Common::String &mname);
 	void setCurrentCamera(const Common::String &cname);
diff --git a/engines/tetraedge/te/te_scene_warp.cpp b/engines/tetraedge/te/te_scene_warp.cpp
index 27f9df25744..f7c5486232b 100644
--- a/engines/tetraedge/te/te_scene_warp.cpp
+++ b/engines/tetraedge/te/te_scene_warp.cpp
@@ -77,8 +77,8 @@ bool TeSceneWarp::load(const Common::Path &name, TeWarp *warp, bool flag) {
 
 	TeSceneWarpXmlParser parser(this, flag);
 	TeCore *core = g_engine->getCore();
-	Common::Path path = core->findFile(name);
-	if (!parser.loadFile(path))
+	TetraedgeFSNode node = core->findFile(name);
+	if (!node.loadXML(parser))
 		error("TeSceneWarp::load: failed to load data from %s", name.toString(Common::Path::kNativeSeparator).c_str());
 	if (!parser.parse())
 		error("TeSceneWarp::load: failed to parse data from %s", name.toString(Common::Path::kNativeSeparator).c_str());
diff --git a/engines/tetraedge/te/te_scummvm_codec.cpp b/engines/tetraedge/te/te_scummvm_codec.cpp
index d46df2b95db..26953eee885 100644
--- a/engines/tetraedge/te/te_scummvm_codec.cpp
+++ b/engines/tetraedge/te/te_scummvm_codec.cpp
@@ -38,10 +38,10 @@ TeScummvmCodec::~TeScummvmCodec() {
 	}
 }
 
-bool TeScummvmCodec::load(const Common::Path &path) {
-	Common::File file;
-	if (file.open(path) && load(static_cast<Common::SeekableReadStream &>(file))) {
-		_loadedPath = path;
+bool TeScummvmCodec::load(const TetraedgeFSNode &node) {
+	Common::ScopedPtr<Common::SeekableReadStream> file(node.createReadStream());
+	if (file && load(*file)) {
+		_loadedPath = node.getPath();
 		return true;
 	}
 	return false;
diff --git a/engines/tetraedge/te/te_scummvm_codec.h b/engines/tetraedge/te/te_scummvm_codec.h
index 474f80b086b..ebbc6eba341 100644
--- a/engines/tetraedge/te/te_scummvm_codec.h
+++ b/engines/tetraedge/te/te_scummvm_codec.h
@@ -32,7 +32,7 @@ public:
 	TeScummvmCodec();
 	virtual ~TeScummvmCodec();
 
-	virtual bool load(const Common::Path &path) override;
+	virtual bool load(const TetraedgeFSNode &node) override;
 	virtual bool load(Common::SeekableReadStream &stream) = 0;
 	virtual uint width() override;
 	virtual uint height() override;
diff --git a/engines/tetraedge/te/te_sound_manager.cpp b/engines/tetraedge/te/te_sound_manager.cpp
index 4763da20eb0..d21743f262e 100644
--- a/engines/tetraedge/te/te_sound_manager.cpp
+++ b/engines/tetraedge/te/te_sound_manager.cpp
@@ -43,12 +43,16 @@ void TeSoundManager::playFreeSound(const Common::Path &path) {
 
 void TeSoundManager::playFreeSound(const Common::Path &path, float vol, const Common::String &channel) {
 	TeCore *core = g_engine->getCore();
-	Common::Path sndPath = core->findFile(path);
+	TetraedgeFSNode sndNode = core->findFile(path);
 
-	Common::File *streamfile = new Common::File();
-	if (!streamfile->open(sndPath)) {
-		warning("TeSoundManager::playFreeSound: couldn't open %s", sndPath.toString(Common::Path::kNativeSeparator).c_str());
-		delete streamfile;
+	if (!sndNode.isReadable()) {
+		warning("TeSoundManager::playFreeSound: couldn't open %s", sndNode.toString().c_str());
+		return;
+	}
+
+	Common::SeekableReadStream *streamfile = sndNode.createReadStream();
+	if (!streamfile) {
+		warning("TeSoundManager::playFreeSound: couldn't open %s", sndNode.toString().c_str());
 		return;
 	}
 
diff --git a/engines/tetraedge/te/te_sprite_layout.cpp b/engines/tetraedge/te/te_sprite_layout.cpp
index 949b809d74e..31e91d906a8 100644
--- a/engines/tetraedge/te/te_sprite_layout.cpp
+++ b/engines/tetraedge/te/te_sprite_layout.cpp
@@ -82,28 +82,19 @@ bool TeSpriteLayout::load(const Common::Path &path) {
 	}
 
 	TeCore *core = g_engine->getCore();
-	Common::FSNode spriteNode = core->getFSNode(path);
-	Common::Path spritePath(path);
+	TetraedgeFSNode spriteNode = core->findFile(path);
 
 	// The path can point to a single file, or a folder with files
-	if (spriteNode.isDirectory()) {
-		if (!spriteNode.exists()) {
-			_tiledSurfacePtr->unload();
-			return false;
-		}
-	} else {
-		spritePath = core->findFile(path);
-		if (!Common::File::exists(spritePath)) {
-			_tiledSurfacePtr->unload();
-			return false;
-		}
+	if (!spriteNode.exists()) {
+		_tiledSurfacePtr->unload();
+		return false;
 	}
 
 	stop();
 	unload();
 
 	_tiledSurfacePtr->setLoadedPath(path);
-	if (_tiledSurfacePtr->load(spritePath)) {
+	if (_tiledSurfacePtr->load(spriteNode)) {
 		const TeVector2s32 texSize = _tiledSurfacePtr->tiledTexture()->totalSize();
 		if (texSize._y <= 0) {
 			setRatio(1.0);
@@ -115,7 +106,7 @@ bool TeSpriteLayout::load(const Common::Path &path) {
 		}
 		updateMesh();
 	} else {
-		debug("Failed to load TeSpriteLayout %s", path.toString(Common::Path::kNativeSeparator).c_str());
+		debug("Failed to load TeSpriteLayout %s", spriteNode.toString().c_str());
 		_tiledSurfacePtr->setLoadedPath("");
 	}
 	return true;
diff --git a/engines/tetraedge/te/te_text_layout.cpp b/engines/tetraedge/te/te_text_layout.cpp
index dba780a098c..22ebdb632e1 100644
--- a/engines/tetraedge/te/te_text_layout.cpp
+++ b/engines/tetraedge/te/te_text_layout.cpp
@@ -125,12 +125,13 @@ void TeTextLayout::setText(const Common::String &val) {
 		_baseFontSize = parser.fontSize();
 
 	if (!parser.fontFile().empty()) {
-		Common::Path fontPath(g_engine->getCore()->findFile(Common::Path(parser.fontFile())));
+		Common::Path fontPath(parser.fontFile());
+		TetraedgeFSNode fontNode = g_engine->getCore()->findFile(fontPath);
 		TeIntrusivePtr<TeIFont> font;
 		if (parser.fontFile().hasSuffixIgnoreCase(".ttf"))
-			font = g_engine->getResourceManager()->getResource<TeFont3>(fontPath).get();
+			font = g_engine->getResourceManager()->getResource<TeFont3>(fontNode).get();
 		else
-			font = g_engine->getResourceManager()->getResource<TeFont2>(fontPath).get();
+			font = g_engine->getResourceManager()->getResource<TeFont2>(fontNode).get();
 		_base.setFont(0, font);
 	}
 	if (parser.style().size())
diff --git a/engines/tetraedge/te/te_theora.cpp b/engines/tetraedge/te/te_theora.cpp
index ffd467e995b..1d69b514fe5 100644
--- a/engines/tetraedge/te/te_theora.cpp
+++ b/engines/tetraedge/te/te_theora.cpp
@@ -40,11 +40,9 @@ bool TeTheora::matchExtension(const Common::String &extn) {
 	return extn == "ogv";
 }
 
-bool TeTheora::load(const Common::Path &path) {
-	Common::File *theoraFile = new Common::File();
-	theoraFile->open(path);
-	_loadedPath = path;
-	if (!_decoder->loadStream(theoraFile))
+bool TeTheora::load(const TetraedgeFSNode &node) {
+	_loadedNode = node;
+	if (!_decoder->loadStream(node.createReadStream()))
 		return false;
 	_decoder->setOutputPixelFormat(Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
 	return true;
@@ -108,10 +106,10 @@ bool TeTheora::update(uint i, TeImage &imgout) {
 	if (!_decoder->isPlaying())
 		_decoder->start();
 
-	if (_decoder->getCurFrame() > (int)i && Common::File::exists(_loadedPath)) {
+	if (_decoder->getCurFrame() > (int)i && _loadedNode.exists()) {
 		// rewind.. no good way to do that, but it should
 		// only happen on loop.
-		load(_loadedPath);
+		load(_loadedNode);
 		_decoder->start();
 	}
 
@@ -125,9 +123,9 @@ bool TeTheora::update(uint i, TeImage &imgout) {
 		//debug("TeTheora: %s %ld", _path.toString().c_str(), i);
 		imgout.copyFrom(*frame);
 		return true;
-	} else if (_hitEnd && Common::File::exists(_loadedPath)) {
+	} else if (_hitEnd && _loadedNode.exists()) {
 		// Loop to the start.
-		load(_loadedPath);
+		load(_loadedNode);
 		frame = _decoder->decodeNextFrame();
 		if (frame) {
 			imgout.copyFrom(*frame);
diff --git a/engines/tetraedge/te/te_theora.h b/engines/tetraedge/te/te_theora.h
index 933658163c2..17cefadfa1a 100644
--- a/engines/tetraedge/te/te_theora.h
+++ b/engines/tetraedge/te/te_theora.h
@@ -36,7 +36,7 @@ public:
 	TeTheora();
 	virtual ~TeTheora();
 
-	virtual bool load(const Common::Path &path) override;
+	virtual bool load(const TetraedgeFSNode &node) override;
 	virtual uint width() override;
 	virtual uint height() override;
 	virtual int nbFrames() override;
@@ -61,7 +61,7 @@ public:
 private:
 	Video::TheoraDecoder *_decoder;
 
-	Common::Path _loadedPath;
+	TetraedgeFSNode _loadedNode;
 	bool _hitEnd;
 
 };
diff --git a/engines/tetraedge/te/te_tiled_surface.cpp b/engines/tetraedge/te/te_tiled_surface.cpp
index 10fac11bcbb..0475cc7a9ed 100644
--- a/engines/tetraedge/te/te_tiled_surface.cpp
+++ b/engines/tetraedge/te/te_tiled_surface.cpp
@@ -60,12 +60,12 @@ byte TeTiledSurface::isLoaded() {
 	return _tiledTexture && _tiledTexture->isLoaded();
 }
 
-bool TeTiledSurface::load(const Common::Path &path) {
+bool TeTiledSurface::load(const TetraedgeFSNode &node) {
 	unload();
 
 	TeResourceManager *resmgr = g_engine->getResourceManager();
 	if (_loadedPath.empty())
-		_loadedPath = path;
+		_loadedPath = node.getPath();
 
 	Common::Path ttPath(_loadedPath.append(".tt"));
 	TeIntrusivePtr<TeTiledTexture> texture;
@@ -76,13 +76,13 @@ bool TeTiledSurface::load(const Common::Path &path) {
 
 	if (!texture) {
 		TeCore *core = g_engine->getCore();
-		_codec = core->createVideoCodec(Common::Path(_loadedPath));
+		_codec = core->createVideoCodec(node);
 		if (!_codec)
 			return false;
 
 		texture = new TeTiledTexture();
 
-		if (_codec->load(path)) {
+		if (_codec->load(node)) {
 			texture->setAccessName(ttPath);
 			resmgr->addResource(texture.get());
 			_imgFormat = _codec->imageFormat();
diff --git a/engines/tetraedge/te/te_tiled_surface.h b/engines/tetraedge/te/te_tiled_surface.h
index 8208a192234..53c8dbfa511 100644
--- a/engines/tetraedge/te/te_tiled_surface.h
+++ b/engines/tetraedge/te/te_tiled_surface.h
@@ -45,7 +45,7 @@ public:
 	void draw() override;
 	virtual void entry() {};
 	byte isLoaded();
-	bool load(const Common::Path &path);
+	bool load(const TetraedgeFSNode &node);
 	bool load(const TeImage &image);
 	bool load(const TeIntrusivePtr<Te3DTexture> &texture);
 
diff --git a/engines/tetraedge/te/te_warp.cpp b/engines/tetraedge/te/te_warp.cpp
index be85e018de9..0a0986237c2 100644
--- a/engines/tetraedge/te/te_warp.cpp
+++ b/engines/tetraedge/te/te_warp.cpp
@@ -42,7 +42,7 @@ TeWarp::TeWarp() : _visible1(false), _loaded(false), _preloaded(false),
 TeWarp::~TeWarp() {
 	_markerValidatedSignal.clear();
 	unload();
-	_file.close();
+	_file.reset();
 }
 
 void TeWarp::activeMarkers(bool active) {
@@ -243,41 +243,41 @@ void TeWarp::load(const Common::Path &path, bool flag) {
 		error("Empty TeWarp path!");
 
 	TeCore *core = g_engine->getCore();
-	Common::Path warpPath = core->findFile(_warpPath);
-	if (!Common::File::exists(warpPath)) {
+	TetraedgeFSNode node = core->findFile(_warpPath);
+	if (!node.isReadable()) {
 		error("Couldn't find TeWarp path data '%s'", _warpPath.toString(Common::Path::kNativeSeparator).c_str());
 	}
 
 	if (_preloaded)
 		error("TODO: Support preloading in TeWarp::load");
-	_file.open(warpPath);
+	_file.reset(node.createReadStream());
 	char header[7];
 	header[6] = '\0';
-	_file.read(header, 6);
+	_file->read(header, 6);
 	if (Common::String(header) != "TeWarp")
-		error("Invalid header in warp data %s", _warpPath.toString(Common::Path::kNativeSeparator).c_str());
-	uint32 globalTexDataOffset = _file.readUint32LE();
-	_texEncodingType = _file.readPascalString();
-	_xCount = _file.readUint32LE();
-	_yCount = _file.readUint32LE();
-	uint32 numAnims = _file.readUint32LE();
-	_someXVal = _file.readUint32LE();
-	_someYVal = _file.readUint32LE();
-	_someMeshX = _file.readUint32LE();
-	_someMeshY = _file.readUint32LE();
+		error("Invalid header in warp data %s", _warpPath.toString().c_str());
+	uint32 globalTexDataOffset = _file->readUint32LE();
+	_texEncodingType = _file->readPascalString();
+	_xCount = _file->readUint32LE();
+	_yCount = _file->readUint32LE();
+	uint32 numAnims = _file->readUint32LE();
+	_someXVal = _file->readUint32LE();
+	_someYVal = _file->readUint32LE();
+	_someMeshX = _file->readUint32LE();
+	_someMeshY = _file->readUint32LE();
 	if (_xCount > 1000 || _yCount > 1000 || numAnims > 1000)
 		error("Improbable values in TeWarp data xCount %d yCount %d numAnims %d", _xCount, _yCount, numAnims);
 	_warpBlocs.resize(_xCount * _yCount * 6);
 	for (uint i = 0; i < _xCount * _yCount * 6; i++) {
-		TeWarpBloc::CubeFace face = static_cast<TeWarpBloc::CubeFace>(_file.readByte());
+		TeWarpBloc::CubeFace face = static_cast<TeWarpBloc::CubeFace>(_file->readByte());
 		// TODO: This is strange, surely we only need to set the offset and create the bloc
 		// once but the code seems to do it xCount * yCount times..
 		for (uint j = 0; j < _xCount * _yCount; j++) {
-			uint xoff = _file.readUint16LE();
-			uint yoff = _file.readUint16LE();
+			uint xoff = _file->readUint16LE();
+			uint yoff = _file->readUint16LE();
 			if (xoff > 1000 || yoff > 1000)
 				error("TeWarp::load: Improbable offsets %d, %d", xoff, yoff);
-			uint32 blocTexOffset = _file.readUint32LE();
+			uint32 blocTexOffset = _file->readUint32LE();
 			_warpBlocs[i].setTextureFileOffset(globalTexDataOffset + blocTexOffset);
 			_warpBlocs[i].create(face, _xCount, _yCount, TeVector2s32(xoff, yoff));
 		}
@@ -286,45 +286,45 @@ void TeWarp::load(const Common::Path &path, bool flag) {
 	_putAnimData.reserve(numAnims);
 	for (uint i = 0; i < numAnims; i++) {
 		char aname[5];
-		_file.read(aname, 4);
+		_file->read(aname, 4);
 		aname[4] = '\0';
 		_loadedAnimData[i]._name = aname;
-		uint numFrames = _file.readUint32LE();
+		uint numFrames = _file->readUint32LE();
 		if (numFrames > 1000)
 			error("TeWarp::load: Improbable frame count %d", numFrames);
-		byte numSomething = _file.readByte();
+		byte numSomething = _file->readByte();
 		_loadedAnimData[i]._frameDatas.resize(numFrames);
 		for (uint j = 0; j < numFrames; j++) {
 			FrameData &frameData = _loadedAnimData[i]._frameDatas[j];
 			frameData._loadedTexCount = 0;
 			Common::Array<TeWarpBloc> warpBlocs;
 			for (uint k = 0; k < numSomething; k++) {
-				uint blocCount = _file.readUint32LE();
+				uint blocCount = _file->readUint32LE();
 				if (blocCount > 1000)
 					error("TeWarp::load: Improbable bloc count %d", blocCount);
 				if (blocCount) {
-					TeWarpBloc::CubeFace face = static_cast<TeWarpBloc::CubeFace>(_file.readByte());
+					TeWarpBloc::CubeFace face = static_cast<TeWarpBloc::CubeFace>(_file->readByte());
 					warpBlocs.resize(blocCount);
 					for (auto &warpBloc : warpBlocs) {
-						uint xoff = _file.readUint16LE();
-						uint yoff = _file.readUint16LE();
+						uint xoff = _file->readUint16LE();
+						uint yoff = _file->readUint16LE();
 						if (xoff > 10000 || yoff > 10000)
 							error("TeWarp::load: Improbable offsets %d, %d", xoff, yoff);
-						uint32 texDataOff = _file.readUint32LE();
+						uint32 texDataOff = _file->readUint32LE();
 						warpBloc.setTextureFileOffset(globalTexDataOffset + texDataOff);
 						warpBloc.create(face, _someXVal, _someYVal, TeVector2s32(xoff, yoff));
 						if (flag)
 							warpBloc.color(TeColor(255, 0, 0, 255));
 					}
-					uint meshSize = _file.readUint32LE();
+					uint meshSize = _file->readUint32LE();
 					if (meshSize > 1000)
 						error("TeWarp::load: Improbable meshSize %d", meshSize);
 					TePickMesh tmpMesh;
 					tmpMesh.setName(aname);
 					tmpMesh.nbTriangles(meshSize * 2);
 					for (uint m = 0; m < meshSize; m++) {
-						uint xoff = _file.readUint16LE();
-						uint yoff = _file.readUint16LE();
+						uint xoff = _file->readUint16LE();
+						uint yoff = _file->readUint16LE();
 						if (xoff > 10000 || yoff > 10000)
 							error("TeWarp::load: Improbable offsets %d, %d", xoff, yoff);
 						addQuadToPickMesh(tmpMesh, m * 2, face, TeVector2s32(xoff, yoff), _someMeshX, _someMeshY);
@@ -413,12 +413,12 @@ void TeWarp::putObject(const Common::String &name, bool enable) {
 }
 
 void TeWarp::update() {
-	if (!_visible1 || !_file.isOpen())
+	if (!_visible1 || !_file)
 		return;
 	//Application *app = g_engine->getApplication();
 	_frustum.update(_camera);
 	for (auto &bloc : _warpBlocs) {
-		bloc.loadTexture(_file, _texEncodingType);
+		bloc.loadTexture(*_file, _texEncodingType);
 	}
 
 	for (auto &anim : _loadedAnimData) {
@@ -441,7 +441,7 @@ void TeWarp::update() {
 				anim._frameDatas[lastFrame]._loadedTexCount = 0;
 			}
 		}
-		anim._frameDatas[anim._curFrameNo].loadTextures(_frustum, _file, _texEncodingType);
+		anim._frameDatas[anim._curFrameNo].loadTextures(_frustum, *_file, _texEncodingType);
 	}
 }
 
@@ -664,7 +664,7 @@ void TeWarp::updateCamera(const TeVector3f32 &screen) {
 	_camera.projectionMatrix();
 }
 
-void TeWarp::FrameData::loadTextures(const TeFrustum &frustum, Common::File &file, const Common::String &fileType) {
+void TeWarp::FrameData::loadTextures(const TeFrustum &frustum, Common::SeekableReadStream &file, const Common::String &fileType) {
 	for (auto &b : _warpBlocs) {
 		if (!b.isLoaded() && (frustum.isTriangleInside(b.vertex(0), b.vertex(1), b.vertex(3))
 				|| frustum.isTriangleInside(b.vertex(1), b.vertex(2), b.vertex(3)))) {
diff --git a/engines/tetraedge/te/te_warp.h b/engines/tetraedge/te/te_warp.h
index 303c45834b0..2731fc2e78c 100644
--- a/engines/tetraedge/te/te_warp.h
+++ b/engines/tetraedge/te/te_warp.h
@@ -43,7 +43,7 @@ public:
 		int _loadedTexCount;
 		// Note: dropped "minblock" param from original as it's only
 		// ever set to 0
-		void loadTextures(const TeFrustum &frustum, Common::File &file, const Common::String &fileType);
+		void loadTextures(const TeFrustum &frustum, Common::SeekableReadStream &file, const Common::String &fileType);
 		void unloadTextures();
 	};
 
@@ -125,7 +125,7 @@ private:
 	bool onMouseLeftDown(const Common::Point &pt);
 	bool onMarkerValidated(const Common::String &name);
 
-	Common::File _file;
+	Common::ScopedPtr<Common::SeekableReadStream> _file;
 	Common::Path _warpPath;
 	TeCamera _camera;
 	bool _markersActive;
diff --git a/engines/tetraedge/te/te_warp_bloc.cpp b/engines/tetraedge/te/te_warp_bloc.cpp
index 5956e97917d..53b0ec38b54 100644
--- a/engines/tetraedge/te/te_warp_bloc.cpp
+++ b/engines/tetraedge/te/te_warp_bloc.cpp
@@ -123,7 +123,7 @@ bool TeWarpBloc::isLoaded() const {
 	return _mesh->materials().size() > 0 && _mesh->material(0)->_texture;
 }
 
-void TeWarpBloc::loadTexture(Common::File &file, const Common::String &type) {
+void TeWarpBloc::loadTexture(Common::SeekableReadStream &file, const Common::String &type) {
 	if (isLoaded())
 		return;
 
diff --git a/engines/tetraedge/te/te_warp_bloc.h b/engines/tetraedge/te/te_warp_bloc.h
index f16868b8c78..c69f9c6e9e5 100644
--- a/engines/tetraedge/te/te_warp_bloc.h
+++ b/engines/tetraedge/te/te_warp_bloc.h
@@ -55,7 +55,7 @@ public:
 	void create();
 	void index(uint offset, uint val);
 	bool isLoaded() const;
-	void loadTexture(Common::File &file, const Common::String &type);
+	void loadTexture(Common::SeekableReadStream &file, const Common::String &type);
 	//void operator=(const TeWarpBloc &other); // unused
 	//bool operator==(const TeWarpBloc &other); // unused
 	void render();
diff --git a/engines/tetraedge/tetraedge.cpp b/engines/tetraedge/tetraedge.cpp
index 4851d470f61..2b85ab44da5 100644
--- a/engines/tetraedge/tetraedge.cpp
+++ b/engines/tetraedge/tetraedge.cpp
@@ -41,6 +41,7 @@
 #include "tetraedge/te/te_sound_manager.h"
 #include "tetraedge/te/te_input_mgr.h"
 #include "tetraedge/te/te_particle.h"
+#include "tetraedge/obb_archive.h"
 
 namespace Tetraedge {
 
@@ -62,6 +63,8 @@ TetraedgeEngine::~TetraedgeEngine() {
 	delete _soundManager;
 	delete _resourceManager;
 	delete _inputMgr;
+	for (Common::Array<Common::Archive *>::iterator it = _rootArchives.begin(); it != _rootArchives.end(); it++)
+		delete *it;
 	Object3D::cleanup();
 	Character::cleanup();
 	TeAnimation::cleanup();
@@ -207,8 +210,19 @@ void TetraedgeEngine::closeGameDialogs() {
 
 void TetraedgeEngine::configureSearchPaths() {
 	const Common::FSNode gameDataDir(ConfMan.getPath("path"));
-	if (_gameDescription->platform != Common::kPlatformIOS)
+	if (_gameDescription->platform == Common::kPlatformMacintosh) {
 		SearchMan.addSubDirectoryMatching(gameDataDir, "Resources", 0, 6);
+		_rootArchives.push_back(new Common::FSDirectory(gameDataDir.getChild("Resources"), 10));
+	} else
+		_rootArchives.push_back(new Common::FSDirectory(gameDataDir, 10));
+
+	if (_gameDescription->platform == Common::Platform::kPlatformAndroid
+	    && strlen(_gameDescription->filesDescriptions[0].fileName) > 4
+	    && scumm_stricmp(_gameDescription->filesDescriptions[0].fileName + strlen(_gameDescription->filesDescriptions[0].fileName) - 4, ".obb") == 0) {
+		ObbArchive *obb = ObbArchive::open(_gameDescription->filesDescriptions[0].fileName);
+		_rootArchives.push_back(obb);
+		SearchMan.add("obbarchive", obb, 0, false);
+	}
 }
 
 int TetraedgeEngine::getDefaultScreenWidth() const {


Commit: 22666274a40617356dddf3c08e9f5341b642ddeb
    https://github.com/scummvm/scummvm/commit/22666274a40617356dddf3c08e9f5341b642ddeb
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Skip pausable attribute

Changed paths:
    engines/tetraedge/te/te_lua_gui_lua_callbacks.cpp


diff --git a/engines/tetraedge/te/te_lua_gui_lua_callbacks.cpp b/engines/tetraedge/te/te_lua_gui_lua_callbacks.cpp
index d66f27372e9..7ad66443b28 100644
--- a/engines/tetraedge/te/te_lua_gui_lua_callbacks.cpp
+++ b/engines/tetraedge/te/te_lua_gui_lua_callbacks.cpp
@@ -602,8 +602,9 @@ int layoutPositionLinearAnimationBindings(lua_State *L) {
 			} else if (!strcmp(s, "endValue")) {
 				static const TeVector3f32 defaultEnd(0.0f, 0.0f, 0.0f);
 				anim->_endVal = TeLuaToTeVector3f32(L, -1, defaultEnd);
-			} else if (!strcmp(s, "layout")) {
+			} else if (!strcmp(s, "layout") || !strcmp(s, "pausable")) {
 				// skip.
+				// TODO: What should we do with "pausable" attribute?
 			} else if (!strcmp(s, "curve")) {
 				const Common::Array<float> curve = TeLuaToFloatArray(L, -1);
 				anim->setCurve(curve);


Commit: d3e1834e969c01343fe24b79e9d365a122ad8fb9
    https://github.com/scummvm/scummvm/commit/d3e1834e969c01343fe24b79e9d365a122ad8fb9
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Fallback to English if selected language is not available

Changed paths:
    engines/tetraedge/te/te_core.cpp


diff --git a/engines/tetraedge/te/te_core.cpp b/engines/tetraedge/te/te_core.cpp
index ffbad1ede04..6e6e450eb3b 100644
--- a/engines/tetraedge/te/te_core.cpp
+++ b/engines/tetraedge/te/te_core.cpp
@@ -207,22 +207,29 @@ TetraedgeFSNode TeCore::findFile(const Common::Path &path, bool quiet) const {
 	activeFlags.erase("SD");
 	activeFlags["HD"] = true;
 
-	for (uint dirNode = 0; dirNode < dirNodes.size(); dirNode++) {
-		Common::Array<TetraedgeFSNode> foundFiles;
-		_findFileRecursively(dirNodes[dirNode], activeFlags, fname, foundFiles, 5);
-		if (foundFiles.empty())
-			continue;
-		TetraedgeFSNode best = foundFiles[0];
-		int bestDepth = best.getDepth();
-		for (uint i = 1; i < foundFiles.size(); i++) {
-			int depth = foundFiles[i].getDepth();
-			if (depth > bestDepth) {
-				bestDepth = depth;
-				best = foundFiles[i];
+	for (int attempt = 0; attempt < 2; attempt++) {
+		if (attempt == 1)
+			activeFlags["en"] = true;
+		for (uint dirNode = 0; dirNode < dirNodes.size(); dirNode++) {
+			Common::Array<TetraedgeFSNode> foundFiles;
+			_findFileRecursively(dirNodes[dirNode], activeFlags, fname, foundFiles, 5);
+			if (foundFiles.empty())
+				continue;
+			TetraedgeFSNode best = foundFiles[0];
+			int bestDepth = best.getDepth();
+			for (uint i = 1; i < foundFiles.size(); i++) {
+				int depth = foundFiles[i].getDepth();
+				if (depth > bestDepth) {
+					bestDepth = depth;
+					best = foundFiles[i];
+				}
 			}
-		}
 
-		return best;
+			if (attempt == 1 && !quiet)
+				debug("TeCore::findFile Falled back to English for %s", path.toString().c_str());
+
+			return best;
+		}
 	}
 
 	// Didn't find it at all..


Commit: ede5b4b77e921d8e02d132ef11c8f6e3e0707f97
    https://github.com/scummvm/scummvm/commit/ede5b4b77e921d8e02d132ef11c8f6e3e0707f97
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Support UTF-8 Switch releases

Changed paths:
    engines/tetraedge/detection.h
    engines/tetraedge/detection_tables.h
    engines/tetraedge/te/te_i_font.cpp
    engines/tetraedge/tetraedge.cpp
    engines/tetraedge/tetraedge.h


diff --git a/engines/tetraedge/detection.h b/engines/tetraedge/detection.h
index cf5bbf1c796..9908ab2a8c0 100644
--- a/engines/tetraedge/detection.h
+++ b/engines/tetraedge/detection.h
@@ -34,6 +34,10 @@ enum TetraedgeDebugChannels {
 	kDebugScript   = 1 << 4
 };
 
+enum GameFeatures {
+	GF_UTF8        = 1 << 0,
+};
+
 extern const PlainGameDescriptor GAME_NAMES[];
 
 extern const ADGameDescription GAME_DESCRIPTIONS[];
diff --git a/engines/tetraedge/detection_tables.h b/engines/tetraedge/detection_tables.h
index 4464ec7f81e..d7faebb1ed0 100644
--- a/engines/tetraedge/detection_tables.h
+++ b/engines/tetraedge/detection_tables.h
@@ -80,7 +80,8 @@ const ADGameDescription GAME_DESCRIPTIONS[] = {
 	{
 		"syberia",
 	        nullptr,
-		AD_ENTRY1s("InGame.lua", "acaf61504a12aebf3862648e04cf29aa", 3920),
+		AD_ENTRY2s("InGame.lua", "acaf61504a12aebf3862648e04cf29aa", 3920,
+			   "texts/de.xml", "14681ac50bbfa50427058d2793b415eb", (uint32_t)-1),
 		Common::UNK_LANG,
 		Common::kPlatformNintendoSwitch,
 		ADGF_NO_FLAGS,
@@ -89,14 +90,50 @@ const ADGameDescription GAME_DESCRIPTIONS[] = {
 	{
 		"syberia2",
 		nullptr,
-		AD_ENTRY2s("Debug.lua", "a2ea493892e96bea64013819195c081e", 7024,
-			   "InGame.lua", "a7df110fe816cb342574150c6f992964", 4654),
+		AD_ENTRY3s("Debug.lua", "a2ea493892e96bea64013819195c081e", 7024,
+			   "InGame.lua", "a7df110fe816cb342574150c6f992964", 4654,
+			   "texts/de.xml", "dabad822a917b1f87de8f09eadc3ec85", (uint32_t)-1),
 		Common::UNK_LANG,
 		Common::kPlatformNintendoSwitch,
 		ADGF_NO_FLAGS,
 		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
 	},
 
+	// Nintendo Switch, from Syberia1+2 Online bundle, v0
+	{
+		"syberia",
+	        nullptr,
+		AD_ENTRY2s("InGame.lua", "ca319e6f014d04baaf1e77f13f89b44f", 4271,
+			   "texts/de.xml", "14681ac50bbfa50427058d2793b415eb", (uint32_t)-1),
+		Common::UNK_LANG,
+		Common::kPlatformNintendoSwitch,
+		ADGF_NO_FLAGS,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
+	// Nintendo Switch, from Syberia1+2 Online bundle, v196608
+	{
+		"syberia",
+	        nullptr,
+		AD_ENTRY2s("InGame.lua", "ca319e6f014d04baaf1e77f13f89b44f", 4271,
+			   "texts/de.xml", "17d7a875e81a7761d2b30698bd947c15", (uint32_t)-1),
+		Common::UNK_LANG,
+		Common::kPlatformNintendoSwitch,
+		ADGF_NO_FLAGS | GF_UTF8,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+	{
+		"syberia2",
+		nullptr,
+		AD_ENTRY3s("Debug.lua", "a2ea493892e96bea64013819195c081e", 7024,
+			   "InGame.lua", "7d7fdb9005675618220e7cd8962c6482", 4745,
+			   "texts/de.xml", "78ed3567b3621459229f39c03132e5bb", (uint32_t)-1),
+		Common::UNK_LANG,
+		Common::kPlatformNintendoSwitch,
+		ADGF_NO_FLAGS | GF_UTF8,
+		GUIO1(GAMEOPTION_CORRECT_MOVIE_ASPECT)
+	},
+
 	// Android v1.0.5
 	{
 		"syberia",
diff --git a/engines/tetraedge/te/te_i_font.cpp b/engines/tetraedge/te/te_i_font.cpp
index 65fb6034cbd..4cd0a968e18 100644
--- a/engines/tetraedge/te/te_i_font.cpp
+++ b/engines/tetraedge/te/te_i_font.cpp
@@ -51,6 +51,8 @@ TeIFont::GlyphData TeIFont::glyph(uint pxSize, uint charcode) {
 
 Common::CodePage TeIFont::codePage() const {
 	Common::String lang = g_engine->getCore()->language();
+	if (g_engine->isUtf8Release())
+		return Common::CodePage::kUtf8;
 	if (lang == "ru")
 		return Common::kISO8859_5;
 	if (g_engine->getGamePlatform() == Common::Platform::kPlatformAndroid)
diff --git a/engines/tetraedge/tetraedge.cpp b/engines/tetraedge/tetraedge.cpp
index 2b85ab44da5..3f927aac38a 100644
--- a/engines/tetraedge/tetraedge.cpp
+++ b/engines/tetraedge/tetraedge.cpp
@@ -149,6 +149,10 @@ Common::Platform TetraedgeEngine::getGamePlatform() const {
 	return _gameDescription->platform;
 }
 
+bool TetraedgeEngine::isUtf8Release() const {
+	return !!(_gameDescription->flags & GF_UTF8);
+}
+
 bool TetraedgeEngine::isGameDemo() const {
 	return (_gameDescription->flags & ADGF_DEMO) != 0;
 }
diff --git a/engines/tetraedge/tetraedge.h b/engines/tetraedge/tetraedge.h
index 0ee7299f4c3..28904d8614e 100644
--- a/engines/tetraedge/tetraedge.h
+++ b/engines/tetraedge/tetraedge.h
@@ -124,6 +124,8 @@ public:
 
 	Common::Platform getGamePlatform() const;
 
+	bool isUtf8Release() const;
+
 	bool isGameDemo() const;
 
 	/**


Commit: 0cc45aabf83754db24f46835ecb9997527ac7125
    https://github.com/scummvm/scummvm/commit/0cc45aabf83754db24f46835ecb9997527ac7125
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2025-01-11T14:20:56+01:00

Commit Message:
TETRAEDGE: Declare Polish and Japanese translations

Changed paths:
    engines/tetraedge/detection.cpp


diff --git a/engines/tetraedge/detection.cpp b/engines/tetraedge/detection.cpp
index d5edb2a4c3c..77d70c7d31a 100644
--- a/engines/tetraedge/detection.cpp
+++ b/engines/tetraedge/detection.cpp
@@ -46,6 +46,8 @@ static const Common::Language *getGameLanguages() {
 		Common::ES_ESP,
 		Common::RU_RUS,
 		Common::HE_ISR,  // This is a Fan-translation, which requires additional patch
+		Common::JA_JPN,
+		Common::PL_POL,
 		Common::UNK_LANG
 	};
 	return languages;




More information about the Scummvm-git-logs mailing list