[Scummvm-git-logs] scummvm master -> 9963be16b657a636086dd999d7732501b0edb6bb

bluegr noreply at scummvm.org
Fri Apr 17 19:03:27 UTC 2026


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

Summary:
6a8370beab AGOS: Support for FR/DE Elvira Atari ST music.
cfad9511ce AGOS: Add PRG filenames for ST Action issue 37 Elvira ST demo
4de51f5e84 AGOS: Add detection entry for German Elvira II Atari ST
9963be16b6 AGOS: Move Pack Ice decompressor to common


Commit: 6a8370beabc7646bb74a0014ebac31708af4d856
    https://github.com/scummvm/scummvm/commit/6a8370beabc7646bb74a0014ebac31708af4d856
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-04-17T22:03:21+03:00

Commit Message:
AGOS: Support for FR/DE Elvira Atari ST music.

Changed paths:
    engines/agos/agos.cpp
    engines/agos/detection_tables.h
    engines/agos/drivers/elvira_atarist.cpp
    engines/agos/res_snd.cpp


diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 56d8aa01f18..b9f3d9e612e 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -1077,6 +1077,7 @@ const AGOSEngine::PnAmigaTextPlane *AGOSEngine::getPnAmigaTextPlane(const Window
 
 AGOSEngine::~AGOSEngine() {
 	_system->getAudioCDManager()->stop();
+	stopMusic();
 	delete _pnAmigaFont;
 
 	for (uint i = 0; i < _itemHeap.size(); i++) {
diff --git a/engines/agos/detection_tables.h b/engines/agos/detection_tables.h
index d5009b46f02..2c9ba21b290 100644
--- a/engines/agos/detection_tables.h
+++ b/engines/agos/detection_tables.h
@@ -246,6 +246,29 @@ static const AGOSGameDescription gameDescriptions[] = {
 		GF_OLD_BUNDLE | GF_CRUNCHED | GF_PLANAR
 	},
 
+	// Elvira 1 - German Atari ST Floppy
+	{
+		{
+			"elvira1",
+			"Floppy",
+
+			{
+				{ "gamest",		GAME_BASEFILE,	"8942859018fcfb2dbed13e83d974d1ab", 121266},
+				{ "icon.dat",	GAME_ICONFILE,	"2db931e84f1ca01f0816dddfae3f49e1", 36573},
+				{ "tbllist",	GAME_TBLFILE,	"5b6ff494bf7e24213758598ef4ac0a8b", 476},
+				AD_LISTEND
+			},
+			Common::DE_DEU,
+			Common::kPlatformAtariST,
+			ADGF_NO_FLAGS,
+			GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI)
+		},
+
+		GType_ELVIRA1,
+		GID_ELVIRA1,
+		GF_OLD_BUNDLE | GF_CRUNCHED | GF_PLANAR
+	},
+
 	// Elvira 1 - English Atari ST Floppy alternative?
 	{
 		{
diff --git a/engines/agos/drivers/elvira_atarist.cpp b/engines/agos/drivers/elvira_atarist.cpp
index d19e6af6a72..da82ab4dd65 100644
--- a/engines/agos/drivers/elvira_atarist.cpp
+++ b/engines/agos/drivers/elvira_atarist.cpp
@@ -139,7 +139,7 @@ static uint32 findTuneTable(const Common::Array<uint8> &mem) {
 
 
 static bool applyKnownElviraDriverLayout(uint32 textSize, ElviraPrgLabels &L) {
-	// Full game
+	// Full game (DE)
 	if (textSize == 0x15920) {
 		L.L003C = 0xDF6A;
 		L.L003D = 0xDFFE;
@@ -170,6 +170,37 @@ static bool applyKnownElviraDriverLayout(uint32 textSize, ElviraPrgLabels &L) {
 		return true;
 	}
 
+	// Full game (RUNENG variants)
+	if (textSize == 0x158DE) {
+		L.L003C = 0xDF28;
+		L.L003D = 0xDFBC;
+		L.L003E = 0xDFC2;
+		L.L003F = 0xDFC8;
+		L.L0040 = 0xDFD4;
+		L.L0041 = 0xDFE0;
+		L.L0042 = 0xDFF0;
+		L.L0043 = 0xE008;
+		L.L0044 = 0xE00E;
+		L.L0045 = 0xE014;
+		L.L0046 = 0xE01A;
+		L.L0047 = 0xE06E;
+		L.L0048 = 0xE070;
+		L.L0049 = 0xE074;
+		L.L004A = 0xE076;
+		L.L004B = 0xE07E;
+		L.L004C = 0xE084;
+		L.L004D = 0xE08A;
+		L.L004E = 0xE091;
+		L.L004F = 0xE09C;
+		L.L003A = L.L003C - 64;
+		L.L003B = L.L003C - 62;
+		L.tunetab = 0xE0D2;
+		L.L0051 = 0xE142;
+		L.L0067 = 0xE5A6;
+		L.L0068 = 0xE610;
+		return true;
+	}
+
 	// Demo
 	if (textSize == 0x0A4A6) {
 		L.L003C = 0x686C;
@@ -206,6 +237,9 @@ static bool applyKnownElviraDriverLayout(uint32 textSize, ElviraPrgLabels &L) {
 
 
 static bool locateEmbeddedDriverLayout(const Common::Array<uint8> &mem, ElviraPrgLabels &L, uint32 textSize) {
+	if (applyKnownElviraDriverLayout(textSize, L))
+		return true;
+
 	static const uint8 sigL0068[] = {0xFF,0xFF,0x00,0x01,0x00,0xA0,0xFF,0xFF,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x14};
 	static const uint8 sigL004B[] = {0x00,0x00,0x00,0x1A,0x00,0x34};
 	static const uint8 sigL003C[] = {0x0D,0x60,0x0C,0xA0,0x0B,0xE8,0x0B,0x40};
@@ -224,7 +258,7 @@ static bool locateEmbeddedDriverLayout(const Common::Array<uint8> &mem, ElviraPr
 	L.tunetab = findTuneTable(mem);
 
 	if (!L.L0068 || !L.L004B || !L.L003C || !L.L003D || !L.L003E || !L.L0067 || !L.L0045 || !L.tunetab)
-		return applyKnownElviraDriverLayout(textSize, L);
+		return false;
 
 	if (L.L003C >= 64) {
 		L.L003A = L.L003C - 64;
diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp
index fac14acc6e3..c9e6b3bf07c 100644
--- a/engines/agos/res_snd.cpp
+++ b/engines/agos/res_snd.cpp
@@ -110,6 +110,291 @@ static Common::Array<byte> unsquashAcornDesktopTracker(const byte *data, uint32
 	}
 }
 
+/*
+ * Pack-Ice depacker is based on a simplified version of IceDecompressor:
+ * https://github.com/temisu/ancient
+ *
+ * BSD 2-Clause License
+ *
+ * Copyright (c) 2017-2026, Teemu Suutari
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+static bool isElvira1PackIcePrg(const Common::Array<byte> &data) {
+	return data.size() >= 0x26 && !memcmp(data.begin() + 0x1E, "Pack-Ice", 8);
+}
+
+static uint32 makePackIceBitMask(uint8 bitCount) {
+	return bitCount == 32 ? 0xFFFFFFFF : ((1U << bitCount) - 1);
+}
+
+class PackIceBitReader {
+public:
+	PackIceBitReader(const Common::Array<byte> &data, uint32 startOffset, uint32 endOffset)
+		: _data(data), _pos(endOffset), _startOffset(startOffset) {
+	}
+
+	void reset(uint32 value, uint8 bitCount) {
+		_bitBuffer = value;
+		_bitsLeft = bitCount;
+	}
+
+	uint8 readByte() {
+		if (_pos <= _startOffset)
+			error("AGOS: Pack-Ice depacker byte underrun");
+		return _data[--_pos];
+	}
+
+	uint32 readBitsBE32(uint32 count) {
+		uint32 value = 0;
+		while (count) {
+			if (!_bitsLeft) {
+				if (_pos < _startOffset + 4)
+					error("AGOS: Pack-Ice depacker long underrun");
+				_pos -= 4;
+				_bitBuffer = READ_BE_UINT32(_data.begin() + _pos);
+				_bitsLeft = 32;
+			}
+			const uint8 bitsToRead = (count < _bitsLeft) ? (uint8)count : _bitsLeft;
+			_bitsLeft -= bitsToRead;
+			const uint32 nextBits = (_bitBuffer >> _bitsLeft) & makePackIceBitMask(bitsToRead);
+			value = bitsToRead == 32 ? nextBits : ((value << bitsToRead) | nextBits);
+			count -= bitsToRead;
+		}
+		return value;
+	}
+
+	bool eof() const {
+		return _pos == _startOffset;
+	}
+
+private:
+	const Common::Array<byte> &_data;
+	uint32 _pos;
+	uint32 _startOffset;
+	uint32 _bitBuffer = 0;
+	uint8 _bitsLeft = 0;
+};
+
+static uint32 decodePackIceVlc(PackIceBitReader &bits, const uint8 *bitLengths, const uint32 *offsets, uint32 count,
+		uint32 base) {
+	if (base >= count)
+		error("AGOS: Pack-Ice depacker invalid VLC base");
+
+	return offsets[base] + bits.readBitsBE32(bitLengths[base]);
+}
+
+static uint32 decodePackIceCascade(PackIceBitReader &bits, const uint8 *bitLengths, const uint32 *offsets,
+		uint32 count) {
+	for (uint32 i = 0; i < count; ++i) {
+		const uint8 bitLength = bitLengths[i];
+		const uint32 value = bits.readBitsBE32(bitLength);
+		if (i + 1 == count || value != makePackIceBitMask(bitLength))
+			return offsets[i] - i + value;
+	}
+
+	error("AGOS: Pack-Ice depacker invalid VLC cascade");
+}
+
+static bool depackElvira1PackIcePrg(const Common::Array<byte> &packedData, Common::Array<byte> &unpackedData) {
+	enum {
+		kPackedStreamStart = 0x021C,
+		kPackedStreamEnd = 0xAFE2,
+		kRawSize = 0x1694C
+	};
+	const uint8 literalBitLengths[] = {1, 2, 2, 3, 8, 15};
+	const uint32 literalOffsets[] = {0, 2, 6, 10, 18, 274};
+	const uint8 countBaseBitLengths[] = {1, 1, 1, 1};
+	const uint32 countBaseOffsets[] = {0, 2, 4, 6};
+	const uint8 countBitLengths[] = {0, 0, 1, 2, 10};
+	const uint32 countOffsets[] = {0, 1, 2, 4, 8};
+	const uint8 distanceBaseBitLengths[] = {1, 1};
+	const uint32 distanceBaseOffsets[] = {0, 2};
+	const uint8 distanceBitLengths[] = {5, 8, 12};
+	const uint32 distanceOffsets[] = {0, 32, 288};
+
+	if (!isElvira1PackIcePrg(packedData) || packedData.size() < kPackedStreamEnd)
+		return false;
+
+	PackIceBitReader bits(packedData, kPackedStreamStart, kPackedStreamEnd);
+	uint32 initialBits = bits.readBitsBE32(32);
+	uint32 shiftedBits = initialBits;
+	uint32 initialBitCount = 0;
+	while (shiftedBits) {
+		shiftedBits <<= 1;
+		++initialBitCount;
+	}
+	if (initialBitCount)
+		--initialBitCount;
+	if (initialBitCount)
+		bits.reset(initialBits >> (32 - initialBitCount), (uint8)initialBitCount);
+
+	unpackedData.resize(kRawSize);
+	uint32 outPos = kRawSize;
+
+	while (true) {
+		if (bits.readBitsBE32(1)) {
+			const uint32 literalLength = decodePackIceCascade(bits, literalBitLengths, literalOffsets,
+				ARRAYSIZE(literalBitLengths)) + 1;
+			for (uint32 i = 0; i < literalLength; ++i) {
+				if (!outPos)
+					return false;
+				unpackedData[--outPos] = bits.readByte();
+			}
+		}
+
+		if (!outPos)
+			break;
+
+		const uint32 countBase = decodePackIceCascade(bits, countBaseBitLengths, countBaseOffsets,
+			ARRAYSIZE(countBaseBitLengths));
+		const uint32 copyCount = decodePackIceVlc(bits, countBitLengths, countOffsets, ARRAYSIZE(countBitLengths),
+			countBase) + 2;
+
+		uint32 distance = 0;
+		if (copyCount == 2) {
+			if (bits.readBitsBE32(1))
+				distance = bits.readBitsBE32(9) + 0x40;
+			else
+				distance = bits.readBitsBE32(6);
+			distance += copyCount;
+		} else {
+			uint32 distanceBase = decodePackIceCascade(bits, distanceBaseBitLengths, distanceBaseOffsets,
+				ARRAYSIZE(distanceBaseBitLengths));
+			if (distanceBase < 2)
+				distanceBase ^= 1;
+			distance = decodePackIceVlc(bits, distanceBitLengths, distanceOffsets, ARRAYSIZE(distanceBitLengths), distanceBase);
+			distance += copyCount;
+		}
+
+		if (!distance || outPos < copyCount || outPos + distance > kRawSize)
+			return false;
+
+		for (uint32 i = 0; i < copyCount; ++i) {
+			--outPos;
+			unpackedData[outPos] = unpackedData[outPos + distance];
+		}
+	}
+
+	return bits.eof() && unpackedData.size() >= 28 && READ_BE_UINT16(unpackedData.begin()) == 0x601A;
+}
+
+static bool extractEmbeddedTosPrg(const Common::Array<byte> &containerPrg, Common::Array<byte> &innerPrg) {
+	if (containerPrg.size() < 28)
+		return false;
+
+	const byte *prg = containerPrg.begin();
+	if (READ_BE_UINT16(prg) != 0x601A)
+		return false;
+
+	const uint32 outerTextSize = READ_BE_UINT32(prg + 2);
+	const uint32 outerRelOffset = 28 + outerTextSize + READ_BE_UINT32(prg + 6) + READ_BE_UINT32(prg + 14);
+	if (containerPrg.size() < outerRelOffset + 4)
+		return false;
+
+	for (uint32 off = 30; off + 28 <= 28 + outerTextSize; off += 2) {
+		if (READ_BE_UINT16(prg + off) != 0x601A)
+			continue;
+
+		const uint32 textSize = READ_BE_UINT32(prg + off + 2);
+		const uint32 dataSize = READ_BE_UINT32(prg + off + 6);
+		const uint32 bssSize = READ_BE_UINT32(prg + off + 10);
+		const uint32 symSize = READ_BE_UINT32(prg + off + 14);
+		const uint32 innerOffText = off + 28;
+		const uint32 innerOffData = innerOffText + textSize;
+		const uint32 innerOffSym = innerOffData + dataSize;
+		const uint32 innerOffRel = innerOffSym + symSize;
+		if (innerOffText < off || innerOffData < innerOffText || innerOffSym < innerOffData || innerOffRel < innerOffSym)
+			continue;
+		if (innerOffRel + 4 > containerPrg.size())
+			continue;
+
+		const uint32 firstRel = READ_BE_UINT32(prg + innerOffRel);
+		if (firstRel >= textSize + dataSize + bssSize && firstRel != 0)
+			continue;
+
+		uint32 pos = innerOffRel + 4;
+		while (pos < containerPrg.size()) {
+			if (containerPrg[pos++] == 0)
+				break;
+		}
+		if (containerPrg[pos - 1] != 0)
+			continue;
+
+		innerPrg.resize(pos - off);
+		memcpy(innerPrg.begin(), prg + off, pos - off);
+		debug(1, "AGOS: Found embedded Atari ST PRG at 0x%X (text=0x%X, data=0x%X, bss=0x%X)",
+			off, textSize, dataSize, bssSize);
+		return true;
+	}
+
+	return false;
+}
+
+static Common::SeekableReadStream *openElvira1AtariSTPrg() {
+	const char *const prgNames[] = {
+		"ELVIRA.PRG",
+		"ELVIRA+.PRG",
+		"RUNENG.PRG",
+		"AUTO/RUNENG.PRG"
+	};
+
+	Common::File file;
+	for (uint i = 0; i < ARRAYSIZE(prgNames); ++i) {
+		const char *prgName = prgNames[i];
+		if (!file.open(Common::Path(prgName)))
+			continue;
+
+		Common::Array<byte> prgData;
+		prgData.resize((uint32)file.size());
+		if (!prgData.empty() && file.read(prgData.begin(), prgData.size()) != prgData.size()) {
+			warning("playMusic: Failed to read Atari ST Elvira 1 PRG '%s'", prgName);
+			return nullptr;
+		}
+
+		if (isElvira1PackIcePrg(prgData)) {
+			Common::Array<byte> unpackedOuterPrg;
+			if (!depackElvira1PackIcePrg(prgData, unpackedOuterPrg)) {
+				warning("playMusic: Failed to depack Atari ST Elvira 1 Pack-Ice PRG '%s'", prgName);
+				return nullptr;
+			}
+			if (!extractEmbeddedTosPrg(unpackedOuterPrg, prgData)) {
+				warning("playMusic: Failed to locate embedded Atari ST PRG inside depacked Elvira 1 wrapper '%s'", prgName);
+				return nullptr;
+			}
+		}
+
+		byte *buf = nullptr;
+		if (!prgData.empty()) {
+			buf = new byte[prgData.size()];
+			memcpy(buf, prgData.begin(), prgData.size());
+		}
+		return new Common::MemoryReadStream(buf, prgData.size(), DisposeAfterUse::YES);
+	}
+
+	return nullptr;
+}
+
 // This data is hardcoded in the executable.
 const int AGOSEngine_Simon1::SIMON1_GMF_SIZE[] = {
 	8900, 12166,  2848,  3442,  4034,  4508,  7064,  9730,  6014,  4742,
@@ -533,19 +818,18 @@ void AGOSEngine::playMusic(uint16 music, uint16 track) {
 				return;
 			}
 
-			Common::File *file = new Common::File();
-			if (!file->open(Common::Path("ELVIRA.PRG"))) {
-				warning("playMusic: Can't load Atari ST ELVIRA.PRG for music id %d", music);
-				delete file;
+			Common::SeekableReadStream *stream = openElvira1AtariSTPrg();
+			if (!stream) {
+				warning("playMusic: Can't load Atari ST Elvira 1 PRG for music id %d", music);
 				return;
 			}
 
 			delete _elviraAtariSTPlayer;
 			_elviraAtariSTPlayer = nullptr;
 
-			_elviraAtariSTPlayer = new ElviraAtariSTPlayer(file, prgTune);
+			_elviraAtariSTPlayer = new ElviraAtariSTPlayer(stream, prgTune);
 			if (!_elviraAtariSTPlayer->isValid()) {
-				warning("playMusic: Unsupported or unreadable Atari ST ELVIRA.PRG, skipping music id %d", music);
+				warning("playMusic: Unsupported or unreadable Atari ST Elvira 1 PRG, skipping music id %d", music);
 				delete _elviraAtariSTPlayer;
 				_elviraAtariSTPlayer = nullptr;
 				return;


Commit: cfad9511ce19dc341ce4d75355614fa3ab5f4994
    https://github.com/scummvm/scummvm/commit/cfad9511ce19dc341ce4d75355614fa3ab5f4994
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-04-17T22:03:21+03:00

Commit Message:
AGOS: Add PRG filenames for ST Action issue 37 Elvira ST demo

Changed paths:
    engines/agos/res_snd.cpp


diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp
index c9e6b3bf07c..b5e91dc8bed 100644
--- a/engines/agos/res_snd.cpp
+++ b/engines/agos/res_snd.cpp
@@ -356,7 +356,9 @@ static Common::SeekableReadStream *openElvira1AtariSTPrg() {
 		"ELVIRA.PRG",
 		"ELVIRA+.PRG",
 		"RUNENG.PRG",
-		"AUTO/RUNENG.PRG"
+		"AUTO/RUNENG.PRG",
+		"AUTO/ADEMO.PRG",
+		"ADEMO.PRG"
 	};
 
 	Common::File file;


Commit: 4de51f5e84fa5f9d79e1cc6931babfc206034dd1
    https://github.com/scummvm/scummvm/commit/4de51f5e84fa5f9d79e1cc6931babfc206034dd1
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-04-17T22:03:21+03:00

Commit Message:
AGOS: Add detection entry for German Elvira II Atari ST

Changed paths:
    engines/agos/detection_tables.h


diff --git a/engines/agos/detection_tables.h b/engines/agos/detection_tables.h
index 2c9ba21b290..50336280e43 100644
--- a/engines/agos/detection_tables.h
+++ b/engines/agos/detection_tables.h
@@ -634,6 +634,34 @@ static const AGOSGameDescription gameDescriptions[] = {
 		GF_OLD_BUNDLE | GF_CRUNCHED | GF_PLANAR
 	},
 
+	// Elvira 2 - German Atari ST Floppy
+	// Suppliedd by Eli0rZeR70 in bug report #16679
+	{
+		{
+			"elvira2",
+			"Floppy",
+
+			{
+				{"gamest", 			GAME_BASEFILE, 	"43cb10d38af2b4a0c707965c83431f4c", 137609},
+				{"icon.dat", 		GAME_ICONFILE, 	"9a4eaf4df0cdf5cc85a5134150f96589", 69538},
+				{"menus.dat",		GAME_MENUFILE, 	"a2fdc88a77c8bdffec6b36cbeda4d955", 108},
+				{"start", 			GAME_RESTFILE, 	"8cddf461f418ea12f711fda3d3dd62fe", 27752},
+				{"stripped.txt", 	GAME_STRFILE, 	"41c975a9c1106cb5298a0bc3df0a266e", 72},
+				{"tbllist", 		GAME_TBLFILE, 	"177f5f2640e80ef92d1421d32de06a5e", 272},
+				AD_LISTEND
+			},
+			Common::DE_DEU,
+			Common::kPlatformAtariST,
+			ADGF_NO_FLAGS,
+			GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI)
+		},
+
+		GType_ELVIRA2,
+		GID_ELVIRA2,
+		GF_OLD_BUNDLE | GF_CRUNCHED | GF_PLANAR
+	},
+
+
 
 	// Elvira 2 - English DOS Floppy
 	{


Commit: 9963be16b657a636086dd999d7732501b0edb6bb
    https://github.com/scummvm/scummvm/commit/9963be16b657a636086dd999d7732501b0edb6bb
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-04-17T22:03:21+03:00

Commit Message:
AGOS: Move Pack Ice decompressor to common

Changed paths:
  A common/compression/packice.cpp
  A common/compression/packice.h
    common/compression/module.mk
    engines/agos/res_snd.cpp


diff --git a/common/compression/module.mk b/common/compression/module.mk
index d4620adf5b0..05c6ec4f545 100644
--- a/common/compression/module.mk
+++ b/common/compression/module.mk
@@ -7,6 +7,7 @@ MODULE_OBJS := \
 	gzio.o \
 	installshield_cab.o \
 	installshieldv3_archive.o \
+	packice.o \
 	powerpacker.o \
 	rnc_deco.o \
 	stuffit.o \
diff --git a/common/compression/packice.cpp b/common/compression/packice.cpp
new file mode 100644
index 00000000000..047eca4eb96
--- /dev/null
+++ b/common/compression/packice.cpp
@@ -0,0 +1,479 @@
+/* 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/>.
+ *
+ */
+
+/*
+ * Pack-Ice depacker is based on IceDecompressor:
+ * https://github.com/temisu/ancient
+ *
+ * BSD 2-Clause License
+ *
+ * Copyright (c) 2017-2026, Teemu Suutari
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "common/compression/packice.h"
+
+#include "common/endian.h"
+
+namespace Common {
+
+namespace PackIce {
+
+static uint32 makeBitMask(uint8 bitCount) {
+	return bitCount == 32 ? 0xFFFFFFFF : ((1U << bitCount) - 1);
+}
+
+static bool detectHeader(uint32 hdr, uint32 footer) {
+	return footer == MKTAG('I', 'c', 'e', '!') ||
+			hdr == MKTAG('I', 'c', 'e', '!') ||
+			hdr == MKTAG('T', 'M', 'M', '!') ||
+			hdr == MKTAG('T', 'S', 'M', '!') ||
+			hdr == MKTAG('S', 'H', 'E', '!') ||
+			hdr == MKTAG('I', 'C', 'E', '!');
+}
+
+class BackwardInputStream {
+public:
+	BackwardInputStream(const byte *data, uint32 startOffset, uint32 endOffset)
+		: _data(data), _pos(endOffset), _startOffset(startOffset) {
+	}
+
+	bool readByte(byte &value) {
+		if (_pos <= _startOffset)
+			return false;
+		value = _data[--_pos];
+		return true;
+	}
+
+	bool readBE32(uint32 &value) {
+		if (_pos < _startOffset + 4)
+			return false;
+		_pos -= 4;
+		value = READ_BE_UINT32(_data + _pos);
+		return true;
+	}
+
+	bool eof() const {
+		return _pos == _startOffset;
+	}
+
+	uint32 remainingBytes() const {
+		return _pos - _startOffset;
+	}
+
+private:
+	const byte *_data;
+	uint32 _pos;
+	uint32 _startOffset;
+};
+
+class MSBBitReader {
+public:
+	MSBBitReader(BackwardInputStream &inputStream) : _inputStream(inputStream) {
+	}
+
+	void reset(uint32 value, uint8 bitCount) {
+		_bitBuffer = value;
+		_bitsLeft = bitCount;
+	}
+
+	bool readBitsBE32(uint32 count, uint32 &value) {
+		value = 0;
+		while (count) {
+			if (!_bitsLeft) {
+				if (!_inputStream.readBE32(_bitBuffer))
+					return false;
+				_bitsLeft = 32;
+			}
+			const uint8 bitsToRead = (count < _bitsLeft) ? (uint8)count : _bitsLeft;
+			_bitsLeft -= bitsToRead;
+			const uint32 nextBits = (_bitBuffer >> _bitsLeft) & makeBitMask(bitsToRead);
+			value = bitsToRead == 32 ? nextBits : ((value << bitsToRead) | nextBits);
+			count -= bitsToRead;
+		}
+		return true;
+	}
+
+	bool readBits8(uint32 count, uint32 &value) {
+		value = 0;
+		while (count) {
+			if (!_bitsLeft) {
+				byte nextByte = 0;
+				if (!_inputStream.readByte(nextByte))
+					return false;
+				_bitBuffer = nextByte;
+				_bitsLeft = 8;
+			}
+			const uint8 bitsToRead = (count < _bitsLeft) ? (uint8)count : _bitsLeft;
+			_bitsLeft -= bitsToRead;
+			const uint32 nextBits = (_bitBuffer >> _bitsLeft) & makeBitMask(bitsToRead);
+			value = bitsToRead == 32 ? nextBits : ((value << bitsToRead) | nextBits);
+			count -= bitsToRead;
+		}
+		return true;
+	}
+
+	uint32 availableBits() const {
+		return _bitsLeft + _inputStream.remainingBytes() * 8;
+	}
+
+private:
+	BackwardInputStream &_inputStream;
+	uint32 _bitBuffer = 0;
+	uint8 _bitsLeft = 0;
+};
+
+class BitReaderProxy {
+public:
+	BitReaderProxy(MSBBitReader &bitReader, bool useBytes) : _bitReader(bitReader), _useBytes(useBytes) {
+	}
+
+	bool readBits(uint32 count, uint32 &value) {
+		return _useBytes ? _bitReader.readBits8(count, value) : _bitReader.readBitsBE32(count, value);
+	}
+
+private:
+	MSBBitReader &_bitReader;
+	bool _useBytes;
+};
+
+class VariableLengthCodeDecoder {
+public:
+	VariableLengthCodeDecoder(const uint8 *bitLengths, uint32 count) : _bitLengths(bitLengths), _count(count) {
+		_offsets[0] = 0;
+		for (uint32 i = 1; i < _count; ++i)
+			_offsets[i] = _offsets[i - 1] + (1U << _bitLengths[i - 1]);
+	}
+
+	bool decode(BitReaderProxy &bits, uint32 base, uint32 &value) const {
+		if (base >= _count)
+			return false;
+
+		uint32 extra = 0;
+		if (!bits.readBits(_bitLengths[base], extra))
+			return false;
+		value = _offsets[base] + extra;
+		return true;
+	}
+
+	bool decodeCascade(BitReaderProxy &bits, uint32 &value) const {
+		for (uint32 i = 0; i < _count; ++i) {
+			const uint8 bitLength = _bitLengths[i];
+			uint32 extra = 0;
+			if (!bits.readBits(bitLength, extra))
+				return false;
+			if (i + 1 == _count || extra != makeBitMask(bitLength)) {
+				value = _offsets[i] - i + extra;
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+private:
+	const uint8 *_bitLengths;
+	uint32 _count;
+	uint32 _offsets[8];
+};
+
+class BackwardOutputStream {
+public:
+	BackwardOutputStream(Common::Array<byte> &data) : _data(data), _pos(data.size()) {
+	}
+
+	bool writeByte(byte value) {
+		if (!_pos)
+			return false;
+		_data[--_pos] = value;
+		return true;
+	}
+
+	bool copy(uint32 distance, uint32 count) {
+		if (!distance || _pos < count || _pos + distance > _data.size())
+			return false;
+
+		for (uint32 i = 0; i < count; ++i) {
+			--_pos;
+			_data[_pos] = _data[_pos + distance];
+		}
+		return true;
+	}
+
+	bool eof() const {
+		return _pos == 0;
+	}
+
+private:
+	Common::Array<byte> &_data;
+	uint32 _pos;
+};
+
+static bool decompressInternal(const byte *data, uint32 streamStart, uint32 streamEnd, Common::Array<byte> &rawData,
+		Common::PackIceVersion version, bool useBytes, bool allowPictureMode) {
+	static const uint8 litOld[] = { 1, 2, 2, 3, 10 };
+	static const uint8 litNew[] = { 1, 2, 2, 3, 8, 15 };
+	static const uint8 countBaseBits[] = { 1, 1, 1, 1 };
+	static const uint8 countBits[] = { 0, 0, 1, 2, 10 };
+	static const uint8 distanceBaseBits[] = { 1, 1 };
+	static const uint8 distanceBits[] = { 5, 8, 12 };
+
+	BackwardInputStream inputStream(data, streamStart, streamEnd);
+	MSBBitReader bitReader(inputStream);
+	BitReaderProxy bits(bitReader, useBytes);
+
+	uint32 value = 0;
+	if (useBytes) {
+		byte initialByte = 0;
+		if (!inputStream.readByte(initialByte))
+			return false;
+		value = initialByte;
+	} else {
+		if (!inputStream.readBE32(value))
+			return false;
+	}
+
+	uint32 shiftedValue = value;
+	uint32 count = 0;
+	while (shiftedValue) {
+		shiftedValue <<= 1;
+		++count;
+	}
+	if (count)
+		--count;
+	if (count)
+		bitReader.reset(value >> (32 - count), count - (useBytes ? 24 : 0));
+
+	BackwardOutputStream outputStream(rawData);
+
+	VariableLengthCodeDecoder litVlcDecoderOld(litOld, ARRAYSIZE(litOld));
+	VariableLengthCodeDecoder litVlcDecoderNew(litNew, ARRAYSIZE(litNew));
+	VariableLengthCodeDecoder countBaseDecoder(countBaseBits, ARRAYSIZE(countBaseBits));
+	VariableLengthCodeDecoder countDecoder(countBits, ARRAYSIZE(countBits));
+	VariableLengthCodeDecoder distanceBaseDecoder(distanceBaseBits, ARRAYSIZE(distanceBaseBits));
+	VariableLengthCodeDecoder distanceDecoder(distanceBits, ARRAYSIZE(distanceBits));
+
+	for (;;) {
+		uint32 bit = 0;
+		if (!bits.readBits(1, bit))
+			return false;
+		if (bit) {
+			uint32 litLength = 0;
+			if (version ? !litVlcDecoderNew.decodeCascade(bits, litLength) :
+					!litVlcDecoderOld.decodeCascade(bits, litLength))
+				return false;
+			++litLength;
+			for (uint32 i = 0; i < litLength; ++i) {
+				byte literal = 0;
+				if (!inputStream.readByte(literal) || !outputStream.writeByte(literal))
+					return false;
+			}
+		}
+
+		if (outputStream.eof())
+			break;
+
+		uint32 countBase = 0;
+		if (!countBaseDecoder.decodeCascade(bits, countBase))
+			return false;
+		uint32 copyCount = 0;
+		if (!countDecoder.decode(bits, countBase, copyCount))
+			return false;
+		copyCount += 2;
+
+		uint32 distance = 0;
+		if (copyCount == 2) {
+			uint32 bitValue = 0;
+			if (!bits.readBits(1, bitValue))
+				return false;
+			if (bitValue) {
+				if (!bits.readBits(9, distance))
+					return false;
+				distance += 0x40;
+			} else if (!bits.readBits(6, distance)) {
+				return false;
+			}
+			distance += copyCount - (useBytes ? 1 : 0);
+		} else {
+			uint32 distanceBase = 0;
+			if (!distanceBaseDecoder.decodeCascade(bits, distanceBase))
+				return false;
+			if (distanceBase < 2)
+				distanceBase ^= 1;
+			if (!distanceDecoder.decode(bits, distanceBase, distance))
+				return false;
+			if (useBytes) {
+				if (distance)
+					distance += copyCount - 1;
+				else
+					distance = 1;
+			} else {
+				distance += copyCount;
+			}
+		}
+
+		if (!outputStream.copy(distance, copyCount))
+			return false;
+	}
+
+	if (allowPictureMode && version && bitReader.availableBits()) {
+		uint32 pictureMode = 0;
+		if (!bits.readBits(1, pictureMode))
+			return false;
+		if (pictureMode) {
+			uint32 pictureSize = 32000;
+			if (version == Common::kPackIceVersion231) {
+				uint32 hasPictureSize = 0;
+				if (bitReader.availableBits() >= 17 && bits.readBits(1, hasPictureSize) && hasPictureSize) {
+					if (!bits.readBits(16, pictureSize))
+						return false;
+					pictureSize = pictureSize * 8 + 8;
+				}
+			}
+			if (!Common::convertPackIcePictureData(rawData, pictureSize))
+				return false;
+		}
+	}
+
+	return inputStream.eof();
+}
+
+} // End of namespace PackIce
+
+bool detectPackIceHeader(const byte *data, uint32 size, bool exactSizeKnown) {
+	if (!data || size < 8)
+		return false;
+
+	const uint32 hdr = READ_BE_UINT32(data);
+	const uint32 footer = exactSizeKnown ? READ_BE_UINT32(data + size - 4) : 0;
+	return PackIce::detectHeader(hdr, footer);
+}
+
+bool parsePackIceHeader(const byte *data, uint32 size, bool exactSizeKnown, PackIceHeader &header) {
+	if (!detectPackIceHeader(data, size, exactSizeKnown))
+		return false;
+
+	const uint32 hdr = READ_BE_UINT32(data);
+	const uint32 footer = exactSizeKnown ? READ_BE_UINT32(data + size - 4) : 0;
+	if (footer == MKTAG('I', 'c', 'e', '!')) {
+		header.packedSize = size;
+		header.rawSize = READ_BE_UINT32(data + size - 8);
+		header.version = kPackIceVersion110;
+	} else {
+		header.packedSize = READ_BE_UINT32(data + 4);
+		if (!header.packedSize || header.packedSize > size)
+			return false;
+		header.rawSize = READ_BE_UINT32(data + 8);
+		header.version = (hdr == MKTAG('I', 'C', 'E', '!')) ? kPackIceVersion231 : kPackIceVersion200;
+	}
+
+	return header.rawSize != 0;
+}
+
+const char *getPackIceName(PackIceVersion version) {
+	static const char *const names[] = {
+		"Ice: Pack-Ice v1.1 - v1.14",
+		"Ice: Pack-Ice v2.0 - v2.20",
+		"ICE: Pack-Ice v2.31+"
+	};
+
+	return names[version];
+}
+
+bool decompressPackIce(const byte *data, uint32 size, Common::Array<byte> &out, bool exactSizeKnown) {
+	PackIceHeader header;
+	if (!parsePackIceHeader(data, size, exactSizeKnown, header))
+		return false;
+
+	out.resize(header.rawSize);
+	const uint32 streamStart = header.version ? 12 : 0;
+	const uint32 streamEnd = header.packedSize - (header.version ? 0 : 8);
+
+	if (header.version) {
+		if (header.version == kPackIceVersion200 &&
+				PackIce::decompressInternal(data, streamStart, streamEnd, out, header.version, false, true))
+			return true;
+
+		out.resize(header.rawSize);
+		if (!PackIce::decompressInternal(data, streamStart, streamEnd, out, header.version, true, true))
+			return false;
+		return true;
+	}
+
+	return PackIce::decompressInternal(data, streamStart, streamEnd, out, header.version, false, false);
+}
+
+bool decompressPackIceStream(const byte *data, uint32 size, uint32 streamStart, uint32 streamEnd,
+		uint32 rawSize, Common::Array<byte> &out, bool useBytes) {
+	if (!data || streamStart >= streamEnd || streamEnd > size || rawSize == 0)
+		return false;
+
+	out.resize(rawSize);
+	return PackIce::decompressInternal(data, streamStart, streamEnd, out, kPackIceVersion200, useBytes, false);
+}
+
+bool convertPackIcePictureData(Common::Array<byte> &data, uint32 pictureSize) {
+	if (!pictureSize)
+		return true;
+	if (data.size() < pictureSize)
+		return false;
+
+	const uint32 start = data.size() - pictureSize;
+	for (uint32 i = start; i + 7 < data.size(); i += 8) {
+		uint16 values[4] = { 0, 0, 0, 0 };
+		for (uint32 j = 0; j < 8; j += 2) {
+			uint16 tmp = READ_BE_UINT16(data.begin() + i + 6 - j);
+			for (uint32 k = 0; k < 16; ++k) {
+				values[k & 3] = (uint16)((values[k & 3] << 1) | (tmp >> 15));
+				tmp <<= 1;
+			}
+		}
+		for (uint32 j = 0; j < 4; ++j) {
+			data[i + j * 2] = (byte)(values[j] >> 8);
+			data[i + j * 2 + 1] = (byte)values[j];
+		}
+	}
+
+	return true;
+}
+
+} // End of namespace Common
diff --git a/common/compression/packice.h b/common/compression/packice.h
new file mode 100644
index 00000000000..7a1c3240c5c
--- /dev/null
+++ b/common/compression/packice.h
@@ -0,0 +1,58 @@
+/* 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/>.
+ *
+ */
+
+/**
+ * @file
+ * Pack-Ice decompressor.
+ */
+
+#ifndef COMMON_PACKICE_H
+#define COMMON_PACKICE_H
+
+#include "common/array.h"
+#include "common/scummsys.h"
+
+namespace Common {
+
+enum PackIceVersion {
+	kPackIceVersion110 = 0,
+	kPackIceVersion200 = 1,
+	kPackIceVersion231 = 2
+};
+
+struct PackIceHeader {
+	uint32 packedSize;
+	uint32 rawSize;
+	PackIceVersion version;
+};
+
+bool detectPackIceHeader(const byte *data, uint32 size, bool exactSizeKnown);
+bool parsePackIceHeader(const byte *data, uint32 size, bool exactSizeKnown, PackIceHeader &header);
+const char *getPackIceName(PackIceVersion version);
+
+bool decompressPackIce(const byte *data, uint32 size, Common::Array<byte> &out, bool exactSizeKnown = true);
+bool decompressPackIceStream(const byte *data, uint32 size, uint32 streamStart, uint32 streamEnd,
+		uint32 rawSize, Common::Array<byte> &out, bool useBytes);
+bool convertPackIcePictureData(Common::Array<byte> &data, uint32 pictureSize = 32000);
+
+} // End of namespace Common
+
+#endif
diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp
index b5e91dc8bed..7d68c2e58e1 100644
--- a/engines/agos/res_snd.cpp
+++ b/engines/agos/res_snd.cpp
@@ -21,6 +21,7 @@
 
 #include "common/config-manager.h"
 #include "common/array.h"
+#include "common/compression/packice.h"
 #include "common/file.h"
 #include "common/memstream.h"
 #include "common/textconsole.h"
@@ -110,193 +111,23 @@ static Common::Array<byte> unsquashAcornDesktopTracker(const byte *data, uint32
 	}
 }
 
-/*
- * Pack-Ice depacker is based on a simplified version of IceDecompressor:
- * https://github.com/temisu/ancient
- *
- * BSD 2-Clause License
- *
- * Copyright (c) 2017-2026, Teemu Suutari
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- *   this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- *   this list of conditions and the following disclaimer in the documentation
- *   and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
 static bool isElvira1PackIcePrg(const Common::Array<byte> &data) {
 	return data.size() >= 0x26 && !memcmp(data.begin() + 0x1E, "Pack-Ice", 8);
 }
 
-static uint32 makePackIceBitMask(uint8 bitCount) {
-	return bitCount == 32 ? 0xFFFFFFFF : ((1U << bitCount) - 1);
-}
-
-class PackIceBitReader {
-public:
-	PackIceBitReader(const Common::Array<byte> &data, uint32 startOffset, uint32 endOffset)
-		: _data(data), _pos(endOffset), _startOffset(startOffset) {
-	}
-
-	void reset(uint32 value, uint8 bitCount) {
-		_bitBuffer = value;
-		_bitsLeft = bitCount;
-	}
-
-	uint8 readByte() {
-		if (_pos <= _startOffset)
-			error("AGOS: Pack-Ice depacker byte underrun");
-		return _data[--_pos];
-	}
-
-	uint32 readBitsBE32(uint32 count) {
-		uint32 value = 0;
-		while (count) {
-			if (!_bitsLeft) {
-				if (_pos < _startOffset + 4)
-					error("AGOS: Pack-Ice depacker long underrun");
-				_pos -= 4;
-				_bitBuffer = READ_BE_UINT32(_data.begin() + _pos);
-				_bitsLeft = 32;
-			}
-			const uint8 bitsToRead = (count < _bitsLeft) ? (uint8)count : _bitsLeft;
-			_bitsLeft -= bitsToRead;
-			const uint32 nextBits = (_bitBuffer >> _bitsLeft) & makePackIceBitMask(bitsToRead);
-			value = bitsToRead == 32 ? nextBits : ((value << bitsToRead) | nextBits);
-			count -= bitsToRead;
-		}
-		return value;
-	}
-
-	bool eof() const {
-		return _pos == _startOffset;
-	}
-
-private:
-	const Common::Array<byte> &_data;
-	uint32 _pos;
-	uint32 _startOffset;
-	uint32 _bitBuffer = 0;
-	uint8 _bitsLeft = 0;
-};
-
-static uint32 decodePackIceVlc(PackIceBitReader &bits, const uint8 *bitLengths, const uint32 *offsets, uint32 count,
-		uint32 base) {
-	if (base >= count)
-		error("AGOS: Pack-Ice depacker invalid VLC base");
-
-	return offsets[base] + bits.readBitsBE32(bitLengths[base]);
-}
-
-static uint32 decodePackIceCascade(PackIceBitReader &bits, const uint8 *bitLengths, const uint32 *offsets,
-		uint32 count) {
-	for (uint32 i = 0; i < count; ++i) {
-		const uint8 bitLength = bitLengths[i];
-		const uint32 value = bits.readBitsBE32(bitLength);
-		if (i + 1 == count || value != makePackIceBitMask(bitLength))
-			return offsets[i] - i + value;
-	}
-
-	error("AGOS: Pack-Ice depacker invalid VLC cascade");
-}
-
 static bool depackElvira1PackIcePrg(const Common::Array<byte> &packedData, Common::Array<byte> &unpackedData) {
 	enum {
 		kPackedStreamStart = 0x021C,
 		kPackedStreamEnd = 0xAFE2,
 		kRawSize = 0x1694C
 	};
-	const uint8 literalBitLengths[] = {1, 2, 2, 3, 8, 15};
-	const uint32 literalOffsets[] = {0, 2, 6, 10, 18, 274};
-	const uint8 countBaseBitLengths[] = {1, 1, 1, 1};
-	const uint32 countBaseOffsets[] = {0, 2, 4, 6};
-	const uint8 countBitLengths[] = {0, 0, 1, 2, 10};
-	const uint32 countOffsets[] = {0, 1, 2, 4, 8};
-	const uint8 distanceBaseBitLengths[] = {1, 1};
-	const uint32 distanceBaseOffsets[] = {0, 2};
-	const uint8 distanceBitLengths[] = {5, 8, 12};
-	const uint32 distanceOffsets[] = {0, 32, 288};
 
 	if (!isElvira1PackIcePrg(packedData) || packedData.size() < kPackedStreamEnd)
 		return false;
 
-	PackIceBitReader bits(packedData, kPackedStreamStart, kPackedStreamEnd);
-	uint32 initialBits = bits.readBitsBE32(32);
-	uint32 shiftedBits = initialBits;
-	uint32 initialBitCount = 0;
-	while (shiftedBits) {
-		shiftedBits <<= 1;
-		++initialBitCount;
-	}
-	if (initialBitCount)
-		--initialBitCount;
-	if (initialBitCount)
-		bits.reset(initialBits >> (32 - initialBitCount), (uint8)initialBitCount);
-
-	unpackedData.resize(kRawSize);
-	uint32 outPos = kRawSize;
-
-	while (true) {
-		if (bits.readBitsBE32(1)) {
-			const uint32 literalLength = decodePackIceCascade(bits, literalBitLengths, literalOffsets,
-				ARRAYSIZE(literalBitLengths)) + 1;
-			for (uint32 i = 0; i < literalLength; ++i) {
-				if (!outPos)
-					return false;
-				unpackedData[--outPos] = bits.readByte();
-			}
-		}
-
-		if (!outPos)
-			break;
-
-		const uint32 countBase = decodePackIceCascade(bits, countBaseBitLengths, countBaseOffsets,
-			ARRAYSIZE(countBaseBitLengths));
-		const uint32 copyCount = decodePackIceVlc(bits, countBitLengths, countOffsets, ARRAYSIZE(countBitLengths),
-			countBase) + 2;
-
-		uint32 distance = 0;
-		if (copyCount == 2) {
-			if (bits.readBitsBE32(1))
-				distance = bits.readBitsBE32(9) + 0x40;
-			else
-				distance = bits.readBitsBE32(6);
-			distance += copyCount;
-		} else {
-			uint32 distanceBase = decodePackIceCascade(bits, distanceBaseBitLengths, distanceBaseOffsets,
-				ARRAYSIZE(distanceBaseBitLengths));
-			if (distanceBase < 2)
-				distanceBase ^= 1;
-			distance = decodePackIceVlc(bits, distanceBitLengths, distanceOffsets, ARRAYSIZE(distanceBitLengths), distanceBase);
-			distance += copyCount;
-		}
-
-		if (!distance || outPos < copyCount || outPos + distance > kRawSize)
-			return false;
-
-		for (uint32 i = 0; i < copyCount; ++i) {
-			--outPos;
-			unpackedData[outPos] = unpackedData[outPos + distance];
-		}
-	}
-
-	return bits.eof() && unpackedData.size() >= 28 && READ_BE_UINT16(unpackedData.begin()) == 0x601A;
+	return Common::decompressPackIceStream(packedData.begin(), packedData.size(), kPackedStreamStart,
+			kPackedStreamEnd, kRawSize, unpackedData, false) &&
+			unpackedData.size() >= 28 && READ_BE_UINT16(unpackedData.begin()) == 0x601A;
 }
 
 static bool extractEmbeddedTosPrg(const Common::Array<byte> &containerPrg, Common::Array<byte> &innerPrg) {




More information about the Scummvm-git-logs mailing list