[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