[Scummvm-git-logs] scummvm master -> 56000821acf65621319f30f4ca42f5295661fc62

neuromancer noreply at scummvm.org
Tue Sep 8 20:14:04 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:
d8ec6445fe FREESCAPE: moved decompression of castle for c64 to unp64 in common
6efdeb5b6f FREESCAPE: decompress some freescape games using unp64
57c0ce99a6 FREESCAPE: decompress driller c64 releases using unp64
56000821ac COLONY: various forklift behavior fixes


Commit: d8ec6445fedfbf0389e92608b828c19583706e87
    https://github.com/scummvm/scummvm/commit/d8ec6445fedfbf0389e92608b828c19583706e87
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T22:11:14+02:00

Commit Message:
FREESCAPE: moved decompression of castle for c64 to unp64 in common

Changed paths:
  A common/compression/unp64/scanners/action_replay.cpp
    common/compression/module.mk
    common/compression/unp64/scanners/scanners.cpp
    common/compression/unp64/unp64.cpp
    engines/freescape/configure.engine
    engines/freescape/games/castle/c64.cpp


diff --git a/common/compression/module.mk b/common/compression/module.mk
index 8ef332a20ee..4022ea0da76 100644
--- a/common/compression/module.mk
+++ b/common/compression/module.mk
@@ -22,6 +22,7 @@ MODULE_OBJS += \
 	unp64/exo_util.o \
 	unp64/scanners/scanners.o \
 	unp64/scanners/action_packer.o \
+	unp64/scanners/action_replay.o \
 	unp64/scanners/byte_boiler.o \
 	unp64/scanners/caution.o \
 	unp64/scanners/ccs.o \
