[Scummvm-git-logs] scummvm master -> 7e42ae4eecc32ca59cbb1834a8c29cab8f733171

dreammaster dreammaster at scummvm.org
Thu Jul 8 05:08:35 UTC 2021


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

Summary:
41b1126aed AGS: Split SpriteCache, picking out read/write into SpriteFile class
7e322be394 AGS: Moved image (de)compression functions out from SpriteFile
bfabf7c94d AGS: Fixed infinite wait in Object_Move under specific conditions
e582a09612 AGS: Reprogram file/path functions to always use UTF-8 encoding
81f0088064 AGS: Start in the UTF-8 mode, switch encoding based on game data
5dc90dabc4 AGS: Added encoding hint to the TRS/TRA
93bc3a65d4 AGS: use text encoding hint from translation
7e42ae4eec MOHAWK: Revert incorrect format specifier changes


Commit: 41b1126aed5b68318081613794decf0bd3937df6
    https://github.com/scummvm/scummvm/commit/41b1126aed5b68318081613794decf0bd3937df6
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-07T22:07:09-07:00

Commit Message:
AGS: Split SpriteCache, picking out read/write into SpriteFile class

>From upstream 565eaaf90e63ad444798cd949b1e4c60dfb4a0f8

Changed paths:
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/ac/sprite.cpp
    engines/ags/engine/ac/sprite.h
    engines/ags/engine/ac/sprite_cache_engine.cpp
    engines/ags/engine/main/engine.cpp
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h


diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index 2a32cdf1a5..6dea34994e 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -287,7 +287,7 @@ int RunAGSGame(const String &newgame, unsigned int mode, int data) {
 		quitprintf("!RunAGSGame: error loading new game file:\n%s", err->FullMessage().GetCStr());
 
 	_GP(spriteset).Reset();
-	err = _GP(spriteset).InitFile(SpriteCache::DefaultSpriteFileName, SpriteCache::DefaultSpriteIndexName);
+	err = _GP(spriteset).InitFile(SpriteFile::DefaultSpriteFileName, SpriteFile::DefaultSpriteIndexName);
 	if (!err)
 		quitprintf("!RunAGSGame: error loading new sprites:\n%s", err->FullMessage().GetCStr());
 
diff --git a/engines/ags/engine/ac/sprite.cpp b/engines/ags/engine/ac/sprite.cpp
index 65ba472c43..802e75e63e 100644
--- a/engines/ags/engine/ac/sprite.cpp
+++ b/engines/ags/engine/ac/sprite.cpp
@@ -110,7 +110,7 @@ Bitmap *remove_alpha_channel(Bitmap *from) {
 	return to;
 }
 
-void pre_save_sprite(int ee) {
+void pre_save_sprite(Bitmap *image) {
 	// not used, we don't save
 }
 
diff --git a/engines/ags/engine/ac/sprite.h b/engines/ags/engine/ac/sprite.h
index 8995e76fec..1731c17586 100644
--- a/engines/ags/engine/ac/sprite.h
+++ b/engines/ags/engine/ac/sprite.h
@@ -31,7 +31,7 @@ void get_new_size_for_sprite(int ee, int ww, int hh, int &newwid, int &newhit);
 void set_rgb_mask_using_alpha_channel(Shared::Bitmap *image);
 // from is a 32-bit RGBA image, to is a 15/16/24-bit destination image
 Shared::Bitmap *remove_alpha_channel(Shared::Bitmap *from);
-void pre_save_sprite(int ee);
+void pre_save_sprite(Shared::Bitmap *bitmap);
 void initialize_sprite(int ee);
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/sprite_cache_engine.cpp b/engines/ags/engine/ac/sprite_cache_engine.cpp
index 52010c83bb..7b2a40b512 100644
--- a/engines/ags/engine/ac/sprite_cache_engine.cpp
+++ b/engines/ags/engine/ac/sprite_cache_engine.cpp
@@ -48,7 +48,6 @@ void SpriteCache::InitNullSpriteParams(sprkey_t index) {
 	_sprInfos[index].Width = _sprInfos[0].Width;
 	_sprInfos[index].Height = _sprInfos[0].Height;
 	_spriteData[index].Image = nullptr;
-	_spriteData[index].Offset = _spriteData[0].Offset;
 	_spriteData[index].Size = _spriteData[0].Size;
 	_spriteData[index].Flags = SPRCACHEFLAG_REMAPPED;
 }
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 09eac0be40..657b4c1615 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -583,13 +583,13 @@ void show_preload() {
 int engine_init_sprites() {
 	Debug::Printf(kDbgMsg_Info, "Initialize sprites");
 
-	HError err = _GP(spriteset).InitFile(SpriteCache::DefaultSpriteFileName, SpriteCache::DefaultSpriteIndexName);
+	HError err = _GP(spriteset).InitFile(SpriteFile::DefaultSpriteFileName, SpriteFile::DefaultSpriteIndexName);
 	if (!err) {
 		sys_main_shutdown();
 		allegro_exit();
 		_G(proper_exit) = 1;
 		_G(platform)->DisplayAlert("Could not load sprite set file %s\n%s",
-		                           SpriteCache::DefaultSpriteFileName.GetCStr(),
+		                           SpriteFile::DefaultSpriteFileName,
 		                           err->FullMessage().GetCStr());
 		return EXIT_ERROR;
 	}
diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index 847283251e..a081c07ea9 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -33,6 +33,7 @@
 #endif
 
 #include "common/system.h"
+#include "ags/lib/std/algorithm.h"
 #include "ags/shared/ac/common.h" // quit
 #include "ags/shared/ac/game_struct_defines.h"
 #include "ags/shared/ac/sprite_cache.h"
@@ -50,7 +51,7 @@ using namespace AGS::Shared;
 
 // [IKM] We have to forward-declare these because their implementations are in the Engine
 extern void initialize_sprite(int);
-extern void pre_save_sprite(int);
+extern void pre_save_sprite(Bitmap *image);
 extern void get_new_size_for_sprite(int, int, int, int &, int &);
 
 #define START_OF_LIST -1
@@ -59,9 +60,8 @@ extern void get_new_size_for_sprite(int, int, int, int &, int &);
 const char *spindexid = "SPRINDEX";
 
 // TODO: should not be part of SpriteCache, but rather some asset management class?
-const String SpriteCache::DefaultSpriteFileName = "acsprset.spr";
-const String SpriteCache::DefaultSpriteIndexName = "sprindex.dat";
-
+const char *const SpriteFile::DefaultSpriteFileName = "acsprset.spr";
+const char *const SpriteFile::DefaultSpriteIndexName = "sprindex.dat";
 
 SpriteInfo::SpriteInfo()
 	: Flags(0)
@@ -70,8 +70,7 @@ SpriteInfo::SpriteInfo()
 }
 
 SpriteCache::SpriteData::SpriteData()
-	: Offset(0)
-	, Size(0)
+	: Size(0)
 	, Flags(0)
 	, Image(nullptr) {
 }
@@ -82,9 +81,13 @@ SpriteCache::SpriteData::~SpriteData() {
 }
 
 
+SpriteFile::SpriteFile() {
+	_compressed = false;
+	_curPos = -2;
+}
+
 SpriteCache::SpriteCache(std::vector<SpriteInfo> &sprInfos)
 	: _sprInfos(sprInfos) {
-	_compressed = false;
 	Init();
 }
 
@@ -108,10 +111,10 @@ size_t SpriteCache::GetSpriteSlotCount() const {
 	return _spriteData.size();
 }
 
-sprkey_t SpriteCache::FindTopmostSprite() const {
+sprkey_t SpriteFile::FindTopmostSprite(const std::vector<Bitmap *> &sprites) {
 	sprkey_t topmost = -1;
-	for (sprkey_t i = 0; i < static_cast<sprkey_t>(_spriteData.size()); ++i)
-		if (DoesSpriteExist(i))
+	for (sprkey_t i = 0; i < static_cast<sprkey_t>(sprites.size()); ++i)
+		if (sprites[i])
 			topmost = i;
 	return topmost;
 }
@@ -126,11 +129,10 @@ void SpriteCache::Init() {
 	_maxCacheSize = (size_t)DEFAULTCACHESIZE_KB * 1024;
 	_liststart = -1;
 	_listend = -1;
-	_lastLoad = -2;
 }
 
 void SpriteCache::Reset() {
-	_stream.reset();
+	_file.Reset();
 	// TODO: find out if it's safe to simply always delete _spriteData.Image with array element
 	for (size_t i = 0; i < _spriteData.size(); ++i) {
 		if (_spriteData[i].Image) {
@@ -157,7 +159,6 @@ void SpriteCache::SetSprite(sprkey_t index, Bitmap *sprite) {
 	}
 	_spriteData[index].Image = sprite;
 	_spriteData[index].Flags = SPRCACHEFLAG_LOCKED; // NOT from asset file
-	_spriteData[index].Offset = 0;
 	_spriteData[index].Size = 0;
 #ifdef DEBUG_SPRITECACHE
 	Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "SetSprite: (external) %d", index);
@@ -174,7 +175,7 @@ void SpriteCache::SetEmptySprite(sprkey_t index, bool as_asset) {
 	RemapSpriteToSprite0(index);
 }
 
-void SpriteCache::SubstituteBitmap(sprkey_t index, Shared::Bitmap *sprite) {
+void SpriteCache::SubstituteBitmap(sprkey_t index, Bitmap *sprite) {
 	if (!DoesSpriteExist(index)) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SubstituteBitmap: attempt to set for non-existing sprite %d", index);
 		return;
@@ -223,7 +224,7 @@ sprkey_t SpriteCache::GetFreeIndex() {
 
 bool SpriteCache::SpriteData::DoesSpriteExist() const {
 	return (Image != nullptr) || // HAS loaded bitmap
-	       ((Flags & SPRCACHEFLAG_ISASSET) != 0); // OR found in the game resources
+		((Flags & SPRCACHEFLAG_ISASSET) != 0); // OR found in the game resources
 }
 
 bool SpriteCache::SpriteData::IsAssetSprite() const {
@@ -232,8 +233,8 @@ bool SpriteCache::SpriteData::IsAssetSprite() const {
 
 bool SpriteCache::SpriteData::IsExternalSprite() const {
 	return (Image != nullptr) &&  // HAS loaded bitmap
-	       ((Flags & SPRCACHEFLAG_ISASSET) == 0) && // AND NOT found in game resources
-	       ((Flags & SPRCACHEFLAG_REMAPPED) == 0); // AND was NOT remapped to another sprite
+		((Flags & SPRCACHEFLAG_ISASSET) == 0) && // AND NOT found in game resources
+		((Flags & SPRCACHEFLAG_REMAPPED) == 0); // AND was NOT remapped to another sprite
 }
 
 bool SpriteCache::SpriteData::IsLocked() const {
@@ -244,7 +245,7 @@ bool SpriteCache::DoesSpriteExist(sprkey_t index) const {
 	return index >= 0 && (size_t)index < _spriteData.size() && _spriteData[index].DoesSpriteExist();
 }
 
-Bitmap *SpriteCache::operator [](sprkey_t index) {
+Bitmap *SpriteCache::operator [] (sprkey_t index) {
 	// invalid sprite slot
 	if (index < 0 || (size_t)index >= _spriteData.size())
 		return nullptr;
@@ -327,7 +328,7 @@ void SpriteCache::DisposeOldest() {
 			// There must be a bug somewhere causing this, but for now
 			// let's just reset the cache
 			Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "RUNTIME CACHE ERROR: CACHE INCONSISTENT: RESETTING\n\tAt size %d (of %d), start %d end %d  fwdlink=%d",
-			              _cacheSize, _maxCacheSize, oldstart, _listend, _liststart);
+				_cacheSize, _maxCacheSize, oldstart, _listend, _liststart);
 			DisposeAll();
 		}
 	}
@@ -342,7 +343,8 @@ void SpriteCache::DisposeAll() {
 	_listend = -1;
 	for (size_t i = 0; i < _spriteData.size(); ++i) {
 		if (!_spriteData[i].IsLocked() && // not locked
-		        _spriteData[i].IsAssetSprite()) { // sprite from game resource
+			_spriteData[i].IsAssetSprite()) // sprite from game resource
+		{
 			delete _spriteData[i].Image;
 			_spriteData[i].Image = nullptr;
 		}
@@ -378,10 +380,12 @@ sprkey_t SpriteCache::GetDataIndex(sprkey_t index) {
 	return (_spriteData[index].Flags & SPRCACHEFLAG_REMAPPED) == 0 ? index : 0;
 }
 
-void SpriteCache::SeekToSprite(sprkey_t index) {
+void SpriteFile::SeekToSprite(sprkey_t index) {
 	// If we didn't just load the previous sprite, seek to it
-	if (index - 1 != _lastLoad)
+	if (index != _curPos) {
 		_stream->Seek(_spriteData[index].Offset, kSeekBegin);
+		_curPos = index;
+	}
 }
 
 size_t SpriteCache::LoadSprite(sprkey_t index) {
@@ -400,54 +404,21 @@ size_t SpriteCache::LoadSprite(sprkey_t index) {
 		quit("sprite cache array index out of bounds");
 
 	sprkey_t load_index = GetDataIndex(index);
-	SeekToSprite(load_index);
-
-	int coldep = _stream->ReadInt16();
-
-	if (coldep == 0) {
-		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "LoadSprite: asked to load sprite %d (for slot %d) which does not exist.", load_index, index);
-		_lastLoad = load_index;
-		return 0;
-	}
-
-	int wdd = _stream->ReadInt16();
-	int htt = _stream->ReadInt16();
-	Bitmap *image = BitmapHelper::CreateBitmap(wdd, htt, coldep * 8);
-	if (image == nullptr) {
-		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn, "LoadSprite: failed to init sprite %d, remapping to sprite 0.", index);
+	Bitmap *image;
+	HError err = _file.LoadSprite(load_index, image);
+	if (!image) {
+		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
+			"LoadSprite: failed to load sprite %d:\n%s\n - remapping to sprite 0.", index,
+			err ? err->FullMessage().GetCStr() : "Sprite does not exist.");
 		RemapSpriteToSprite0(index);
 		return 0;
 	}
 
-	if (this->_compressed) {
-		size_t data_size = _stream->ReadInt32();
-		if (data_size == 0) {
-			Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn, "LoadSprite: bad compressed data for sprite %d, remapping to sprite 0.", index);
-			delete image;
-			RemapSpriteToSprite0(index);
-			return 0;
-		}
-		UnCompressSprite(image, _stream.get());
-	} else {
-		if (coldep == 1) {
-			for (hh = 0; hh < htt; hh++)
-				_stream->ReadArray(&image->GetScanLineForWriting(hh)[0], coldep, wdd);
-		} else if (coldep == 2) {
-			for (hh = 0; hh < htt; hh++)
-				_stream->ReadArrayOfInt16((int16_t *)&image->GetScanLineForWriting(hh)[0], wdd);
-		} else {
-			for (hh = 0; hh < htt; hh++)
-				_stream->ReadArrayOfInt32((int32_t *)&image->GetScanLineForWriting(hh)[0], wdd);
-		}
-	}
-
 	// update the stored width/height
-	_sprInfos[index].Width = wdd;
-	_sprInfos[index].Height = htt;
+	_sprInfos[index].Width = image->GetWidth();
+	_sprInfos[index].Height = image->GetHeight();
 	_spriteData[index].Image = image;
 
-	_lastLoad = load_index;
-
 	// Stop it adding the sprite to the used list just because it's loaded
 	// TODO: this messy hack is required, because initialize_sprite calls operator[]
 	// which puts the sprite to the MRU list.
@@ -463,7 +434,8 @@ size_t SpriteCache::LoadSprite(sprkey_t index) {
 
 	// we need to store this because the main program might
 	// alter spritewidth/height if it resizes stuff
-	size_t size = _sprInfos[index].Width * _sprInfos[index].Height * coldep;
+	size_t size = _sprInfos[index].Width * _sprInfos[index].Height *
+		_spriteData[index].Image->GetBPP();
 	_spriteData[index].Size = size;
 	_cacheSize += size;
 
@@ -479,7 +451,6 @@ void SpriteCache::RemapSpriteToSprite0(sprkey_t index) {
 	_sprInfos[index].Width = _sprInfos[0].Width;
 	_sprInfos[index].Height = _sprInfos[0].Height;
 	_spriteData[index].Image = nullptr;
-	_spriteData[index].Offset = _spriteData[0].Offset;
 	_spriteData[index].Size = _spriteData[0].Size;
 	_spriteData[index].Flags |= SPRCACHEFLAG_REMAPPED;
 #ifdef DEBUG_SPRITECACHE
@@ -489,47 +460,42 @@ void SpriteCache::RemapSpriteToSprite0(sprkey_t index) {
 
 const char *spriteFileSig = " Sprite File ";
 
-void SpriteCache::CompressSprite(Bitmap *sprite, Stream *out) {
+void SpriteFile::CompressSprite(Bitmap *sprite, Stream *out) {
 	const int depth = sprite->GetBPP();
 	if (depth == 1) {
 		for (int y = 0; y < sprite->GetHeight(); y++)
 			cpackbitl(&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), out);
 	} else if (depth == 2) {
 		for (int y = 0; y < sprite->GetHeight(); y++)
-			cpackbitl16((const uint16_t *)&sprite->GetScanLine(y)[0], sprite->GetWidth(), out);
+			cpackbitl16((unsigned short *)&sprite->GetScanLine(y)[0], sprite->GetWidth(), out);
 	} else {
 		for (int y = 0; y < sprite->GetHeight(); y++)
-			cpackbitl32((const uint32_t *)&sprite->GetScanLine(y)[0], sprite->GetWidth(), out);
+			cpackbitl32((unsigned int *)&sprite->GetScanLine(y)[0], sprite->GetWidth(), out);
 	}
 }
 
-void SpriteCache::UnCompressSprite(Bitmap *sprite, Stream *in) {
+void SpriteFile::UnCompressSprite(Bitmap *sprite, Stream *in) {
 	const int depth = sprite->GetBPP();
 	if (depth == 1) {
 		for (int y = 0; y < sprite->GetHeight(); y++)
 			cunpackbitl(&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), in);
 	} else if (depth == 2) {
 		for (int y = 0; y < sprite->GetHeight(); y++)
-			cunpackbitl16((uint16_t *)&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), in);
+			cunpackbitl16((unsigned short *)&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), in);
 	} else {
 		for (int y = 0; y < sprite->GetHeight(); y++)
-			cunpackbitl32((uint32_t *)&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), in);
+			cunpackbitl32((unsigned int *)&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), in);
 	}
 }
 
-int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteFileIndex &index) {
-	Stream *output = Shared::File::CreateFile(filename);
+int SpriteFile::SaveToFile(const String &save_to_file,
+	const std::vector<Bitmap *> &sprites,
+	SpriteFile *read_from_file,
+	bool compressOutput, SpriteFileIndex &index) {
+	std::unique_ptr<Stream> output(File::CreateFile(save_to_file));
 	if (output == nullptr)
 		return -1;
 
-	if (compressOutput) {
-		// re-open the file so that it can be seeked
-		delete output;
-		output = File::OpenFile(filename, Shared::kFile_Open, Shared::kFile_ReadWrite); // CHECKME why mode was "r+" here?
-		if (output == nullptr)
-			return -1;
-	}
-
 	int spriteFileIDCheck = g_system->getMillis();
 
 	// sprite file version
@@ -540,7 +506,8 @@ int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteF
 	output->WriteInt8(compressOutput ? 1 : 0);
 	output->WriteInt32(spriteFileIDCheck);
 
-	sprkey_t lastslot = FindTopmostSprite();
+	sprkey_t lastslot = read_from_file ? read_from_file->GetTopmostSprite() : 0;
+	lastslot = std::max(lastslot, FindTopmostSprite(sprites));
 	output->WriteInt32(lastslot);
 
 	// allocate buffers to store the indexing info
@@ -550,25 +517,32 @@ int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteF
 	spritewidths.resize(numsprits);
 	spriteheights.resize(numsprits);
 	spriteoffs.resize(numsprits);
+	std::unique_ptr<Bitmap> temp_bmp; // for disposing temp sprites
+	std::vector<char> membuf; // for loading raw sprite data
 
-	const size_t memBufferSize = 100000;
-	char *memBuffer = new char[memBufferSize];
+	const bool diff_compress =
+		read_from_file && read_from_file->IsFileCompressed() != compressOutput;
 
 	for (sprkey_t i = 0; i <= lastslot; ++i) {
-		spriteoffs[i] = output->GetPosition();
+		soff_t sproff = output->GetPosition();
+
+		Bitmap *image = (size_t)i < sprites.size() ? sprites[i] : nullptr;
 
-		// if compressing uncompressed sprites, load the sprite into memory
-		if ((_spriteData[i].Image == nullptr) && (this->_compressed != compressOutput))
-			(*this)[i];
+		// if compression setting is different, load the sprite into memory
+		// (otherwise we will be able to simply copy bytes from one file to another
+		if ((image == nullptr) && diff_compress) {
+			read_from_file->LoadSprite(i, image);
+			temp_bmp.reset(image);
+		}
 
-		if (_spriteData[i].Image != nullptr) {
+		// if managed to load an image - save it according the new compression settings
+		if (image != nullptr) {
 			// image in memory -- write it out
-			pre_save_sprite(i);
-			Bitmap *image = _spriteData[i].Image;
-			int bpss = image->GetColorDepth() / 8;
+			int bpp = image->GetColorDepth() / 8;
+			spriteoffs[i] = sproff;
 			spritewidths[i] = image->GetWidth();
 			spriteheights[i] = image->GetHeight();
-			output->WriteInt16(bpss);
+			output->WriteInt16(bpp);
 			output->WriteInt16(spritewidths[i]);
 			output->WriteInt16(spriteheights[i]);
 
@@ -577,7 +551,7 @@ int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteF
 				// write some space for the length data
 				output->WriteInt32(0);
 
-				CompressSprite(image, output);
+				CompressSprite(image, output.get());
 
 				soff_t fileSizeSoFar = output->GetPosition();
 				// write the length of the compressed data
@@ -585,71 +559,37 @@ int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteF
 				output->WriteInt32((fileSizeSoFar - lenloc) - 4);
 				output->Seek(0, kSeekEnd);
 			} else {
-				output->WriteArray(image->GetDataForWriting(), spritewidths[i] * bpss, spriteheights[i]);
+				output->WriteArray(image->GetDataForWriting(), spritewidths[i] * bpp, spriteheights[i]);
 			}
 			continue;
-		}
-
-		if (_spriteData[i].Offset == 0) {
+		} else if (diff_compress) {
 			// sprite doesn't exist
 			output->WriteInt16(0); // colour depth
-			spritewidths[i] = 0;
-			spriteheights[i] = 0;
-			spriteoffs[i] = 0;
-			continue;
-		}
-
-		// not in memory -- seek to it in the source file
-		sprkey_t load_index = GetDataIndex(i);
-		SeekToSprite(load_index);
-		_lastLoad = load_index;
-
-		short colDepth = _stream->ReadInt16();
-		output->WriteInt16(colDepth);
-
-		if (colDepth == 0)
 			continue;
-
-		if (this->_compressed != compressOutput) {
-			// shouldn't be able to get here
-			delete[] memBuffer;
-			delete output;
-			return -2;
 		}
 
-		// and copy the data across
-		int width = _stream->ReadInt16();
-		int height = _stream->ReadInt16();
-
-		spritewidths[i] = width;
-		spriteheights[i] = height;
+		// Not in memory - and same compression option;
+		// Directly copy the sprite bytes from the input file to the output
+		Size metric;
+		int bpp;
+		read_from_file->LoadSpriteData(i, metric, bpp, membuf);
 
-		output->WriteInt16(width);
-		output->WriteInt16(height);
-
-		size_t sizeToCopy;
-		if (this->_compressed) {
-			sizeToCopy = _stream->ReadInt32();
-			output->WriteInt32(sizeToCopy);
-			if (sizeToCopy == 0)
-				continue; // bad data?
-		} else {
-			sizeToCopy = width * height * (int)colDepth;
-		}
-
-		while (sizeToCopy > memBufferSize) {
-			_stream->ReadArray(memBuffer, memBufferSize, 1);
-			output->WriteArray(memBuffer, memBufferSize, 1);
-			sizeToCopy -= memBufferSize;
-		}
+		output->WriteInt16(bpp);
+		if (bpp == 0)
+			continue; // empty slot
 
-		_stream->ReadArray(memBuffer, sizeToCopy, 1);
-		output->WriteArray(memBuffer, sizeToCopy, 1);
+		spriteoffs[i] = sproff;
+		spritewidths[i] = metric.Width;
+		spriteheights[i] = metric.Height;
+		output->WriteInt16(metric.Width);
+		output->WriteInt16(metric.Height);
+		if (compressOutput)
+			output->WriteInt32(membuf.size());
+		if (membuf.size() == 0)
+			continue; // bad data?
+		output->Write(&membuf[0], membuf.size());
 	}
 
-	delete[] memBuffer;
-	delete output;
-
 	index.SpriteFileIDCheck = spriteFileIDCheck;
 	index.LastSlot = lastslot;
 	index.SpriteCount = numsprits;
@@ -659,7 +599,21 @@ int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteF
 	return 0;
 }
 
-int SpriteCache::SaveSpriteIndex(const String &filename, const SpriteFileIndex &index) {
+int SpriteCache::SaveToFile(const String &filename, bool compressOutput, SpriteFileIndex &index) {
+	std::vector<Bitmap *> sprites;
+	for (const auto &data : _spriteData) {
+		// NOTE: this is a horrible hack:
+		// because Editor expects slightly different RGB order, it swaps colors
+		// when loading them (call to initialize_sprite), so here we basically
+		// unfix that fix to save the data in a way that engine will expect.
+		// TODO: perhaps adjust the editor to NOT need this?!
+		pre_save_sprite(data.Image);
+		sprites.push_back(data.Image);
+	}
+	return _file.SaveToFile(filename, sprites, &_file, compressOutput, index);
+}
+
+int SpriteFile::SaveSpriteIndex(const String &filename, const SpriteFileIndex &index) {
 	// write the sprite index file
 	Stream *out = File::CreateFile(filename);
 	if (!out)
@@ -683,79 +637,40 @@ int SpriteCache::SaveSpriteIndex(const String &filename, const SpriteFileIndex &
 }
 
 HError SpriteCache::InitFile(const String &filename, const String &sprindex_filename) {
-	SpriteFileVersion vers;
-	char buff[20];
-	soff_t spr_initial_offs = 0;
-	int spriteFileID = 0;
-
-	_stream.reset(_GP(AssetMgr)->OpenAsset(filename));
-	if (_stream == nullptr)
-		return new Error(String::FromFormat("Failed to open spriteset file '%s'.", filename.GetCStr()));
-
-	spr_initial_offs = _stream->GetPosition();
-
-	vers = (SpriteFileVersion)_stream->ReadInt16();
-	// read the "Sprite File" signature
-	_stream->ReadArray(&buff[0], 13, 1);
-
-	if (vers < kSprfVersion_Uncompressed || vers > kSprfVersion_Current) {
-		_stream.reset();
-		return new Error(String::FromFormat("Unsupported _GP(spriteset) format (requested %d, supported %d - %d).", vers, kSprfVersion_Uncompressed, kSprfVersion_Current));
-	}
-
-	// unknown version
-	buff[13] = 0;
-	if (strcmp(buff, spriteFileSig)) {
-		_stream.reset();
-		return new Error("Uknown _GP(spriteset) format.");
-	}
-
-	if (vers == kSprfVersion_Uncompressed) {
-		this->_compressed = false;
-	} else if (vers == kSprfVersion_Compressed) {
-		this->_compressed = true;
-	} else if (vers >= kSprfVersion_Last32bit) {
-		this->_compressed = (_stream->ReadInt8() == 1);
-		spriteFileID = _stream->ReadInt32();
-	}
-
-	if (vers < kSprfVersion_Compressed) {
-		// skip the palette
-		_stream->Seek(256 * 3); // sizeof(RGB) * 256
-	}
-
-	sprkey_t topmost;
-	if (vers < kSprfVersion_HighSpriteLimit)
-		topmost = (uint16_t)_stream->ReadInt16();
-	else
-		topmost = _stream->ReadInt32();
-	if (vers < kSprfVersion_Uncompressed)
-		topmost = 200;
+	std::vector<Size> metrics;
+	HError err = _file.OpenFile(filename, sprindex_filename, metrics);
+	if (!err)
+		return err;
 
-	EnlargeTo(topmost);
-
-	// if there is a sprite index file, use it
-	if (LoadSpriteIndexFile(sprindex_filename, spriteFileID, spr_initial_offs, topmost)) {
-		// Succeeded
-		return HError::None();
+	// Initialize sprite infos
+	size_t newsize = metrics.size();
+	_sprInfos.resize(newsize);
+	_spriteData.resize(newsize);
+	_mrulist.resize(newsize);
+	_mrubacklink.resize(newsize);
+	for (size_t i = 0; i < metrics.size(); ++i) {
+		if (!metrics[i].IsNull()) {
+			// Existing sprite
+			_spriteData[i].Flags = SPRCACHEFLAG_ISASSET;
+			_spriteData[i].Image = nullptr;
+			get_new_size_for_sprite(i, metrics[i].Width, metrics[i].Height, _sprInfos[i].Width, _sprInfos[i].Height);
+		} else {
+			// Handle empty slot: remap to sprite 0
+			if (i > 0) // FIXME: optimize
+				InitNullSpriteParams(i);
+		}
 	}
-
-	// Failed, index file is invalid; index sprites manually
-	return RebuildSpriteIndex(_stream.get(), topmost, vers);
+	return HError::None();
 }
 
-HError SpriteCache::RebuildSpriteIndex(AGS::Shared::Stream *in, sprkey_t topmost, SpriteFileVersion vers) {
+HError SpriteFile::RebuildSpriteIndex(Stream *in, sprkey_t topmost,
+	SpriteFileVersion vers, std::vector<Size> &metrics) {
 	for (sprkey_t i = 0; i <= topmost; ++i) {
 		_spriteData[i].Offset = in->GetPosition();
-		_spriteData[i].Flags = 0;
 
 		int coldep = in->ReadInt16();
 
 		if (coldep == 0) {
-			// Empty slot
-			if (i > 0)
-				InitNullSpriteParams(i);
-
 			if (in->EOS())
 				break;
 			continue;
@@ -767,14 +682,10 @@ HError SpriteCache::RebuildSpriteIndex(AGS::Shared::Stream *in, sprkey_t topmost
 		if ((size_t)i >= _spriteData.size())
 			break;
 
-		_spriteData[i].Flags = SPRCACHEFLAG_ISASSET;
-		_spriteData[i].Image = nullptr;
-
 		int wdd = in->ReadInt16();
 		int htt = in->ReadInt16();
-		_sprInfos[i].Width = wdd;
-		_sprInfos[i].Height = htt;
-		get_new_size_for_sprite(i, wdd, htt, _sprInfos[i].Width, _sprInfos[i].Height);
+		metrics[i].Width = wdd;
+		metrics[i].Height = htt;
 
 		size_t spriteDataSize;
 		if (vers == kSprfVersion_Compressed) {
@@ -789,7 +700,8 @@ HError SpriteCache::RebuildSpriteIndex(AGS::Shared::Stream *in, sprkey_t topmost
 	return HError::None();
 }
 
-bool SpriteCache::LoadSpriteIndexFile(const String &filename, int expectedFileID, soff_t spr_initial_offs, sprkey_t topmost) {
+bool SpriteFile::LoadSpriteIndexFile(const String &filename, int expectedFileID,
+	soff_t spr_initial_offs, sprkey_t topmost, std::vector<Size> &metrics) {
 	Stream *fidx = _GP(AssetMgr)->OpenAsset(filename);
 	if (fidx == nullptr) {
 		return false;
@@ -829,50 +741,190 @@ bool SpriteCache::LoadSpriteIndexFile(const String &filename, int expectedFileID
 	}
 
 	sprkey_t numsprits = topmost_index + 1;
-	short *rspritewidths = new short[numsprits];
-	short *rspriteheights = new short[numsprits];
-	soff_t *spriteoffs = new soff_t[numsprits];
+	std::vector<int16_t> rspritewidths; rspritewidths.resize(numsprits);
+	std::vector<int16_t> rspriteheights; rspriteheights.resize(numsprits);
+	std::vector<soff_t>  spriteoffs; spriteoffs.resize(numsprits);
 
 	fidx->ReadArrayOfInt16(&rspritewidths[0], numsprits);
 	fidx->ReadArrayOfInt16(&rspriteheights[0], numsprits);
 	if (vers <= kSpridxfVersion_Last32bit) {
 		for (sprkey_t i = 0; i < numsprits; ++i)
 			spriteoffs[i] = fidx->ReadInt32();
-	} else { // large file support
-		fidx->ReadArrayOfInt64(spriteoffs, numsprits);
+	} else // large file support
+	{
+		fidx->ReadArrayOfInt64(&spriteoffs[0], numsprits);
 	}
+	delete fidx;
 
 	for (sprkey_t i = 0; i <= topmost_index; ++i) {
 		if (spriteoffs[i] != 0) {
-			_spriteData[i].Flags = SPRCACHEFLAG_ISASSET;
 			_spriteData[i].Offset = spriteoffs[i] + spr_initial_offs;
-			get_new_size_for_sprite(i, rspritewidths[i], rspriteheights[i], _sprInfos[i].Width, _sprInfos[i].Height);
-		} else if (i > 0) {
-			InitNullSpriteParams(i);
+			metrics[i].Width = rspritewidths[i];
+			metrics[i].Height = rspriteheights[i];
 		}
 	}
-
-	delete[] rspritewidths;
-	delete[] rspriteheights;
-	delete[] spriteoffs;
-	delete fidx;
 	return true;
 }
 
 void SpriteCache::DetachFile() {
+	_file.Reset();
+}
+
+bool SpriteFile::IsFileCompressed() const {
+	return _compressed;
+}
+
+sprkey_t SpriteFile::GetTopmostSprite() const {
+	return (sprkey_t)_spriteData.size() - 1;
+}
+
+void SpriteFile::Reset() {
 	_stream.reset();
-	_lastLoad = -2;
+	_curPos = -2;
+}
+
+HAGSError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
+	sprite = nullptr;
+	if (index < 0 || (size_t)index >= _spriteData.size())
+		new Error(String::FromFormat("LoadSprite: slot index %d out of bounds (%d - %d).",
+			index, 0, _spriteData.size() - 1));
+
+	SeekToSprite(index);
+	_curPos = -2; // mark undefined pos
+
+	int coldep = _stream->ReadInt16();
+	if (coldep == 0) { // empty slot, this is normal
+		return HError::None();
+	}
+
+	int wdd = _stream->ReadInt16();
+	int htt = _stream->ReadInt16();
+	Bitmap *image = BitmapHelper::CreateBitmap(wdd, htt, coldep * 8);
+	if (image == nullptr) {
+		return new Error(String::FromFormat("LoadSprite: failed to allocate bitmap %d (%dx%d%d).",
+			index, wdd, htt, coldep * 8));
+	}
+
+	if (_compressed) {
+		size_t data_size = _stream->ReadInt32();
+		if (data_size == 0) {
+			delete image;
+			return new Error(String::FromFormat("LoadSprite: bad compressed data for sprite %d.", index));
+		}
+		UnCompressSprite(image, _stream.get());
+	} else {
+		if (coldep == 1) {
+			for (int h = 0; h < htt; ++h)
+				_stream->ReadArray(&image->GetScanLineForWriting(h)[0], coldep, wdd);
+		} else if (coldep == 2) {
+			for (int h = 0; h < htt; ++h)
+				_stream->ReadArrayOfInt16((int16_t *)&image->GetScanLineForWriting(h)[0], wdd);
+		} else {
+			for (int h = 0; h < htt; ++h)
+				_stream->ReadArrayOfInt32((int32_t *)&image->GetScanLineForWriting(h)[0], wdd);
+		}
+	}
+	sprite = image;
+	_curPos = index + 1; // mark correct pos
+	return HError::None();
+}
+
+HError SpriteFile::LoadSpriteData(sprkey_t index, Size &metric, int &bpp,
+	std::vector<char> &data) {
+	if (index < 0 || (size_t)index >= _spriteData.size())
+		new Error(String::FromFormat("LoadSprite: slot index %d out of bounds (%d - %d).",
+			index, 0, _spriteData.size() - 1));
+
+	SeekToSprite(index);
+	_curPos = -2; // mark undefined pos
+
+	int coldep = _stream->ReadInt16();
+	if (coldep == 0) { // empty slot, this is normal
+		metric = Size();
+		bpp = 0;
+		data.resize(0);
+		return HError::None();
+	}
+
+	int width = _stream->ReadInt16();
+	int height = _stream->ReadInt16();
+
+	size_t data_size;
+	if (_compressed)
+		data_size = _stream->ReadInt32();
+	else
+		data_size = width * height * coldep;
+	data.resize(data_size);
+	_stream->Read(&data[0], data_size);
+	metric = Size(width, height);
+	bpp = coldep;
+	_curPos = index + 1; // mark correct pos
+	return HError::None();
 }
 
-int SpriteCache::AttachFile(const String &filename) {
+HAGSError SpriteFile::OpenFile(const String &filename, const String &sprindex_filename,
+	std::vector<Size> &metrics) {
+	SpriteFileVersion vers;
+	char buff[20];
+	soff_t spr_initial_offs = 0;
+	int spriteFileID = 0;
+
 	_stream.reset(_GP(AssetMgr)->OpenAsset(filename));
 	if (_stream == nullptr)
-		return -1;
-	return 0;
-}
+		return new Error(String::FromFormat("Failed to open spriteset file '%s'.", filename.GetCStr()));
 
-bool SpriteCache::IsFileCompressed() const {
-	return _compressed;
+	spr_initial_offs = _stream->GetPosition();
+
+	vers = (SpriteFileVersion)_stream->ReadInt16();
+	// read the "Sprite File" signature
+	_stream->ReadArray(&buff[0], 13, 1);
+
+	if (vers < kSprfVersion_Uncompressed || vers > kSprfVersion_Current) {
+		_stream.reset();
+		return new Error(String::FromFormat("Unsupported spriteset format (requested %d, supported %d - %d).", vers, kSprfVersion_Uncompressed, kSprfVersion_Current));
+	}
+
+	// unknown version
+	buff[13] = 0;
+	if (strcmp(buff, spriteFileSig)) {
+		_stream.reset();
+		return new Error("Uknown spriteset format.");
+	}
+
+	if (vers == kSprfVersion_Uncompressed) {
+		this->_compressed = false;
+	} else if (vers == kSprfVersion_Compressed) {
+		this->_compressed = true;
+	} else if (vers >= kSprfVersion_Last32bit) {
+		this->_compressed = (_stream->ReadInt8() == 1);
+		spriteFileID = _stream->ReadInt32();
+	}
+
+	if (vers < kSprfVersion_Compressed) {
+		// skip the palette
+		_stream->Seek(256 * 3); // sizeof(RGB) * 256
+	}
+
+	sprkey_t topmost;
+	if (vers < kSprfVersion_HighSpriteLimit)
+		topmost = (uint16_t)_stream->ReadInt16();
+	else
+		topmost = _stream->ReadInt32();
+	if (vers < kSprfVersion_Uncompressed)
+		topmost = 200;
+
+	_spriteData.resize(topmost + 1);
+	metrics.resize(topmost + 1);
+
+	// if there is a sprite index file, use it
+	if (LoadSpriteIndexFile(sprindex_filename, spriteFileID,
+		spr_initial_offs, topmost, metrics)) {
+		// Succeeded
+		return HError::None();
+	}
+
+	// Failed, index file is invalid; index sprites manually
+	return RebuildSpriteIndex(_stream.get(), topmost, vers, metrics);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index 67bd74b4d2..0eb9ee89c0 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -23,11 +23,9 @@
 //
 // Sprite caching system.
 //
-// TODO: split out sprite serialization into something like "SpriteFile" class.
-// SpriteCache should only manage caching and provide bitmaps by demand.
-// There should be a separate unit which manages streaming and decompressing.
-// Perhaps an interface which would allow multiple implementation depending
-// on compression type.
+// SpriteFile handles sprite serialization and streaming.
+// SpriteCache provides bitmaps by demand; it uses SpriteFile to load sprites
+// and does MRU (most-recent-use) caching.
 //
 // TODO: store sprite data in a specialized container type that is optimized
 // for having most keys allocated in large continious sequences by default.
@@ -48,6 +46,7 @@
 #include "ags/lib/std/vector.h"
 #include "ags/shared/core/platform.h"
 #include "ags/shared/util/error.h"
+#include "ags/shared/util/geometry.h"
 
 namespace AGS3 {
 
@@ -109,6 +108,64 @@ struct SpriteFileIndex {
 	std::vector<soff_t>  Offsets;
 };
 
+class SpriteFile {
+public:
+	// Standart sprite file and sprite index names
+	static const char *const DefaultSpriteFileName;
+	static const char *const DefaultSpriteIndexName;
+
+	SpriteFile();
+	// Loads sprite reference information and inits sprite stream
+	HAGSError   OpenFile(const Shared::String &filename, const Shared::String &sprindex_filename,
+		std::vector<Size> &metrics);
+	void        Reset();
+
+	// Tells if bitmaps in the file are compressed
+	bool        IsFileCompressed() const;
+	// Tells the highest known sprite index
+	sprkey_t    GetTopmostSprite() const;
+
+	// Loads sprite index file
+	bool        LoadSpriteIndexFile(const Shared::String &filename, int expectedFileID,
+		soff_t spr_initial_offs, sprkey_t topmost, std::vector<Size> &metrics);
+	// Rebuilds sprite index from the main sprite file
+	HAGSError   RebuildSpriteIndex(AGS::Shared::Stream *in, sprkey_t topmost, SpriteFileVersion vers,
+		std::vector<Size> &metrics);
+
+	HAGSError LoadSprite(sprkey_t index, Shared::Bitmap *&sprite);
+	HAGSError LoadSpriteData(sprkey_t index, Size &metric, int &bpp, std::vector<char> &data);
+
+	// Saves all sprites to file; fills in index data for external use
+	// TODO: refactor to be able to save main file and index file separately (separate function for gather data?)
+	static int  SaveToFile(const Shared::String &save_to_file,
+		const std::vector<Shared::Bitmap *> &sprites, // available sprites (may contain nullptrs)
+		SpriteFile *read_from_file, // optional file to read missing sprites from
+		bool compressOutput, SpriteFileIndex &index);
+	// Saves sprite index table in a separate file
+	static int  SaveSpriteIndex(const Shared::String &filename, const SpriteFileIndex &index);
+
+private:
+	// Finds the topmost occupied slot index. Warning: may be slow.
+	static sprkey_t FindTopmostSprite(const std::vector<Shared::Bitmap *> &sprites);
+	// Seek stream to sprite
+	void        SeekToSprite(sprkey_t index);
+	// Writes compressed sprite to the stream
+	static void CompressSprite(Shared::Bitmap *sprite, Shared::Stream *out);
+	// Uncompresses sprite from stream into the given bitmap
+	static void UnCompressSprite(Shared::Bitmap *sprite, Shared::Stream *in);
+
+	// Internal sprite reference
+	struct SpriteRef {
+		soff_t Offset = 0; // data offset
+		size_t Size = 0;   // cache size of element, in bytes
+	};
+
+	// Array of sprite references
+	std::vector<SpriteRef> _spriteData;
+	std::unique_ptr<Shared::Stream> _stream; // the sprite stream
+	bool _compressed; // are sprites compressed
+	sprkey_t _curPos; // current stream position (sprite slot)
+};
 
 class SpriteCache {
 public:
@@ -116,13 +173,20 @@ public:
 	static const sprkey_t MAX_SPRITE_INDEX = INT32_MAX - 1;
 	static const size_t   MAX_SPRITE_SLOTS = INT32_MAX;
 
-	// Standart sprite file and sprite index names
-	static const Shared::String DefaultSpriteFileName;
-	static const Shared::String DefaultSpriteIndexName;
-
 	SpriteCache(std::vector<SpriteInfo> &sprInfos);
 	~SpriteCache();
 
+	// Loads sprite reference information and inits sprite stream
+	HAGSError   InitFile(const Shared::String &filename, const Shared::String &sprindex_filename);
+	// Saves current cache contents to the file
+	int         SaveToFile(const Shared::String &filename, bool compressOutput, SpriteFileIndex &index);
+	// Closes an active sprite file stream
+	void        DetachFile();
+
+	inline bool IsFileCompressed() const {
+		return _file.IsFileCompressed();
+	}
+
 	// Tells if there is a sprite registered for the given index;
 	// this includes sprites that were explicitly assigned but failed to init and were remapped
 	bool        DoesSpriteExist(sprkey_t index) const;
@@ -139,8 +203,6 @@ public:
 	size_t      GetMaxCacheSize() const;
 	// Returns number of sprite slots in the bank (this includes both actual sprites and free slots)
 	size_t      GetSpriteSlotCount() const;
-	// Finds the topmost occupied slot index. Warning: may be slow.
-	sprkey_t    FindTopmostSprite() const;
 	// Loads sprite and and locks in memory (so it cannot get removed implicitly)
 	void        Precache(sprkey_t index);
 	// Remap the given index to the sprite 0
@@ -162,40 +224,23 @@ public:
 	// Sets max cache size in bytes
 	void        SetMaxCacheSize(size_t size);
 
-	// Loads sprite reference information and inits sprite stream
-	HAGSError   InitFile(const Shared::String &filename, const Shared::String &sprindex_filename);
-	// Tells if bitmaps in the file are compressed
-	bool        IsFileCompressed() const;
-	// Opens file stream
-	int         AttachFile(const Shared::String &filename);
-	// Closes file stream
-	void        DetachFile();
-	// Saves all sprites to file; fills in index data for external use
-	// TODO: refactor to be able to save main file and index file separately (separate function for gather data?)
-	int         SaveToFile(const Shared::String &filename, bool compressOutput, SpriteFileIndex &index);
-	// Saves sprite index table in a separate file
-	int         SaveSpriteIndex(const Shared::String &filename, const SpriteFileIndex &index);
-
 	// Loads (if it's not in cache yet) and returns bitmap by the sprite index
-	Shared::Bitmap *operator[](sprkey_t index);
+	Shared::Bitmap *operator[] (sprkey_t index);
 
 private:
 	void        Init();
+	// Load sprite from game resource
+	size_t      LoadSprite(sprkey_t index);
 	// Gets the index of a sprite which data is used for the given slot;
 	// in case of remapped sprite this will return the one given sprite is remapped to
 	sprkey_t    GetDataIndex(sprkey_t index);
-	// Load sprite from game resource
-	size_t      LoadSprite(sprkey_t index);
-	// Seek stream to sprite
-	void        SeekToSprite(sprkey_t index);
 	// Delete the oldest image in cache
 	void        DisposeOldest();
 
 	// Information required for the sprite streaming
 	// TODO: split into sprite cache and sprite stream data
 	struct SpriteData {
-		soff_t          Offset; // data offset
-		soff_t          Size;   // cache size of element, in bytes
+		size_t          Size; // to track cache size
 		uint32_t        Flags;
 		// TODO: investigate if we may safely use unique_ptr here
 		// (some of these bitmaps may be assigned from outside of the cache)
@@ -218,10 +263,8 @@ private:
 	std::vector<SpriteInfo> &_sprInfos;
 	// Array of sprite references
 	std::vector<SpriteData> _spriteData;
-	bool _compressed;        // are sprites compressed
 
-	std::unique_ptr<Shared::Stream> _stream; // the sprite stream
-	sprkey_t _lastLoad; // last loaded sprite index
+	SpriteFile _file;
 
 	size_t _maxCacheSize;  // cache size limit
 	size_t _lockedSize;    // size in bytes of currently locked images
@@ -235,15 +278,6 @@ private:
 	int _liststart;
 	int _listend;
 
-	// Loads sprite index file
-	bool        LoadSpriteIndexFile(const Shared::String &filename, int expectedFileID, soff_t spr_initial_offs, sprkey_t topmost);
-	// Rebuilds sprite index from the main sprite file
-	HAGSError   RebuildSpriteIndex(AGS::Shared::Stream *in, sprkey_t topmost, SpriteFileVersion vers);
-	// Writes compressed sprite to the stream
-	void        CompressSprite(Shared::Bitmap *sprite, Shared::Stream *out);
-	// Uncompresses sprite from stream into the given bitmap
-	void        UnCompressSprite(Shared::Bitmap *sprite, Shared::Stream *in);
-
 	// Initialize the empty sprite slot
 	void        InitNullSpriteParams(sprkey_t index);
 };


Commit: 7e322be3945f948f17883083b44e425dd7bf7177
    https://github.com/scummvm/scummvm/commit/7e322be3945f948f17883083b44e425dd7bf7177
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-07T22:07:09-07:00

Commit Message:
AGS: Moved image (de)compression functions out from SpriteFile

>From upstream a3c6e7cfded18daadab00474ff6d148904581f08

Changed paths:
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h
    engines/ags/shared/util/compress.cpp
    engines/ags/shared/util/compress.h


diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index a081c07ea9..945a26c5ac 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -460,34 +460,6 @@ void SpriteCache::RemapSpriteToSprite0(sprkey_t index) {
 
 const char *spriteFileSig = " Sprite File ";
 
-void SpriteFile::CompressSprite(Bitmap *sprite, Stream *out) {
-	const int depth = sprite->GetBPP();
-	if (depth == 1) {
-		for (int y = 0; y < sprite->GetHeight(); y++)
-			cpackbitl(&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), out);
-	} else if (depth == 2) {
-		for (int y = 0; y < sprite->GetHeight(); y++)
-			cpackbitl16((unsigned short *)&sprite->GetScanLine(y)[0], sprite->GetWidth(), out);
-	} else {
-		for (int y = 0; y < sprite->GetHeight(); y++)
-			cpackbitl32((unsigned int *)&sprite->GetScanLine(y)[0], sprite->GetWidth(), out);
-	}
-}
-
-void SpriteFile::UnCompressSprite(Bitmap *sprite, Stream *in) {
-	const int depth = sprite->GetBPP();
-	if (depth == 1) {
-		for (int y = 0; y < sprite->GetHeight(); y++)
-			cunpackbitl(&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), in);
-	} else if (depth == 2) {
-		for (int y = 0; y < sprite->GetHeight(); y++)
-			cunpackbitl16((unsigned short *)&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), in);
-	} else {
-		for (int y = 0; y < sprite->GetHeight(); y++)
-			cunpackbitl32((unsigned int *)&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), in);
-	}
-}
-
 int SpriteFile::SaveToFile(const String &save_to_file,
 	const std::vector<Bitmap *> &sprites,
 	SpriteFile *read_from_file,
@@ -551,7 +523,7 @@ int SpriteFile::SaveToFile(const String &save_to_file,
 				// write some space for the length data
 				output->WriteInt32(0);
 
-				CompressSprite(image, output.get());
+				rle_compress(image, output.get());
 
 				soff_t fileSizeSoFar = output->GetPosition();
 				// write the length of the compressed data
@@ -811,7 +783,7 @@ HAGSError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 			delete image;
 			return new Error(String::FromFormat("LoadSprite: bad compressed data for sprite %d.", index));
 		}
-		UnCompressSprite(image, _stream.get());
+		rle_decompress(image, _stream.get());
 	} else {
 		if (coldep == 1) {
 			for (int h = 0; h < htt; ++h)
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index 0eb9ee89c0..9d39b472ee 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -149,10 +149,6 @@ private:
 	static sprkey_t FindTopmostSprite(const std::vector<Shared::Bitmap *> &sprites);
 	// Seek stream to sprite
 	void        SeekToSprite(sprkey_t index);
-	// Writes compressed sprite to the stream
-	static void CompressSprite(Shared::Bitmap *sprite, Shared::Stream *out);
-	// Uncompresses sprite from stream into the given bitmap
-	static void UnCompressSprite(Shared::Bitmap *sprite, Shared::Stream *in);
 
 	// Internal sprite reference
 	struct SpriteRef {
diff --git a/engines/ags/shared/util/compress.cpp b/engines/ags/shared/util/compress.cpp
index c94bc9cf13..6af5f03ea6 100644
--- a/engines/ags/shared/util/compress.cpp
+++ b/engines/ags/shared/util/compress.cpp
@@ -40,6 +40,10 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
+//-----------------------------------------------------------------------------
+// RLE
+//-----------------------------------------------------------------------------
+
 void cpackbitl(const uint8_t *line, int size, Stream *out) {
 	int cnt = 0;                  // bytes encoded
 
@@ -286,7 +290,37 @@ int cunpackbitl32(uint32_t *line, int size, Stream *in) {
 	return in->HasErrors() ? -1 : 0;
 }
 
-//=============================================================================
+void rle_compress(Bitmap *bmp, Shared::Stream *out) {
+	const int depth = bmp->GetBPP();
+	if (depth == 1) {
+		for (int y = 0; y < bmp->GetHeight(); y++)
+			cpackbitl(&bmp->GetScanLineForWriting(y)[0], bmp->GetWidth(), out);
+	} else if (depth == 2) {
+		for (int y = 0; y < bmp->GetHeight(); y++)
+			cpackbitl16((unsigned short *)&bmp->GetScanLine(y)[0], bmp->GetWidth(), out);
+	} else {
+		for (int y = 0; y < bmp->GetHeight(); y++)
+			cpackbitl32((unsigned int *)&bmp->GetScanLine(y)[0], bmp->GetWidth(), out);
+	}
+}
+
+void rle_decompress(Bitmap *bmp, Shared::Stream *in) {
+	const int depth = bmp->GetBPP();
+	if (depth == 1) {
+		for (int y = 0; y < bmp->GetHeight(); y++)
+			cunpackbitl(&bmp->GetScanLineForWriting(y)[0], bmp->GetWidth(), in);
+	} else if (depth == 2) {
+		for (int y = 0; y < bmp->GetHeight(); y++)
+			cunpackbitl16((unsigned short *)&bmp->GetScanLineForWriting(y)[0], bmp->GetWidth(), in);
+	} else {
+		for (int y = 0; y < bmp->GetHeight(); y++)
+			cunpackbitl32((unsigned int *)&bmp->GetScanLineForWriting(y)[0], bmp->GetWidth(), in);
+	}
+}
+
+//-----------------------------------------------------------------------------
+// LZW
+//-----------------------------------------------------------------------------
 
 const char *lztempfnm = "~aclzw.tmp";
 
diff --git a/engines/ags/shared/util/compress.h b/engines/ags/shared/util/compress.h
index 568d0673b9..17c9cb0827 100644
--- a/engines/ags/shared/util/compress.h
+++ b/engines/ags/shared/util/compress.h
@@ -37,18 +37,10 @@ class Bitmap;
 
 using namespace AGS; // FIXME later
 
-void csavecompressed(Shared::Stream *out, const unsigned char *tobesaved, const RGB pala[256]);
-// RLE compression
-void cpackbitl(const uint8_t *line, int size, Shared::Stream *out);
-void cpackbitl16(const uint16_t *line, int size, Shared::Stream *out);
-void cpackbitl32(const uint32_t *line, int size, Shared::Stream *out);
-// RLE decompression
-int  cunpackbitl(uint8_t *line, int size, Shared::Stream *in);
-int  cunpackbitl16(uint16_t *line, int size, Shared::Stream *in);
-int  cunpackbitl32(uint32_t *line, int size, Shared::Stream *in);
-
-//=============================================================================
+void rle_compress(Shared::Bitmap *, Shared::Stream *);
+void rle_decompress(Shared::Bitmap *, Shared::Stream *);
 
+// LZW compression
 void save_lzw(Shared::Stream *out, const Shared::Bitmap *bmpp, const RGB *pall);
 void load_lzw(Shared::Stream *in, Shared::Bitmap **bmm, int dst_bpp, RGB *pall);
 void savecompressed_allegro(Shared::Stream *out, const Shared::Bitmap *bmpp, const RGB *pall);


Commit: bfabf7c94dc16dbebf45f10efd6bf72afb48d8cb
    https://github.com/scummvm/scummvm/commit/bfabf7c94dc16dbebf45f10efd6bf72afb48d8cb
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-07T22:07:10-07:00

Commit Message:
AGS: Fixed infinite wait in Object_Move under specific conditions

>From upstream 9dbdfe423c870e010a887cada725a8bbdf2de16e

Changed paths:
    engines/ags/engine/ac/object.cpp


diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index 462328332f..5cd1e0416d 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -292,7 +292,7 @@ void Object_Move(ScriptObject *objj, int x, int y, int speed, int blocking, int
 	move_object(objj->id, x, y, speed, direct);
 
 	if ((blocking == BLOCKING) || (blocking == 1))
-		GameLoopUntilValueIsZero(&_G(objs)[objj->id].moving);
+		GameLoopUntilNotMoving(&_G(objs)[objj->id].moving);
 	else if ((blocking != IN_BACKGROUND) && (blocking != 0))
 		quit("Object.Move: invalid BLOCKING paramter");
 }


Commit: e582a09612adfd7d27def3e7f04ec8725da7ac34
    https://github.com/scummvm/scummvm/commit/e582a09612adfd7d27def3e7f04ec8725da7ac34
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-07T22:07:10-07:00

Commit Message:
AGS: Reprogram file/path functions to always use UTF-8 encoding

>From upstream d9fba3fba3ec5f012297f1ec7dd0d1d5a55f3255

This only applies most of this commit, since it changes Allegro
code which I don't fully implement for ScummVM anyway, since all
path and file operations go via the ScummVM API anyway

Changed paths:
    engines/ags/lib/allegro/file.cpp
    engines/ags/lib/allegro/unicode.cpp
    engines/ags/lib/allegro/unicode.h


diff --git a/engines/ags/lib/allegro/file.cpp b/engines/ags/lib/allegro/file.cpp
index b8c02fba76..a2656ad3f2 100644
--- a/engines/ags/lib/allegro/file.cpp
+++ b/engines/ags/lib/allegro/file.cpp
@@ -102,6 +102,17 @@ int PACKFILE::pack_fputs(AL_CONST char *p) {
 
 /*------------------------------------------------------------------*/
 
+/* Use strictly UTF-8 encoding for the file paths
+ */
+#define U_CURRENT U_UTF8
+#define ugetc     utf8_getc
+#define ugetx     utf8_getx
+#define ugetxc    utf8_getx
+#define usetc     utf8_setc
+#define uwidth    utf8_width
+#define ucwidth   utf8_cwidth
+#define uisok     utf8_isok
+
 char *fix_filename_case(char *path) {
 	return path;
 }
diff --git a/engines/ags/lib/allegro/unicode.cpp b/engines/ags/lib/allegro/unicode.cpp
index 1ddd069067..7b77879477 100644
--- a/engines/ags/lib/allegro/unicode.cpp
+++ b/engines/ags/lib/allegro/unicode.cpp
@@ -33,4 +33,151 @@ size_t ustrsize(const char *s) {
 	return strlen(s);
 }
 
+/* utf8_getc:
+ * Reads a character from a UTF - 8 string.
+ */
+/*static*/ int utf8_getc(const char *s) {
+	int c = *((unsigned char *)(s++));
+	int n, t;
+
+	if (c & 0x80) {
+		n = 1;
+		while (c & (0x80 >> n))
+			n++;
+
+		c &= (1 << (8 - n)) - 1;
+
+		while (--n > 0) {
+			t = *((unsigned char *)(s++));
+
+			if ((!(t & 0x80)) || (t & 0x40))
+				return '^';
+
+			c = (c << 6) | (t & 0x3F);
+		}
+	}
+
+	return c;
+}
+
+
+
+/* utf8_getx:
+ *  Reads a character from a UTF-8 string, advancing the pointer position.
+ */
+/*static*/ int utf8_getx(char **s) {
+	int c = *((unsigned char *)((*s)++));
+	int n, t;
+
+	if (c & 0x80) {
+		n = 1;
+		while (c & (0x80 >> n))
+			n++;
+
+		c &= (1 << (8 - n)) - 1;
+
+		while (--n > 0) {
+			t = *((unsigned char *)((*s)++));
+
+			if ((!(t & 0x80)) || (t & 0x40)) {
+				(*s)--;
+				return '^';
+			}
+
+			c = (c << 6) | (t & 0x3F);
+		}
+	}
+
+	return c;
+}
+
+
+
+/* utf8_setc:
+ *  Sets a character in a UTF-8 string.
+ */
+/*static*/ int utf8_setc(char *s, int c) {
+	int size, bits, b, i;
+
+	if (c < 128) {
+		*s = c;
+		return 1;
+	}
+
+	bits = 7;
+	while (c >= (1 << bits))
+		bits++;
+
+	size = 2;
+	b = 11;
+
+	while (b < bits) {
+		size++;
+		b += 5;
+	}
+
+	b -= (7 - size);
+	s[0] = c >> b;
+
+	for (i = 0; i < size; i++)
+		s[0] |= (0x80 >> i);
+
+	for (i = 1; i < size; i++) {
+		b -= 6;
+		s[i] = 0x80 | ((c >> b) & 0x3F);
+	}
+
+	return size;
+}
+
+
+
+/* utf8_width:
+ *  Returns the width of a UTF-8 character.
+ */
+/*static*/ int utf8_width(const char *s) {
+	int c = *((unsigned char *)s);
+	int n = 1;
+
+	if (c & 0x80) {
+		while (c & (0x80 >> n))
+			n++;
+	}
+
+	return n;
+}
+
+
+
+/* utf8_cwidth:
+ *  Returns the width of a UTF-8 character.
+ */
+/*static*/ int utf8_cwidth(int c) {
+	int size, bits, b;
+
+	if (c < 128)
+		return 1;
+
+	bits = 7;
+	while (c >= (1 << bits))
+		bits++;
+
+	size = 2;
+	b = 11;
+
+	while (b < bits) {
+		size++;
+		b += 5;
+	}
+
+	return size;
+}
+
+/* utf8_isok:
+ *  Checks whether this character can be encoded in UTF-8 format.
+ */
+/*static*/ int utf8_isok(int c) {
+	return true;
+}
+
 } // namespace AGS3
diff --git a/engines/ags/lib/allegro/unicode.h b/engines/ags/lib/allegro/unicode.h
index a17ab6d9f7..c87f8e0dc9 100644
--- a/engines/ags/lib/allegro/unicode.h
+++ b/engines/ags/lib/allegro/unicode.h
@@ -36,6 +36,15 @@ namespace AGS3 {
 extern void set_uformat(int format);
 extern size_t ustrsize(const char *s);
 
+/* UTF-8 support functions
+ */
+int utf8_getc(const char *s);
+int utf8_getx(char **s);
+int utf8_setc(char *s, int c);
+int utf8_width(const char *s);
+int utf8_cwidth(int c);
+int utf8_isok(int c);
+
 } // namespace AGS3
 
 #endif


Commit: 81f008806458aa9b31ead550d141054bb4e0549e
    https://github.com/scummvm/scummvm/commit/81f008806458aa9b31ead550d141054bb4e0549e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-07T22:07:10-07:00

Commit Message:
AGS: Start in the UTF-8 mode, switch encoding based on game data

>From upstream 1d8a47d494a5211645ca2883b7c1f4931e1cc34a

Changed paths:
    engines/ags/engine/ac/translation.cpp
    engines/ags/engine/main/engine.cpp
    engines/ags/engine/main/main.cpp
    engines/ags/lib/allegro/file.cpp
    engines/ags/lib/allegro/file.h


diff --git a/engines/ags/engine/ac/translation.cpp b/engines/ags/engine/ac/translation.cpp
index 35c9bb9895..d04f80e3be 100644
--- a/engines/ags/engine/ac/translation.cpp
+++ b/engines/ags/engine/ac/translation.cpp
@@ -45,6 +45,9 @@ void close_translation() {
 	_GP(trans) = Translation();
 	_G(trans_name) = "";
 	_G(trans_filename) = "";
+
+	// Return back to default game's encoding
+	set_uformat(U_ASCII);
 }
 
 bool init_translation(const String &lang, const String &fallback_lang, bool quit_on_error) {
@@ -101,6 +104,9 @@ bool init_translation(const String &lang, const String &fallback_lang, bool quit
 		_GP(game).options[OPT_RIGHTLEFTWRITE] = 1;
 	}
 
+	// Setup a text encoding mode depending on the translation data hint
+	set_uformat(U_ASCII);
+
 	Debug::Printf("Translation initialized: %s", _G(trans_filename).GetCStr());
 	return true;
 }
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 657b4c1615..fd6a77fd59 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -102,7 +102,6 @@ bool engine_init_backend() {
 	}
 
 	// Initialize stripped allegro library
-	set_uformat(U_ASCII);
 	if (install_allegro()) {
 		_G(platform)->DisplayAlert("Internal error: unable to initialize stripped Allegro 4 library.");
 		return false;
@@ -601,6 +600,9 @@ void engine_init_game_settings() {
 	_G(our_eip) = -7;
 	Debug::Printf("Initialize game settings");
 
+	// Setup a text encoding mode depending on the game data hint
+	set_uformat(U_ASCII);
+
 	int ee;
 
 	for (ee = 0; ee < MAX_ROOM_OBJECTS + _GP(game).numcharacters; ee++)
diff --git a/engines/ags/engine/main/main.cpp b/engines/ags/engine/main/main.cpp
index b1a81ac888..20465c447a 100644
--- a/engines/ags/engine/main/main.cpp
+++ b/engines/ags/engine/main/main.cpp
@@ -76,6 +76,10 @@ void main_create_platform_driver() {
 #define SVG_VERSION_FWCOMPAT_REVISION   1111
 
 void main_init(int argc, const char *argv[]) {
+	// Init libraries: set text encoding
+	set_uformat(U_UTF8);
+	set_filename_encoding(U_UNICODE);
+
 	_G(EngineVersion) = Version(ACI_VERSION_STR " " SPECIAL_VERSION);
 #if defined (BUILD_STR)
 	_G(EngineVersion).BuildInfo = BUILD_STR;
diff --git a/engines/ags/lib/allegro/file.cpp b/engines/ags/lib/allegro/file.cpp
index a2656ad3f2..4f35a7442d 100644
--- a/engines/ags/lib/allegro/file.cpp
+++ b/engines/ags/lib/allegro/file.cpp
@@ -113,6 +113,10 @@ int PACKFILE::pack_fputs(AL_CONST char *p) {
 #define ucwidth   utf8_cwidth
 #define uisok     utf8_isok
 
+void set_filename_encoding(int) {
+	// No implementation
+}
+
 char *fix_filename_case(char *path) {
 	return path;
 }
diff --git a/engines/ags/lib/allegro/file.h b/engines/ags/lib/allegro/file.h
index 1fab2c3bd2..2d9b6328d6 100644
--- a/engines/ags/lib/allegro/file.h
+++ b/engines/ags/lib/allegro/file.h
@@ -198,6 +198,7 @@ public:
 	}
 };
 
+extern void set_filename_encoding(int);
 extern char *fix_filename_case(char *path);
 extern char *fix_filename_slashes(char *path);
 extern char *append_filename(char *dest, const char *path, const char *filename, int size);


Commit: 5dc90dabc448514ed6b90db0caa3954da740f36b
    https://github.com/scummvm/scummvm/commit/5dc90dabc448514ed6b90db0caa3954da740f36b
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-07T22:07:10-07:00

Commit Message:
AGS: Added encoding hint to the TRS/TRA

>From upstream 37efe963fd6f3bd62de248a71e8a7aa97a1ba1c2

Changed paths:
    engines/ags/shared/game/tra_file.cpp
    engines/ags/shared/game/tra_file.h
    engines/ags/shared/util/string_utils.cpp
    engines/ags/shared/util/string_utils.h


diff --git a/engines/ags/shared/game/tra_file.cpp b/engines/ags/shared/game/tra_file.cpp
index 964bd96a60..a78cc9d7a2 100644
--- a/engines/ags/shared/game/tra_file.cpp
+++ b/engines/ags/shared/game/tra_file.cpp
@@ -73,7 +73,7 @@ HError OpenTraFile(Stream *in) {
 	return HError::None();
 }
 
-HError ReadTraBlock(Translation &tra, Stream *in, TraFileBlock block, soff_t block_len) {
+HError ReadTraBlock(Translation &tra, Stream *in, TraFileBlock block, const String &ext_id, soff_t block_len) {
 	switch (block) {
 	case kTraFblk_Dict:
 	{
@@ -87,26 +87,36 @@ HError ReadTraBlock(Translation &tra, Stream *in, TraFileBlock block, soff_t blo
 				break;
 			tra.Dict.insert(std::make_pair(String(original), String(translation)));
 		}
+		return HError::None();
 	}
-	break;
 	case kTraFblk_GameID:
 	{
 		char gamename[256];
 		tra.GameUid = in->ReadInt32();
 		read_string_decrypt(in, gamename, sizeof(gamename));
 		tra.GameName = gamename;
+		return HError::None();
 	}
-	break;
 	case kTraFblk_TextOpts:
 		tra.NormalFont = in->ReadInt32();
 		tra.SpeechFont = in->ReadInt32();
 		tra.RightToLeft = in->ReadInt32();
+		return HError::None();
+	case (TraFileBlock)0:
+		// continue reading extensions with string ID
 		break;
 	default:
 		return new TraFileError(kTraFileErr_UnknownBlockType,
 			String::FromFormat("Type: %d, known range: %d - %d.", block, kTraFblk_Dict, kTraFblk_TextOpts));
 	}
-	return HError::None();
+
+	if (ext_id.CompareNoCase("ext_sopts") == 0) {
+		StrUtil::ReadStringMap(tra.StrOptions, in);
+		return HError::None();
+	}
+
+	return new TraFileError(kTraFileErr_UnknownBlockType,
+		String::FromFormat("Type: %s", ext_id.GetCStr()));
 }
 
 static Translation *reader_tra;
@@ -114,7 +124,7 @@ HError TestTraGameIDReader(Stream *in, int block_id, const String &ext_id,
 		soff_t block_len, bool &read_next) {
 	if (block_id == kTraFblk_GameID) {
 		read_next = false;
-		return ReadTraBlock(*reader_tra, in, (TraFileBlock)block_id, block_len);
+		return ReadTraBlock(*reader_tra, in, (TraFileBlock)block_id, ext_id, block_len);
 	}
 	in->Seek(block_len); // skip block
 	return HError::None();
@@ -144,7 +154,7 @@ HError TestTraGameID(int game_uid, const String &game_name, Stream *in) {
 // and read compatible data into the given Translation object
 HError ReadTraDataReader(Stream *in, int block_id, const String &ext_id,
 		soff_t block_len, bool &read_next) {
-	return ReadTraBlock(*reader_tra, in, (TraFileBlock)block_id, block_len);
+	return ReadTraBlock(*reader_tra, in, (TraFileBlock)block_id, ext_id, block_len);
 }
 
 HError ReadTraData(Translation &tra, Stream *in) {
@@ -204,6 +214,10 @@ void WriteTextOpts(const Translation &tra, Stream *out) {
 static const Translation *writer_tra;
 static void(*writer_writer)(const Translation &tra, Stream *out);
 
+void WriteStrOptions(const Translation &tra, Stream *out) {
+	StrUtil::WriteStringMap(tra.StrOptions, out);
+}
+
 static void WriteTraBlockWriter(Stream *out) {
 	writer_writer(*writer_tra, out);
 }
@@ -217,6 +231,15 @@ inline void WriteTraBlock(const Translation &tra, TraFileBlock block,
 		kDataExt_NumID32 | kDataExt_File32, out);
 }
 
+inline void WriteTraBlock(const Translation &tra, const String &ext_id,
+		void(*writer)(const Translation &tra, Stream *out), Stream *out) {
+	writer_tra = &tra;
+	writer_writer = writer;
+
+	WriteExtBlock(ext_id, WriteTraBlockWriter,
+		kDataExt_NumID32 | kDataExt_File32, out);
+}
+
 void WriteTraData(const Translation &tra, Stream *out) {
 	// Write header
 	out->Write(TRASignature, strlen(TRASignature) + 1);
@@ -225,6 +248,7 @@ void WriteTraData(const Translation &tra, Stream *out) {
 	WriteTraBlock(tra, kTraFblk_GameID, WriteGameID, out);
 	WriteTraBlock(tra, kTraFblk_Dict, WriteDict, out);
 	WriteTraBlock(tra, kTraFblk_TextOpts, WriteTextOpts, out);
+	WriteTraBlock(tra, "ext_sopts", WriteStrOptions, out);
 
 	// Write ending
 	out->WriteInt32(kTraFile_EOF);
diff --git a/engines/ags/shared/game/tra_file.h b/engines/ags/shared/game/tra_file.h
index a649b96ddc..6060465d8e 100644
--- a/engines/ags/shared/game/tra_file.h
+++ b/engines/ags/shared/game/tra_file.h
@@ -71,6 +71,7 @@ struct Translation {
 	int NormalFont = -1; // replacement for normal font, or -1 for default
 	int SpeechFont = -1; // replacement for speech font, or -1 for default
 	int RightToLeft = -1; // r2l text mode (0, 1), or -1 for default
+	StringMap StrOptions; // to store extended options with string values
 };
 
 
diff --git a/engines/ags/shared/util/string_utils.cpp b/engines/ags/shared/util/string_utils.cpp
index 0043049013..134a1845cf 100644
--- a/engines/ags/shared/util/string_utils.cpp
+++ b/engines/ags/shared/util/string_utils.cpp
@@ -198,6 +198,23 @@ void StrUtil::WriteCStr(const String &s, Stream *out) {
 	out->Write(s.GetCStr(), s.GetLength() + 1);
 }
 
+void StrUtil::ReadStringMap(StringMap &map, Stream *in) {
+	size_t count = in->ReadInt32();
+	for (size_t i = 0; i < count; ++i) {
+		String key = StrUtil::ReadString(in);
+		String value = StrUtil::ReadString(in);
+		map.insert(std::make_pair(key, value));
+	}
+}
+
+void StrUtil::WriteStringMap(const StringMap &map, Stream *out) {
+	out->WriteInt32(map.size());
+	for (const auto &kv : map) {
+		StrUtil::WriteString(kv._key, out);
+		StrUtil::WriteString(kv._value, out);
+	}
+}
+
 } // namespace Shared
 } // namespace AGS
 } // namespace AGS3
diff --git a/engines/ags/shared/util/string_utils.h b/engines/ags/shared/util/string_utils.h
index 9276f56242..6980aac684 100644
--- a/engines/ags/shared/util/string_utils.h
+++ b/engines/ags/shared/util/string_utils.h
@@ -23,7 +23,7 @@
 #ifndef AGS_SHARED_UTIL_STRING_UTILS_H
 #define AGS_SHARED_UTIL_STRING_UTILS_H
 
-#include "ags/shared/util/string.h"
+#include "ags/shared/util/string_types.h"
 
 namespace AGS3 {
 
@@ -82,6 +82,10 @@ void            SkipCStr(Stream *in);
 void            WriteCStr(const char *cstr, Stream *out);
 void            WriteCStr(const String &s, Stream *out);
 
+// Serialize and unserialize a string map, both keys and values are read using ReadString
+void            ReadStringMap(StringMap &map, Stream *in);
+void            WriteStringMap(const StringMap &map, Stream *out);
+
 } // namespace StrUtil
 } // namespace Shared
 } // namespace AGS


Commit: 93bc3a65d4146917579b4b7b38b8ce9e6af98f90
    https://github.com/scummvm/scummvm/commit/93bc3a65d4146917579b4b7b38b8ce9e6af98f90
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-07T22:07:10-07:00

Commit Message:
AGS: use text encoding hint from translation

>From upstream f77000a5ef7a7188022c6d5d8f25941ae1e19ef6

Changed paths:
    engines/ags/engine/ac/translation.cpp


diff --git a/engines/ags/engine/ac/translation.cpp b/engines/ags/engine/ac/translation.cpp
index d04f80e3be..e91e250d07 100644
--- a/engines/ags/engine/ac/translation.cpp
+++ b/engines/ags/engine/ac/translation.cpp
@@ -105,7 +105,11 @@ bool init_translation(const String &lang, const String &fallback_lang, bool quit
 	}
 
 	// Setup a text encoding mode depending on the translation data hint
-	set_uformat(U_ASCII);
+	String encoding = _GP(trans).StrOptions["encoding"];
+	if (encoding.CompareNoCase("utf-8") == 0)
+		set_uformat(U_UTF8);
+	else
+		set_uformat(U_ASCII);
 
 	Debug::Printf("Translation initialized: %s", _G(trans_filename).GetCStr());
 	return true;


Commit: 7e42ae4eecc32ca59cbb1834a8c29cab8f733171
    https://github.com/scummvm/scummvm/commit/7e42ae4eecc32ca59cbb1834a8c29cab8f733171
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2021-07-07T22:07:10-07:00

Commit Message:
MOHAWK: Revert incorrect format specifier changes

Changed paths:
    engines/mohawk/livingbooks_code.cpp


diff --git a/engines/mohawk/livingbooks_code.cpp b/engines/mohawk/livingbooks_code.cpp
index a1c3f01e96..e4179c414f 100644
--- a/engines/mohawk/livingbooks_code.cpp
+++ b/engines/mohawk/livingbooks_code.cpp
@@ -139,7 +139,7 @@ Common::Point LBValue::toPoint() const {
 	case kLBValueString:
 		{
 		Common::Point ret;
-		sscanf(string.c_str(), "%d , %d", &ret.x, &ret.y);
+		sscanf(string.c_str(), "%hd , %hd", &ret.x, &ret.y);
 		return ret;
 		}
 	case kLBValueInteger:
@@ -158,7 +158,7 @@ Common::Rect LBValue::toRect() const {
 	case kLBValueString:
 		{
 		Common::Rect ret;
-		sscanf(string.c_str(), "%d , %d , %d , %d", &ret.left, &ret.top, &ret.right, &ret.bottom);
+		sscanf(string.c_str(), "%hd , %hd , %hd , %hd", &ret.left, &ret.top, &ret.right, &ret.bottom);
 		return ret;
 		}
 	case kLBValueInteger:




More information about the Scummvm-git-logs mailing list