diff --git a/common/compression/unp64/scanners/action_replay.cpp b/common/compression/unp64/scanners/action_replay.cpp
new file mode 100644
index 00000000000..4d5b5cef9c7
--- /dev/null
+++ b/common/compression/unp64/scanners/action_replay.cpp
@@ -0,0 +1,75 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/endian.h"
+#include "common/compression/unp64/exo_util.h"
+#include "common/compression/unp64/unp64.h"
+
+namespace Common {
+namespace Unp64 {
+
+// Adapted from UNP64 2.42's Action Replay, Super Snapshot and Freeze Machine scanner.
+void scnActionReplay(UnpStr *unp) {
+	if (unp->_idFlag || unp->_depAdr)
+		return;
+
+	const byte *mem = unp->_mem;
+	if (READ_LE_UINT32(mem + 0x80f) == 0xdd0d8d7f &&
+		READ_LE_UINT32(mem + 0x927) == 0x4ce7d0ca) {
+		uint16 destination = READ_LE_UINT16(mem + 0x92b);
+		for (uint offset = 0; offset < 0x100; ++offset) {
+			const byte *code = mem + 0xa00 + offset;
+			if (code[0] == 0x99 && READ_LE_UINT16(code + 1) == destination &&
+				(code[0x11] == 0x40 || code[0x10] == 0x40)) {
+				unp->_depAdr = 0x100 + offset;
+				break;
+			}
+		}
+		if (!unp->_depAdr)
+			unp->_depAdr = destination;
+	} else if (READ_LE_UINT32(mem + 0x812) == 0xdd0d8ddc) {
+		for (uint address = 0x900; address < 0xcfff; ++address) {
+			if (READ_LE_UINT32(mem + address) == 0xa9dc0e8d &&
+				READ_LE_UINT32(mem + address + 0x0a) == 0xa9dd0e8d &&
+				mem[address + 0x18] == 0x4c) {
+				unp->_depAdr = READ_LE_UINT16(mem + address + 0x19);
+				break;
+			}
+		}
+	} else if (READ_LE_UINT32(mem + 0x80f) == 0xdd0d8d7f &&
+		READ_LE_UINT32(mem + 0x8ef) == 0x0330bd01 &&
+		READ_LE_UINT32(mem + 0x8fa) == 0x7e4c00a0 &&
+		READ_LE_UINT32(mem + 0xbc8) == 0x4c01c6df) {
+		unp->_depAdr = READ_LE_UINT16(mem + 0xbcc);
+		unp->_strMem = 2;
+		unp->_endAdr = 0x10000;
+	}
+
+	if (unp->_depAdr) {
+		unp->_rtiFrc = 1;
+		if (unp->_info->_run == -1)
+			unp->_forced = 0x80d;
+		unp->_idFlag = 1;
+	}
+}
+
+} // End of namespace Unp64
+} // End of namespace Common
diff --git a/common/compression/unp64/scanners/scanners.cpp b/common/compression/unp64/scanners/scanners.cpp
index cdd537bd9cd..30af41dcca2 100644
--- a/common/compression/unp64/scanners/scanners.cpp
+++ b/common/compression/unp64/scanners/scanners.cpp
@@ -27,6 +27,7 @@ namespace Unp64 {
 
 void scnECA(UnpStr *unp);
 void scnExpert(UnpStr *unp);
+void scnActionReplay(UnpStr *unp);
 void scnCruel(UnpStr *unp);
 void scnPuCrunch(UnpStr *unp);
 void scnByteBoiler(UnpStr *unp);
@@ -44,6 +45,7 @@ void scnExomizer(UnpStr *unp);
 Scnptr g_scanFunc[] = {
 	scnECA,
 	scnExpert,
+	scnActionReplay,
 	scnCruel,
 	scnPuCrunch,
 	scnByteBoiler,
diff --git a/common/compression/unp64/unp64.cpp b/common/compression/unp64/unp64.cpp
index 140bdc7e6d6..11477446b9b 100644
--- a/common/compression/unp64/unp64.cpp
+++ b/common/compression/unp64/unp64.cpp
@@ -96,6 +96,9 @@ static int isBasicRun2(int pc) {
 }
 
 int unp64(const byte *compressed, uint32 length, byte *destinationBuffer, uint32 *finalLength, const char *switches) {
+	if (!compressed || !destinationBuffer || !finalLength || length < 2)
+		return 0;
+	*finalLength = 0;
 
 	char settings[4][64];
 	int numSettings = 0;
@@ -716,6 +719,8 @@ int unp64(const byte *compressed, uint32 length, byte *destinationBuffer, uint32
 	if (unp._endAdr < unp._strMem)
 		unp._endAdr = 0x10000;
 
+	if (unp._strMem < 2 || unp._strMem >= unp._endAdr)
+		return 0;
 	mem[unp._strMem - 2] = unp._strMem & 0xff;
 	mem[unp._strMem - 1] = unp._strMem >> 8;
 
diff --git a/engines/freescape/configure.engine b/engines/freescape/configure.engine
index 6afbc3b536c..193df84aea0 100644
--- a/engines/freescape/configure.engine
+++ b/engines/freescape/configure.engine
@@ -1,3 +1,3 @@
 # This file is included from the main "configure" script
 # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
-add_engine freescape "Freescape" yes "" "" "highres 16bit 3d" "tinygl sid_audio"
+add_engine freescape "Freescape" yes "" "" "highres 16bit 3d unp64" "tinygl sid_audio"
diff --git a/engines/freescape/games/castle/c64.cpp b/engines/freescape/games/castle/c64.cpp
index 6cf1461de86..b647f992ceb 100644
--- a/engines/freescape/games/castle/c64.cpp
+++ b/engines/freescape/games/castle/c64.cpp
@@ -19,6 +19,8 @@
  *
  */
 
+#include "common/compression/unp64.h"
+#include "common/endian.h"
 #include "common/file.h"
 #include "common/memstream.h"
 #include "common/random.h"
@@ -32,7 +34,7 @@
 namespace Freescape {
 
 enum {
-	kCastleC64DatabaseOffset = 0x9951,
+	kCastleC64DatabaseAddress = 0x9d00,
 	kCastleC64RuntimeDemoPointerOffset = 0x42,
 	kCastleC64RuntimeAreaTableOffset = 0x4f,
 	kCastleC64CompactDemoPointerOffset = 0x3e,
@@ -69,55 +71,41 @@ static uint32 castleC64UIColor(const Graphics::PixelFormat &format, byte color)
 	return format.ARGBToColor(255, rgb[0], rgb[1], rgb[2]);
 }
 
-static Common::Array<byte> unpackCastleC64UI(Common::SeekableReadStream *file) {
-	// The startup relocates the packed stream from $0d50 to $2708, then
-	// expands it into $0200..$ffff. Decode only through the screen attributes
-	// at $c400..$c7e7: the later bitmap pages require the separate tape loader.
-	// Page flags at $09ff descend through memory, least significant bit first.
-	// A clear bit selects a page with its own escape byte and count/value runs.
+static Common::Array<byte> unpackCastleC64(Common::SeekableReadStream *file) {
+	if (file->size() < 2 || file->size() > 0x10000)
+		error("Invalid Castle C64 program size");
+
 	Common::Array<byte> packed;
 	packed.resize(file->size());
 	file->seek(0);
-	if (packed.size() < 0x551 || file->read(packed.data(), packed.size()) != packed.size())
-		error("Unable to read Castle C64 UI data");
+	if (file->read(packed.data(), packed.size()) != packed.size() || READ_LE_UINT16(packed.data()) != 0x0801)
+		error("Unable to read Castle C64 program");
+
+	// The split tape loader preserves the rest of the snapshot at $d000.
+	Common::File loader;
+	if (loader.open("castlemaster.c64.loader")) {
+		const uint32 sourceOffset = 2 + 0x0847 - 0x0801;
+		const uint32 destinationOffset = 2 + 0xd000 - 0x0801;
+		if (loader.size() <= sourceOffset || loader.size() - sourceOffset > 0x3000 ||
+			loader.readUint16LE() != 0x0801 || packed.size() > destinationOffset)
+			error("Invalid Castle C64 split loader");
+		uint32 size = loader.size() - sourceOffset;
+		packed.resize(destinationOffset + size);
+		loader.seek(sourceOffset);
+		if (loader.read(packed.data() + destinationOffset, size) != size)
+			error("Truncated Castle C64 split loader");
+	}
 
 	Common::Array<byte> data;
-	data.resize(0xc800);
-	uint32 source = 0x551; // $0d50, including the PRG load-address adjustment
-	int flagOffset = 0x200; // $09ff
-	byte flags = packed[flagOffset];
-	int bitsLeft = 6; // The first two pages are not part of the packed stream.
-	for (uint page = 2; page < 0xc8; page++) {
-		if (!bitsLeft) {
-			flags = packed[--flagOffset];
-			bitsLeft = 8;
-		}
-		bool raw = flags & 1;
-		flags >>= 1;
-		bitsLeft--;
-		uint end = (page + 1) * 256;
-		if (source >= packed.size())
-			error("Truncated Castle C64 UI page %x", page);
-		byte escape = raw ? 0 : packed[source++];
-		for (uint dest = page * 256; dest < end;) {
-			if (source >= packed.size())
-				error("Truncated Castle C64 UI page %x", page);
-			byte value = packed[source++];
-			uint count = 1;
-			if (!raw && value == escape) {
-				if (source + 2 > packed.size())
-					error("Truncated Castle C64 UI run");
-				count = packed[source++];
-				if (!count)
-					count = 256;
-				value = packed[source++];
-			}
-			if (count > end - dest)
-				error("Castle C64 UI run crosses a page boundary");
-			while (count--)
-				data[dest++] = value;
-		}
-	}
+	data.resize(0x10000);
+	uint32 size = 0;
+	if (!Common::Unp64::unp64(packed.data(), packed.size(), data.data(), &size, nullptr) || size < 2)
+		error("Unable to unpack Castle C64 snapshot");
+	uint32 address = READ_LE_UINT16(data.data());
+	if (address > 0x1401 || size - 2 > data.size() - address || address + size - 2 < 0xc800)
+		error("Incomplete Castle C64 snapshot");
+	memmove(data.data() + address, data.data() + 2, size - 2);
+	memset(data.data(), 0, address);
 	return data;
 }
 
@@ -148,32 +136,6 @@ static void loadCastleC64Frame(const Common::Array<byte> &data, uint address, Gr
 	loadCastleC64Bitmap(data, pixels, width, height, surface);
 }
 
-struct CastleC64Repeat {
-	uint16 offset;
-	byte count;
-	byte value;
-};
-
-const uint16 kCastleC64DatabaseSkips[] = {
-	0x01fc, 0x02fc, 0x05fa, 0x08f9, 0x09f9, 0x0af9, 0x0bf4, 0x0cf0,
-	0x0deb, 0x0fe9, 0x10e5, 0x11e3, 0x13e1, 0x14de, 0x18dd, 0x19db,
-	0x1cd9, 0x1ed7, 0x20cf, 0x21c8
-};
-
-const CastleC64Repeat kCastleC64DatabaseRepeats[] = {
-	{ 0x0006, 4, 0x00 }, { 0x0009, 4, 0xff }, { 0x0010, 4, 0x55 }, { 0x001b, 4, 0xaa },
-	{ 0x02dd, 4, 0x02 }, { 0x0327, 4, 0x00 }, { 0x0347, 4, 0x01 }, { 0x0355, 4, 0x00 },
-	{ 0x05fe, 4, 0x00 }, { 0x0610, 4, 0x01 }, { 0x0936, 4, 0x00 }, { 0x0aca, 4, 0x00 },
-	{ 0x0b58, 6, 0x00 }, { 0x0bd9, 6, 0x00 }, { 0x0c5a, 4, 0x01 }, { 0x0c69, 4, 0x01 },
-	{ 0x0c78, 4, 0x02 }, { 0x0c87, 4, 0x02 }, { 0x0c95, 4, 0x00 }, { 0x0d43, 6, 0x00 },
-	{ 0x0d9e, 6, 0x00 }, { 0x0ec9, 6, 0x00 }, { 0x0fea, 5, 0x00 }, { 0x1085, 6, 0x00 },
-	{ 0x1163, 6, 0x00 }, { 0x127e, 6, 0x00 }, { 0x140b, 4, 0x06 }, { 0x1497, 6, 0x00 },
-	{ 0x1543, 5, 0x00 }, { 0x1910, 6, 0x00 }, { 0x1a1b, 6, 0x00 }, { 0x1da9, 6, 0x00 },
-	{ 0x1edb, 6, 0x00 }, { 0x1ee1, 6, 0x00 }, { 0x1ee7, 6, 0x00 }, { 0x20d0, 4, 0x00 },
-	{ 0x2128, 6, 0x00 }, { 0x217e, 6, 0x00 }, { 0x21c5, 4, 0x00 }, { 0x2250, 6, 0x00 },
-	{ 0x2255, 113, 0x00 }
-};
-
 uint16 readCastleC64Uint16LE(const Common::Array<byte> &data, uint32 offset) {
 	if (offset + 1 >= data.size())
 		error("Castle C64 database pointer read out of range at 0x%x", offset);
@@ -181,60 +143,6 @@ uint16 readCastleC64Uint16LE(const Common::Array<byte> &data, uint32 offset) {
 	return data[offset] | (data[offset + 1] << 8);
 }
 
-Common::Array<byte> normalizeCastleC64Database(Common::SeekableReadStream *file) {
-	file->seek(kCastleC64DatabaseOffset);
-	if (file->pos() != kCastleC64DatabaseOffset)
-		error("Unable to seek to Castle C64 database at 0x%x", kCastleC64DatabaseOffset);
-	if (file->size() <= kCastleC64DatabaseOffset)
-		error("Castle C64 database file is too short");
-
-	uint32 rawSize = file->size() - kCastleC64DatabaseOffset;
-	Common::Array<byte> raw;
-	raw.resize(rawSize);
-	if (file->read(&raw[0], rawSize) != rawSize)
-		error("Unable to read Castle C64 database");
-	if (raw.size() < 3)
-		error("Castle C64 database is too short");
-
-	const uint16 decodedSize = readCastleC64Uint16LE(raw, 1);
-	Common::Array<byte> decoded;
-	uint32 sourceOffset = 0;
-	uint skipIndex = 0;
-	uint repeatIndex = 0;
-
-	while (decoded.size() < decodedSize) {
-		if (sourceOffset >= raw.size())
-			error("Castle C64 database normalization ran out of source data");
-
-		if (skipIndex < ARRAYSIZE(kCastleC64DatabaseSkips) && sourceOffset == kCastleC64DatabaseSkips[skipIndex]) {
-			sourceOffset++;
-			skipIndex++;
-			continue;
-		}
-
-		if (repeatIndex < ARRAYSIZE(kCastleC64DatabaseRepeats) && sourceOffset == kCastleC64DatabaseRepeats[repeatIndex].offset) {
-			const CastleC64Repeat &repeat = kCastleC64DatabaseRepeats[repeatIndex];
-			if (sourceOffset + 2 >= raw.size() || raw[sourceOffset + 1] != repeat.count || raw[sourceOffset + 2] != repeat.value)
-				error("Castle C64 database repeat mismatch at 0x%x", sourceOffset);
-
-			for (uint i = 0; i < repeat.count && decoded.size() < decodedSize; i++)
-				decoded.push_back(repeat.value);
-
-			sourceOffset += 3;
-			repeatIndex++;
-			continue;
-		}
-
-		decoded.push_back(raw[sourceOffset++]);
-	}
-
-	if (skipIndex != ARRAYSIZE(kCastleC64DatabaseSkips) || repeatIndex != ARRAYSIZE(kCastleC64DatabaseRepeats))
-		error("Castle C64 database normalization did not consume all relocation entries");
-
-	debugC(1, kFreescapeDebugParser, "Castle C64 normalized database: 0x%x -> 0x%x bytes", sourceOffset, decodedSize);
-	return decoded;
-}
-
 static Common::Array<Graphics::ManagedSurface *> loadCastleC64Font(const Common::Array<byte> &data) {
 	Common::Array<Graphics::ManagedSurface *> chars;
 
@@ -266,7 +174,7 @@ class CastleC64DatabaseReadStream : public Common::SeekableReadStream {
 public:
 	CastleC64DatabaseReadStream(const Common::Array<byte> &data) : _data(data), _pos(0), _eos(false), _colorMapRead(false) {
 		if (_data.size() < kCastleC64RuntimeAreaTableOffset)
-			error("Castle C64 normalized database is too short");
+			error("Castle C64 database is too short");
 
 		for (uint i = 0; i < 4; i++)
 			_compactPointerBytes[i] = _data[kCastleC64RuntimeDemoPointerOffset + i];
@@ -456,7 +364,7 @@ void CastleEngine::loadAssetsC64FullGame() {
 	if (!file.isOpen())
 		error("Failed to open castlemaster.c64.data");
 
-	Common::Array<byte> uiData = unpackCastleC64UI(&file);
+	Common::Array<byte> uiData = unpackCastleC64(&file);
 	Common::MemoryReadStream uiStream(uiData.data(), uiData.size());
 	Common::Array<Graphics::ManagedSurface *> chars = loadCastleC64Font(uiData);
 	_font = Font(chars);
@@ -576,7 +484,10 @@ void CastleEngine::loadAssetsC64FullGame() {
 		_flagFrames.push_back(flag);
 	}
 
-	Common::Array<byte> database = normalizeCastleC64Database(&file);
+	uint16 databaseSize = readCastleC64Uint16LE(uiData, kCastleC64DatabaseAddress + 1);
+	if (databaseSize < kCastleC64RuntimeAreaTableOffset || kCastleC64DatabaseAddress + databaseSize > uiData.size())
+		error("Invalid Castle C64 database size");
+	Common::Array<byte> database(uiData.data() + kCastleC64DatabaseAddress, databaseSize);
 	CastleC64DatabaseReadStream databaseStream(database);
 	load8bitBinary(&databaseStream, 0, 16);
 
@@ -619,7 +530,7 @@ void CastleEngine::loadAssetsC64FullGame() {
 	_sound = createCastleC64Sound(_mixer, uiData);
 	_playerMusic = new CastleC64MusicPlayer(_mixer);
 
-	// TODO: title screen is in BASIC loader (file 009) - not yet extracted
+	// TODO: Extract the title screen from the snapshot.
 }
 
 void CastleEngine::drawC64HudSurface(Graphics::Surface *surface, const Graphics::Surface &frame, const Common::Point &origin) {


Commit: 6efdeb5b6ff0f55d6a0ac9810f2a4f16ae158a55
    https://github.com/scummvm/scummvm/commit/6efdeb5b6ff0f55d6a0ac9810f2a4f16ae158a55
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T22:11:14+02:00

Commit Message:
FREESCAPE: decompress some freescape games using unp64

Changed paths:
    engines/freescape/detection.cpp
    engines/freescape/freescape.h
    engines/freescape/games/dark/c64.cpp
    engines/freescape/games/eclipse/c64.cpp
    engines/freescape/loaders/c64.cpp


diff --git a/engines/freescape/detection.cpp b/engines/freescape/detection.cpp
index 8dae99dcd0f..fe0333fe2d2 100644
--- a/engines/freescape/detection.cpp
+++ b/engines/freescape/detection.cpp
@@ -535,7 +535,8 @@ const ADGameDescription gameDescriptions[] = {
 	{
 		"darkside", // Tape release
 		"",
-		AD_ENTRY1s("DARKSIDE.C64.DATA", "7d5fc9a962a146e303a0c71a2d5c651e", 48129),
+		AD_ENTRY2s("DARKSIDE.C64.DATA", "7d5fc9a962a146e303a0c71a2d5c651e", 48129,
+			"DARKSIDE.C64.DATA2", "b0c66200fcd60cf746f00bd7f9177d70", 2682),
 		Common::EN_ANY,
 		Common::kPlatformC64,
 		GF_C64_TAPE,
@@ -668,7 +669,8 @@ const ADGameDescription gameDescriptions[] = {
 	{
 		"totaleclipse2", // Tape release
 		"",
-		AD_ENTRY1s("TOTALECLIPSE2.C64.DATA", "7ab839a4260c197f24b41ef6ab45ef21", 47105),
+		AD_ENTRY2s("TOTALECLIPSE2.C64.DATA", "7ab839a4260c197f24b41ef6ab45ef21", 47105,
+			"TOTALECLIPSE2.C64.DATA2", "0c1ddf1de6b8995d52bfb21abed062f4", 2818),
 		Common::EN_ANY,
 		Common::kPlatformC64,
 		ADGF_UNSTABLE | GF_C64_TAPE,
@@ -728,7 +730,8 @@ const ADGameDescription gameDescriptions[] = {
 	{
 		"totaleclipse", // Tape relese
 		"",
-		AD_ENTRY1s("TOTALECLIPSE.C64.DATA", "968fd46b941a00f887741dfc348ac149", 47105),
+		AD_ENTRY2s("TOTALECLIPSE.C64.DATA", "968fd46b941a00f887741dfc348ac149", 47105,
+			"TOTALECLIPSE.C64.DATA2", "c45f16800b83c8dc4e0bcdbc3a4a75ac", 4097),
 		Common::EN_ANY,
 		Common::kPlatformC64,
 		ADGF_TESTING | GF_C64_TAPE,
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index b0310b1948e..6994fdf6f78 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -682,7 +682,7 @@ public:
 	Common::RandomSource *_rnd;
 
 	// C64 specifics
-	byte *decompressC64RLE(byte *buffer, int *size, byte marker);
+	Common::Array<byte> unpackC64Snapshot(Common::SeekableReadStream *file, const Common::Path &continuation);
 	byte *_extraBuffer;
 };
 
diff --git a/engines/freescape/games/dark/c64.cpp b/engines/freescape/games/dark/c64.cpp
index b872c054e94..3b2c3560b15 100644
--- a/engines/freescape/games/dark/c64.cpp
+++ b/engines/freescape/games/dark/c64.cpp
@@ -215,22 +215,16 @@ void DarkEngine::loadAssetsC64FullGame() {
 	file.open("darkside.c64.data");
 
 	if (_variant & GF_C64_TAPE) {
-		int size = file.size();
-
-		byte *buffer = (byte *)malloc(size * sizeof(byte));
-		file.read(buffer, file.size());
-
-		_extraBuffer = decompressC64RLE(buffer, &size, 0xdf);
-		// size should be the size of the decompressed data
-		Common::MemoryReadStream dfile(_extraBuffer, size, DisposeAfterUse::NO);
-
-		loadMessagesFixedSize(&dfile, 0x1edf, 16, 27);
-		loadFonts(&dfile, 0xc3e);
-		loadGlobalObjects(&dfile, 0x20bd, 23);
-		load8bitBinary(&dfile, 0x9b3e, 16);
-		loadDarkC64CompassTable(&dfile, 0x7e37, _c64CompassTable);
-		loadDarkC64Indicators(&dfile, 0xadba, _indicators, _gfx->_texturePixelFormat);
-		loadDarkC64ModeFrames(&dfile, 0xd2f6, _c64ModeFrames, _gfx->_texturePixelFormat);
+		Common::Array<byte> data = unpackC64Snapshot(&file, "darkside.c64.data2");
+		Common::MemoryReadStream dfile(data.data(), data.size(), DisposeAfterUse::NO);
+
+		loadMessagesFixedSize(&dfile, 0x1aa1, 16, 27);
+		loadFonts(&dfile, 0x0800);
+		loadGlobalObjects(&dfile, 0x1c7f, 23);
+		load8bitBinary(&dfile, 0x9700, 16);
+		loadDarkC64CompassTable(&dfile, 0x79f9, _c64CompassTable);
+		loadDarkC64Indicators(&dfile, 0xa97c, _indicators, _gfx->_texturePixelFormat);
+		loadDarkC64ModeFrames(&dfile, 0xceb8, _c64ModeFrames, _gfx->_texturePixelFormat);
 	} else if (_variant & GF_C64_DISC) {
 		loadMessagesFixedSize(&file, 0x16a3, 16, 27);
 		loadFonts(&file, 0x402);
diff --git a/engines/freescape/games/eclipse/c64.cpp b/engines/freescape/games/eclipse/c64.cpp
index 30ef6458d62..19c609432b5 100644
--- a/engines/freescape/games/eclipse/c64.cpp
+++ b/engines/freescape/games/eclipse/c64.cpp
@@ -58,20 +58,15 @@ extern byte kC64Palette[16][3];
 void EclipseEngine::loadAssetsC64FullGame() {
 	Common::File file;
 	file.open(isEclipse2() ? "totaleclipse2.c64.data" : "totaleclipse.c64.data");
+	Common::Array<byte> data;
 
 	if (_variant & GF_C64_TAPE) {
-		int size = file.size();
+		data = unpackC64Snapshot(&file, isEclipse2() ? "totaleclipse2.c64.data2" : "totaleclipse.c64.data2");
+		Common::MemoryReadStream dfile(data.data(), data.size(), DisposeAfterUse::NO);
 
-		byte *buffer = (byte *)malloc(size * sizeof(byte));
-		file.read(buffer, file.size());
-
-		_extraBuffer = decompressC64RLE(buffer, &size, isEclipse2() ? 0xd2 : 0xe1);
-		// size should be the size of the decompressed data
-		Common::MemoryReadStream dfile(_extraBuffer, size, DisposeAfterUse::NO);
-
-		loadMessagesFixedSize(&dfile, 0x1d84, 16, isEclipse2() ? 34 : 30);
-		loadFonts(&dfile, 0xc3e);
-		load8bitBinary(&dfile, 0x9a3e, 16);
+		loadMessagesFixedSize(&dfile, 0x1946, 16, isEclipse2() ? 34 : 30);
+		loadFonts(&dfile, 0x0800);
+		load8bitBinary(&dfile, 0x9600, 16);
 	} else if (_variant & GF_C64_DISC) {
 		loadMessagesFixedSize(&file, isEclipse2() ? 0x1538 : 0x1534, 16, isEclipse2() ? 34 : 30);
 		loadFonts(&file, 0x3f2);
@@ -120,14 +115,10 @@ void EclipseEngine::loadAssetsC64FullGame() {
 					_playerMusic = new EclipseC64MusicPlayer(_c64MusicData);
 				}
 			}
-		} else if ((_variant & GF_C64_TAPE) && _extraBuffer) {
-			// Tape decompressed data has music at a 0x0C3F offset from disc addresses.
-			// The music player expects data indexed from load address 0x0410.
-			// Remap: musicData[i] = decompressed[i + 0x084E]
-			static const int kTapeMusicShift = 0x084E;
-			static const int kMusicRegionSize = 0x1100; // covers 0x0410..0x14FF
+		} else if (_variant & GF_C64_TAPE) {
+			static const int kMusicRegionSize = 0x1100; // covers 0x0410..0x150f
 			_c64MusicData.resize(kMusicRegionSize);
-			memcpy(_c64MusicData.data(), _extraBuffer + kTapeMusicShift, kMusicRegionSize);
+			memcpy(_c64MusicData.data(), data.data() + 0x0410, kMusicRegionSize);
 			delete _playerMusic;
 			_playerMusic = new EclipseC64MusicPlayer(_c64MusicData);
 		}
diff --git a/engines/freescape/loaders/c64.cpp b/engines/freescape/loaders/c64.cpp
index 2f7c3499f3d..c63fb710ea7 100644
--- a/engines/freescape/loaders/c64.cpp
+++ b/engines/freescape/loaders/c64.cpp
@@ -19,32 +19,49 @@
  *
  */
 
+#include "common/compression/unp64.h"
+#include "common/endian.h"
+
 #include "freescape/freescape.h"
 
 namespace Freescape {
 
-byte *FreescapeEngine::decompressC64RLE(byte *buffer, int *size, byte marker) {
-	Common::MemoryReadWriteStream *tmp = new Common::MemoryReadWriteStream(DisposeAfterUse::NO);
-	// Format is: [ Byte, Marker, Length ] or [ Byte ]
-	for (int i = 0; i < *size - 1; ) {
-		if (buffer[i] == marker && i > 0) {
-			int length = buffer[i + 1];
-			byte value = buffer[i - 1];
-			if (length == 0)
-				tmp->writeByte(value);
-
-			for (int j = 0; j < length; j++) {
-				tmp->writeByte(value);
-			}
-			i += 2;
-		} else {
-			tmp->writeByte(buffer[i]);
-			i += 1;
-		}
-	}
-	*size = tmp->size();
-	byte *data = tmp->getData();
-	delete tmp;
+Common::Array<byte> FreescapeEngine::unpackC64Snapshot(Common::SeekableReadStream *file, const Common::Path &continuation) {
+	if (file->size() <= 2 || file->size() > 0x10002)
+		error("Invalid C64 program size");
+
+	Common::Array<byte> packed;
+	packed.resize(file->size());
+	file->seek(0);
+	if (file->read(packed.data(), packed.size()) != packed.size())
+		error("Unable to read C64 program");
+
+	uint32 endAddress = READ_LE_UINT16(packed.data()) + packed.size() - 2;
+	if (endAddress > 0x10000)
+		error("Invalid C64 program load address");
+
+	Common::File part;
+	if (!part.open(continuation))
+		error("Unable to open C64 continuation %s", continuation.toString().c_str());
+	if (part.size() <= 2 || part.size() - 2 > 0x10000 - endAddress || part.readUint16LE() != endAddress)
+		error("Invalid C64 continuation %s", continuation.toString().c_str());
+
+	uint32 offset = packed.size();
+	uint32 partSize = part.size() - 2;
+	packed.resize(offset + partSize);
+	if (part.read(packed.data() + offset, partSize) != partSize)
+		error("Truncated C64 continuation %s", continuation.toString().c_str());
+
+	Common::Array<byte> data;
+	data.resize(0x10000);
+	uint32 size = 0;
+	if (!Common::Unp64::unp64(packed.data(), packed.size(), data.data(), &size, nullptr))
+		error("Unable to unpack C64 snapshot");
+	if (size != data.size() || READ_LE_UINT16(data.data()) != 2)
+		error("Incomplete C64 snapshot");
+
+	// The PRG covers $0002..$ffff, so its payload is already at the RAM offsets.
+	data[0] = data[1] = 0;
 	return data;
 }
 


Commit: 57c0ce99a60ff387b19f4b8c57ad1a2aa51fd685
    https://github.com/scummvm/scummvm/commit/57c0ce99a60ff387b19f4b8c57ad1a2aa51fd685
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T22:11:14+02:00

Commit Message:
FREESCAPE: decompress driller c64 releases using unp64

Changed paths:
    engines/freescape/detection.cpp
    engines/freescape/freescape.h
    engines/freescape/games/driller/c64.cpp
    engines/freescape/loaders/c64.cpp


diff --git a/engines/freescape/detection.cpp b/engines/freescape/detection.cpp
index fe0333fe2d2..0b2765dfeb7 100644
--- a/engines/freescape/detection.cpp
+++ b/engines/freescape/detection.cpp
@@ -110,12 +110,12 @@ const ADGameDescription gameDescriptions[] = {
 		GUIO3(GUIO_NOMIDI, GAMEOPTION_AUTOMATIC_DRILLING, GUIO_RENDERC64)
 	},
 	{
-		"driller", // Tape re-relase
+		"driller", // Tape rerelease
 		"",
-		AD_ENTRY1s("DRILLER.C64.DATA", "4afec6eea3887343e7f91fb21a2f2948", 43278),
+		AD_ENTRY1s("DRILLER.C64.DATA", "4d7ed1343f9cd522447602bf402c79a6", 56329),
 		Common::EN_ANY,
 		Common::kPlatformC64,
-		ADGF_UNSUPPORTED, //| GF_C64_TAPE,
+		ADGF_UNSTABLE | GF_C64_TAPE | GF_C64_PACKED,
 		GUIO3(GUIO_NOMIDI, GAMEOPTION_AUTOMATIC_DRILLING, GUIO_RENDERC64)
 	},
 	{
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 6994fdf6f78..dc24f83e743 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -682,6 +682,7 @@ public:
 	Common::RandomSource *_rnd;
 
 	// C64 specifics
+	Common::Array<byte> unpackC64Snapshot(const Common::Array<byte> &packed);
 	Common::Array<byte> unpackC64Snapshot(Common::SeekableReadStream *file, const Common::Path &continuation);
 	byte *_extraBuffer;
 };
@@ -700,6 +701,7 @@ enum GameReleaseFlags {
 	GF_ATARI_BUDGET = (1 << 10),
 	GF_C64_TAPE = (1 << 11),
 	GF_C64_DISC = (1 << 12),
+	GF_C64_PACKED = (1 << 13),
 };
 
 extern FreescapeEngine *g_freescape;
diff --git a/engines/freescape/games/driller/c64.cpp b/engines/freescape/games/driller/c64.cpp
index 7d82ada1112..1181352290c 100644
--- a/engines/freescape/games/driller/c64.cpp
+++ b/engines/freescape/games/driller/c64.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "common/endian.h"
 #include "common/file.h"
 
 #include "freescape/freescape.h"
@@ -29,6 +30,36 @@ namespace Freescape {
 
 extern byte kC64Palette[16][3];
 
+static Common::Array<byte> loadDrillerC64PackedProgram(Common::SeekableReadStream *file) {
+	static const struct {
+		uint16 loadAddress;
+		uint16 address;
+		uint16 size;
+	} parts[] = {
+		{0x0400, 0x0400, 2112},
+		{0x0c40, 0x0c40, 43276},
+		{0xb54c, 0xb54c, 4788},
+		{0xc800, 0xc800, 2047},
+		// The tape loader moves this block to $d000 before loading the title.
+		{0x4000, 0xd000, 4096}
+	};
+
+	if (file->size() != 56329)
+		error("Invalid packed Driller C64 program size");
+
+	Common::Array<byte> packed;
+	packed.resize(0xe000 - 0x0400 + 2);
+	WRITE_LE_UINT16(packed.data(), 0x0400);
+	// Concatenated PRGs retain their two-byte load addresses.
+	for (const auto &part : parts) {
+		if (file->readUint16LE() != part.loadAddress)
+			error("Invalid Driller C64 tape block at $%04x", part.loadAddress);
+		if (file->read(packed.data() + part.address - 0x0400 + 2, part.size) != part.size)
+			error("Truncated Driller C64 tape block at $%04x", part.loadAddress);
+	}
+	return packed;
+}
+
 void DrillerEngine::initC64() {
 	_viewArea = Common::Rect(32, 16, 288, 120);
 }
@@ -42,18 +73,22 @@ void DrillerEngine::loadAssetsC64FullGame() {
 		load8bitBinary(&file, 0x8e02, 4);
 		loadGlobalObjects(&file, 0x1855, 8);
 	} else if (_targetName.hasPrefix("driller")) {
-		file.open("driller.c64.data");
-
-		if (_variant) {
+		if (!file.open("driller.c64.data"))
+			error("Unable to open driller.c64.data");
+
+		if (_variant & GF_C64_PACKED) {
+			Common::Array<byte> packed = loadDrillerC64PackedProgram(&file);
+			Common::Array<byte> data = unpackC64Snapshot(packed);
+			Common::MemoryReadStream stream(data.data(), data.size(), DisposeAfterUse::NO);
+			loadMessagesFixedSize(&stream, 0x1a78, 14, 20);
+			loadGlobalObjects(&stream, 0x1c53, 8);
+			loadFonts(&stream, 0x0800);
+			load8bitBinary(&stream, 0x9200, 16);
+		} else if (_variant & (GF_C64_TAPE | GF_C64_DISC)) {
 			loadMessagesFixedSize(&file, 0x167a, 14, 20);
 			loadGlobalObjects(&file, 0x1855, 8);
 			loadFonts(&file, 0x402);
 			load8bitBinary(&file, 0x8b04, 16);
-		/*} else if (_variant & GF_C64_BUDGET) {
-			//loadFonts(&file, 0x402);
-			load8bitBinary(&file, 0x7df7, 16);
-			loadMessagesFixedSize(&file, 0x1399, 14, 20);
-			loadGlobalObjects(&file, 0x150a, 8);*/
 		} else
 			error("Unknown C64 variant %x", _variant);
 
diff --git a/engines/freescape/loaders/c64.cpp b/engines/freescape/loaders/c64.cpp
index c63fb710ea7..682a5e810b5 100644
--- a/engines/freescape/loaders/c64.cpp
+++ b/engines/freescape/loaders/c64.cpp
@@ -52,6 +52,10 @@ Common::Array<byte> FreescapeEngine::unpackC64Snapshot(Common::SeekableReadStrea
 	if (part.read(packed.data() + offset, partSize) != partSize)
 		error("Truncated C64 continuation %s", continuation.toString().c_str());
 
+	return unpackC64Snapshot(packed);
+}
+
+Common::Array<byte> FreescapeEngine::unpackC64Snapshot(const Common::Array<byte> &packed) {
 	Common::Array<byte> data;
 	data.resize(0x10000);
 	uint32 size = 0;


Commit: 56000821acf65621319f30f4ca42f5295661fc62
    https://github.com/scummvm/scummvm/commit/56000821acf65621319f30f4ca42f5295661fc62
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-08T22:11:14+02:00

Commit Message:
COLONY: various forklift behavior fixes

Changed paths:
    engines/colony/animation.cpp
    engines/colony/colony.h
    engines/colony/console.cpp
    engines/colony/interaction.cpp
    engines/colony/movement.cpp
    engines/colony/sound.cpp


diff --git a/engines/colony/animation.cpp b/engines/colony/animation.cpp
index 217fb1ae837..e5cf29d88b7 100644
--- a/engines/colony/animation.cpp
+++ b/engines/colony/animation.cpp
@@ -372,6 +372,28 @@ bool ColonyEngine::loadAnimation(const Common::String &name) {
 	return true;
 }
 
+bool ColonyEngine::loadLiftAnimation(int objectType) {
+	switch (objectType) {
+	case kObjTeleport:
+		_liftObject = 1;
+		break;
+	case kObjBox1:
+	case kObjBox2:
+		_liftObject = 2;
+		break;
+	case kObjCryo:
+		_liftObject = 3;
+		break;
+	case kObjReactor:
+		_liftObject = 4;
+		break;
+	default:
+		return false;
+	}
+
+	return loadAnimation("lift");
+}
+
 void ColonyEngine::deleteAnimation() {
 	delete _backgroundMask;
 	_backgroundMask = nullptr;
@@ -551,28 +573,10 @@ void ColonyEngine::playAnimation() {
 			setObjectOnOff(4, false);
 		}
 	} else if (_animationName == "lift") {
-		// Original DoLift: set up initial state based on forklift mode.
-		// _fl==1 → picking up (up=0, object starts at bottom)
-		// _fl==2 → putting down (up=1, object starts at top)
-		// Object sprite mapping: BOX1/BOX2→2, TELEPORT→1, CRYO→3, REACTOR→4
+		if (getPlatform() == Common::kPlatformMacintosh)
+			_sound->stop();
+
 		_liftUp = (_fl == 2);
-		switch (_fl == 2 ? _carryType : 0) {
-		case kObjBox1: case kObjBox2: _liftObject = 2; break;
-		case kObjTeleport: _liftObject = 1; break;
-		case kObjCryo: _liftObject = 3; break;
-		case kObjReactor: _liftObject = 4; break;
-		default: _liftObject = 2; break; // pickup: we don't know yet, but dolSprite handles it
-		}
-		// For pickup, determine object from what we're about to pick up
-		if (!_liftUp) {
-			// The interaction code sets _carryType AFTER the animation,
-			// but the object type is in the Thing we're interacting with.
-			// We can infer from which sprites are visible.
-			for (int i = 1; i <= 4; i++) {
-				if (i < (int)_lSprites.size() && _lSprites[i - 1] && _lSprites[i - 1]->onoff)
-					_liftObject = i;
-			}
-		}
 		// Hide all object sprites except the active one
 		for (int i = 1; i <= 4; i++) {
 			if (i != _liftObject)
@@ -725,6 +729,13 @@ void ColonyEngine::playAnimation() {
 		_system->delayMillis(2);
 	}
 
+	if (_animationName == "lift" && getPlatform() == Common::kPlatformMacintosh) {
+		// Mac KillTSound waits for the lift sample before returning.
+		while (_sound->isPlaying() && !shouldQuit())
+			responsiveAnimationDelay(_system, 10);
+		_sound->stop();
+	}
+
 	if (useSquarePixelViewport)
 		_gfx->setSquarePixelViewport(false);
 	_system->lockMouse(true);
@@ -1478,6 +1489,7 @@ void ColonyEngine::handleAnimationClick(int item) {
 		// item 8 = lower button (active when _liftUp)
 		// item 9 = raise button (active when !_liftUp)
 		if (item == 8 && _liftUp) {
+			_sound->play(Sound::kDrop);
 			// Lower the object: animate states 5→1
 			setObjectState(8, 2); // lower arrow OFF
 			setObjectState(9, 1); // raise arrow ON
@@ -1490,6 +1502,7 @@ void ColonyEngine::handleAnimationClick(int item) {
 			_liftUp = false;
 			_animationResult = 1;
 		} else if (item == 9 && !_liftUp) {
+			_sound->play(Sound::kLift);
 			// Raise the object: animate states 1→5
 			setObjectState(9, 2); // raise arrow OFF
 			setObjectState(8, 1); // lower arrow ON
diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index c063e59b80a..58bbb585aee 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -795,7 +795,7 @@ private:
 	bool patchMapFrom(const PassPatch &from, uint8 *mapdata);
 	void exitForklift();
 	void dropCarriedObject();
-	bool stepOutOfCell();
+	bool stepOutOfCell(uint8 angle, bool backwards = false);
 	bool exitTeleport();
 	void teleportPlayer();
 	bool setDoorState(int x, int y, int direction, int state);
@@ -914,6 +914,7 @@ private:
 	bool timeSquare(const Common::String &str, const Graphics::Font *macFont = nullptr, bool gameOver = false);
 	bool drawPict(int resID);
 	bool loadAnimation(const Common::String &name);
+	bool loadLiftAnimation(int objectType);
 	void deleteAnimation();
 	void takeOff();
 	void fullOfStars();
diff --git a/engines/colony/console.cpp b/engines/colony/console.cpp
index a109fe08579..a39cfc43ab1 100644
--- a/engines/colony/console.cpp
+++ b/engines/colony/console.cpp
@@ -337,20 +337,19 @@ bool Debugger::cmdTeleport(int argc, const char **argv) {
 		}
 	}
 
-	// Clear player from current robot array position
-	if (_vm->_me.xindex >= 0 && _vm->_me.xindex < 32 &&
-		_vm->_me.yindex >= 0 && _vm->_me.yindex < 32)
-		_vm->_robotArray[_vm->_me.xindex][_vm->_me.yindex] = 0;
+	_vm->clearPlayerCellMarker();
 
 	// Load the target level
-	if (level != _vm->_level)
+	if (level != _vm->_level) {
 		_vm->loadMap(level);
+		_vm->clearPlayerCellMarker();
+	}
 
 	// If no coordinates given, scan for an entry point (stairs/tunnel/elevator)
 	if (targetX < 0) {
 		for (int x = 0; x < 31 && targetX < 0; x++) {
 			for (int y = 0; y < 31 && targetX < 0; y++) {
-				for (int d = 0; d < 5; d++) {
+				for (int d = 0; d < 4; d++) {
 					int feat = _vm->_mapData[x][y][d][0];
 					if (feat == kWallFeatureUpStairs || feat == kWallFeatureDnStairs ||
 						feat == kWallFeatureTunnel || feat == kWallFeatureElevator) {
@@ -377,8 +376,8 @@ bool Debugger::cmdTeleport(int argc, const char **argv) {
 	_vm->_me.xloc = (targetX << 8) + 128;
 	_vm->_me.yloc = (targetY << 8) + 128;
 
-	// Register player in robot array
-	_vm->_robotArray[targetX][targetY] = kMeNum;
+	_vm->setPlayerCellMarker();
+	_vm->_bumpedObject = 0;
 
 	debugPrintf("Teleported to level %d at (%d, %d)\n", _vm->_level, targetX, targetY);
 	return false;
@@ -697,6 +696,7 @@ bool Debugger::cmdForklift(int argc, const char **argv) {
 			return true;
 		}
 		_vm->_fl = state;
+		_vm->_bumpedObject = 0;
 		if (state > 0)
 			_vm->_me.lookY = 0; // reset vertical look
 		if (state == 0) {
diff --git a/engines/colony/interaction.cpp b/engines/colony/interaction.cpp
index 15d7aac0a82..839abb076bc 100644
--- a/engines/colony/interaction.cpp
+++ b/engines/colony/interaction.cpp
@@ -116,8 +116,7 @@ void ColonyEngine::interactWithObject(int objNum) {
 	{
 		if (_fl == 1) {
 			// In empty forklift  pick up the teleporter itself
-			if (loadAnimation("lift")) {
-				_sound->play(Sound::kLift); // GANIMATE.C DoLift: DoLiftSound()
+			if (loadLiftAnimation(obj.type)) {
 				_animationResult = 0;
 				playAnimation();
 				if (_animationResult) {
@@ -213,8 +212,7 @@ void ColonyEngine::interactWithObject(int objNum) {
 	case kObjCryo:
 		if (_fl == 1) {
 			// In empty forklift  pick up object
-			if (loadAnimation("lift")) {
-				_sound->play(Sound::kLift); // GANIMATE.C DoLift: DoLiftSound()
+			if (loadLiftAnimation(obj.type)) {
 				_animationResult = 0;
 				playAnimation();
 				if (_animationResult) {
@@ -246,8 +244,7 @@ void ColonyEngine::interactWithObject(int objNum) {
 	case kObjReactor:
 		if (_fl == 1 && _coreState[_coreIndex] == 1) {
 			// Empty forklift at open reactor  pick up reactor core
-			if (loadAnimation("lift")) {
-				_sound->play(Sound::kLift); // GANIMATE.C DoLift: DoLiftSound()
+			if (loadLiftAnimation(obj.type)) {
 				_animationResult = 0;
 				playAnimation();
 				if (_animationResult) {
@@ -260,8 +257,7 @@ void ColonyEngine::interactWithObject(int objNum) {
 			}
 		} else if (_fl == 2 && _carryType == kObjReactor && _coreState[_coreIndex] == 2) {
 			// Carrying reactor core  drop it into reactor
-			if (loadAnimation("lift")) {
-				_sound->play(Sound::kDrop); // GANIMATE.C DoLift: DoDropSound()
+			if (loadLiftAnimation(_carryType)) {
 				_animationResult = 0;
 				playAnimation();
 				if (_animationResult) {
diff --git a/engines/colony/movement.cpp b/engines/colony/movement.cpp
index 3df6164f05f..dae6fafb879 100644
--- a/engines/colony/movement.cpp
+++ b/engines/colony/movement.cpp
@@ -1369,22 +1369,28 @@ void ColonyEngine::cCommand(int xnew, int ynew, bool allowInteraction) {
 	_suppressCollisionSound = false;
 }
 
-// DOS Forward(): inch ahead along _me.ang until the player leaves the cell.
-bool ColonyEngine::stepOutOfCell() {
+// DOS Forward(), ExitFL() and DropFL(): leave the current cell.
+bool ColonyEngine::stepOutOfCell(uint8 angle, bool backwards) {
 	const int xindex = _me.xindex;
 	const int yindex = _me.yindex;
+	const int direction = backwards ? -1 : 1;
+	clearPlayerCellMarker();
 
 	// clampToWalls() can pin the player short of the boundary, so cap the walk.
 	int guard = 16;
 	_me.type = 2; // temporary small collision type
 	while (_me.xindex == xindex && _me.yindex == yindex) {
-		if (--guard < 0 || checkwall(_me.xloc + _cost[_me.ang], _me.yloc + _sint[_me.ang], &_me)) {
+		const int xnew = _me.xloc + direction * _cost[angle];
+		const int ynew = _me.yloc + direction * _sint[angle];
+		if (--guard < 0 || checkwall(xnew, ynew, &_me)) {
 			_sound->play(Sound::kChime);
 			_me.type = kMeNum;
+			setPlayerCellMarker();
 			return false;
 		}
 	}
 	_me.type = kMeNum;
+	setPlayerCellMarker();
 	return true;
 }
 
@@ -1396,14 +1402,10 @@ bool ColonyEngine::exitTeleport() {
 	const int xindex = _me.xindex;
 	const int yindex = _me.yindex;
 
-	// goToDestination() stamped this cell as the player's; occupiedObjectAt()
-	// would read that marker back as a blocker.
-	clearPlayerCellMarker();
-
 	_me.ang = 48;
 	bool out = false;
 	for (int tries = 0; tries < 4 && !out; tries++) {
-		out = stepOutOfCell();
+		out = stepOutOfCell(_me.ang);
 		if (!out) {
 			_me.xloc = xloc;
 			_me.yloc = yloc;
@@ -1446,18 +1448,8 @@ void ColonyEngine::exitForklift() {
 	int xindex = _me.xindex;
 	int yindex = _me.yindex;
 
-	// Walk backward until we move into a different cell
-	while (_me.xindex == xindex && _me.yindex == yindex) {
-		int xnew = _me.xloc - _cost[_me.ang];
-		int ynew = _me.yloc - _sint[_me.ang];
-		_me.type = 2; // temporary small collision type
-		if (checkwall(xnew, ynew, &_me)) {
-			_sound->play(Sound::kChime);
-			_me.type = kMeNum;
-			return;
-		}
-		_me.type = kMeNum;
-	}
+	if (!stepOutOfCell(_me.look, true))
+		return;
 
 	// Snap to cell center for the dropped forklift
 	xloc = (xloc >> 8);
@@ -1495,9 +1487,8 @@ void ColonyEngine::dropCarriedObject() {
 		return;
 	}
 
-	// Play the drop animation — GANIMATE.C DoLift: DoDropSound()
-	if (loadAnimation("lift")) {
-		_sound->play(Sound::kDrop);
+	// Play the drop animation.
+	if (loadLiftAnimation(_carryType)) {
 		_animationResult = 0;
 		playAnimation();
 		if (!_animationResult) {
@@ -1511,18 +1502,8 @@ void ColonyEngine::dropCarriedObject() {
 	int xindex = _me.xindex;
 	int yindex = _me.yindex;
 
-	// Walk backward until we move into a different cell
-	while (_me.xindex == xindex && _me.yindex == yindex) {
-		int xnew = _me.xloc - _cost[_me.ang];
-		int ynew = _me.yloc - _sint[_me.ang];
-		_me.type = 2;
-		if (checkwall(xnew, ynew, &_me)) {
-			_sound->play(Sound::kChime);
-			_me.type = kMeNum;
-			return;
-		}
-		_me.type = kMeNum;
-	}
+	if (!stepOutOfCell(_me.look, true))
+		return;
 
 	// DOS: teleport always drops at ang=0; other objects use player's angle
 	uint8 ang = (_carryType == kObjTeleport) ? 0 : _me.ang;
diff --git a/engines/colony/sound.cpp b/engines/colony/sound.cpp
index 82d232786e0..85f3cc5b8ba 100644
--- a/engines/colony/sound.cpp
+++ b/engines/colony/sound.cpp
@@ -247,22 +247,17 @@ void Sound::playPCSpeaker(int soundID) {
 		break;
 	}
 	case kLift:
-	{
-		uint32 div = 4649;
-		queueTick(div, 1);
-		while (div > 3103) {
-			div -= 8;
-			queueTick(div, 1);
-		}
-		break;
-	}
 	case kDrop:
 	{
-		uint32 div = 3103;
-		queueTick(div, 1);
-		while (div < 4649) {
-			div += 8;
-			queueTick(div, 1);
+		// VSP uses PIT divisor 0x4000; DURATION 1 waits two interrupts.
+		const uint32 stepUs = uint64(0x4000) * 2 * 1000000 / 1193180;
+		const bool lifting = soundID == kLift;
+		const int step = lifting ? -8 : 8;
+		int div = lifting ? 4649 : 3103;
+		_speaker->playQueue(Audio::PCSpeaker::kWaveFormSquare, 1193180.0f / div, stepUs);
+		while (lifting ? div > 3103 : div < 4649) {
+			div += step;
+			_speaker->playQueue(Audio::PCSpeaker::kWaveFormSquare, 1193180.0f / div, stepUs);
 		}
 		break;
 	}
@@ -368,7 +363,8 @@ bool Sound::playMacSound(int soundID, bool loop) {
 	case kSlug: resID = 8347; break;
 	case kTunnel1: resID = 16403; break;
 	case kTunnel2: resID = 17354; break;
-	case kLift: resID = 28521; break;
+	case kLift:
+	case kDrop: resID = 28521; break;
 	case kGlass: resID = 19944; break;
 	case kDoor: resID = 26867; break;
 	case kToilet: resID = 4955; break;




More information about the Scummvm-git-logs mailing list