[Scummvm-git-logs] scummvm master -> 54a6e9c6365c842e631f19b01c173eb14edba3ca
neuromancer
noreply at scummvm.org
Wed Sep 23 18:57:43 UTC 2026
This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
77761a452d FREESCAPE: added support for a compressed variant of eclipse (zx)
54a6e9c636 EEM: workaround to avoid zero-bytes sound pops in eem1 mac (cd)
Commit: 77761a452dbeb0fa8c68621712fc116dea1b7d5a
https://github.com/scummvm/scummvm/commit/77761a452dbeb0fa8c68621712fc116dea1b7d5a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-23T20:55:57+02:00
Commit Message:
FREESCAPE: added support for a compressed variant of eclipse (zx)
Changed paths:
engines/freescape/detection.cpp
engines/freescape/games/eclipse/zx.cpp
engines/freescape/zx_tape.cpp
engines/freescape/zx_tape.h
diff --git a/engines/freescape/detection.cpp b/engines/freescape/detection.cpp
index 04a8553c400..9acf014ca86 100644
--- a/engines/freescape/detection.cpp
+++ b/engines/freescape/detection.cpp
@@ -624,6 +624,15 @@ const ADGameDescription gameDescriptions[] = {
ADGF_NO_FLAGS,
GUIO5(GUIO_NOMIDI, GUIO_RENDERZX, GAMEOPTION_MODERN_MOVEMENT, GAMEOPTION_WASD_CONTROLS, GAMEOPTION_AY_MUSIC)
},
+ {
+ "totaleclipse2",
+ "",
+ AD_ENTRY1s("totaleclipse2.zx.data", "1be6a1747decb1f788e3c277dccb1ac4", 28916),
+ Common::EN_ANY,
+ Common::kPlatformZX,
+ ADGF_NO_FLAGS,
+ GUIO5(GUIO_NOMIDI, GUIO_RENDERZX, GAMEOPTION_MODERN_MOVEMENT, GAMEOPTION_WASD_CONTROLS, GAMEOPTION_AY_MUSIC)
+ },
{
"totaleclipse",
"",
diff --git a/engines/freescape/games/eclipse/zx.cpp b/engines/freescape/games/eclipse/zx.cpp
index 2a06e8d08b0..7e104e42483 100644
--- a/engines/freescape/games/eclipse/zx.cpp
+++ b/engines/freescape/games/eclipse/zx.cpp
@@ -26,6 +26,7 @@
#include "freescape/games/eclipse/ay.music.h"
#include "freescape/games/eclipse/eclipse.h"
#include "freescape/language/variables.h"
+#include "freescape/zx_tape.h"
namespace Freescape {
@@ -91,21 +92,21 @@ void EclipseEngine::loadAssetsZXFullGame() {
Common::Path borderFile(Common::String::format("%s.zx.border", prefix));
Common::Path dataFile(Common::String::format("%s.zx.data", prefix));
- file.open(titleFile);
+ file.open(openZxSpectrumFile(titleFile), titleFile.toString());
if (file.isOpen()) {
_title = loadAndConvertScrImage(&file);
} else
error("Unable to find %s", titleFile.toString().c_str());
file.close();
- file.open(borderFile);
+ file.open(openZxSpectrumFile(borderFile), borderFile.toString());
if (file.isOpen()) {
_border = loadAndConvertScrImage(&file);
} else
error("Unable to find %s", borderFile.toString().c_str());
file.close();
- file.open(dataFile);
+ file.open(openZxSpectrumFile(dataFile), dataFile.toString());
if (!file.isOpen())
error("Failed to open %s", dataFile.toString().c_str());
diff --git a/engines/freescape/zx_tape.cpp b/engines/freescape/zx_tape.cpp
index a72bde27be8..e373e348e7a 100644
--- a/engines/freescape/zx_tape.cpp
+++ b/engines/freescape/zx_tape.cpp
@@ -126,6 +126,124 @@ void ZxRecordingDecoder::finish() {
_sync = false;
}
+bool unpackZxSpectrumBlock(Common::Array<byte> &data) {
+ // Recognize the self-extracting LZ/RLE loader before reading its operands.
+ const byte loader[] = { 0x01, 0x34, 0x00, 0x11, 0xc6, 0x5b, 0xd5, 0xed, 0xb0, 0xb7, 0xc9, 0x11 };
+ const byte lzPrefix[] = { 0x2b, 0x7e, 0x1b, 0x12, 0xd6 };
+ const byte lzSuffix[] = {
+ 0x20, 0xf8, 0x2b, 0xb6, 0x28, 0x14, 0x2b, 0xe5, 0x6e, 0x4f, 0xe6, 0x07, 0x67, 0x23, 0x19, 0xa9,
+ 0x0f, 0x0f, 0x0f, 0xc6, 0x03, 0x4f, 0xed, 0xb8, 0x13, 0xe1, 0xed, 0x52, 0x19, 0x20, 0xdb, 0xc3
+ };
+ if (data.size() < 66 || data[0] != 0x21 || memcmp(data.data() + 3, loader, sizeof(loader)) ||
+ data[17] != 0x01 || READ_BE_UINT32(data.data() + 20) != 0xedb0eb11 ||
+ memcmp(data.data() + 26, lzPrefix, sizeof(lzPrefix)) || memcmp(data.data() + 32, lzSuffix, sizeof(lzSuffix)))
+ return true;
+
+ int base = READ_LE_UINT16(data.data() + 15);
+ uint16 packedSize = READ_LE_UINT16(data.data() + 18);
+ int end = READ_LE_UINT16(data.data() + 24);
+ int entry = READ_LE_UINT16(data.data() + 64);
+ byte marker = data[31];
+ if (READ_LE_UINT16(data.data() + 1) != base + 14 || !packedSize || packedSize + 66U != data.size() ||
+ base + data.size() > 0x10000 || base + packedSize >= end)
+ return false;
+
+ Common::Array<byte> memory;
+ memory.resize(0x10000);
+ memcpy(memory.data() + base, data.data() + 66, packedSize);
+ int src = base + packedSize;
+ int dst = end;
+
+ // The first stage expands backward until its input and output pointers meet.
+ do {
+ if (src <= base || src >= dst)
+ return false;
+ byte value = memory[--src];
+ memory[--dst] = value;
+ if (value != marker)
+ continue;
+ if (src <= base)
+ return false;
+ byte code = memory[--src];
+ if (code) {
+ if (src <= base)
+ return false;
+ int offset = ((code & 7) << 8) | memory[--src];
+ int count = (code >> 3) + 3;
+ int copy = dst + offset + 1;
+ if (copy >= end || dst - count + 1 < src)
+ return false;
+ while (count--)
+ memory[dst--] = memory[copy--];
+ ++dst;
+ }
+ } while (src != dst);
+
+ if (entry < base || entry + 4 > end)
+ return false;
+ bool screen = memory[entry] == 0x21 && READ_LE_UINT16(memory.data() + entry + 1) == base && memory[entry + 3] == 0xe5;
+ if (screen)
+ entry += 4;
+
+ const byte rleLoader[] = { 0x01, 0x12, 0x00, 0xd5, 0xed, 0xb0, 0x21 };
+ const byte rleCode[] = { 0x7e, 0x07, 0xcb, 0x3f, 0x2b, 0xed, 0xa8, 0xe0, 0x3d, 0xcb, 0xbf, 0x28, 0xf3, 0x30, 0xf6, 0x23, 0x18, 0xf3 };
+ if (entry + 40 > end || memory[entry] != 0x21 || READ_LE_UINT16(memory.data() + entry + 1) != entry + 22 ||
+ memory[entry + 3] != 0x11 || memcmp(memory.data() + entry + 6, rleLoader, sizeof(rleLoader)) ||
+ memory[entry + 15] != 0x11 || memory[entry + 18] != 0x01 || memory[entry + 21] != 0xc9 ||
+ memcmp(memory.data() + entry + 22, rleCode, sizeof(rleCode)))
+ return false;
+
+ src = READ_LE_UINT16(memory.data() + entry + 13);
+ dst = READ_LE_UINT16(memory.data() + entry + 16);
+ int remaining = READ_LE_UINT16(memory.data() + entry + 19);
+ int start = dst + 1 - remaining;
+ if (!remaining || start < base || src < start || src >= entry || src > dst)
+ return false;
+ end = dst + 1;
+
+ // The second stage uses the high bit for repeated runs; a zero count means 128.
+ while (remaining) {
+ if (src < start || src > dst)
+ return false;
+ byte control = memory[src--];
+ int count = MIN<int>((control & 0x7f) ? (control & 0x7f) : 128, remaining);
+ remaining -= count;
+ while (count--) {
+ if (src < start || src > dst)
+ return false;
+ memory[dst--] = memory[src];
+ if (!(control & 0x80))
+ --src;
+ }
+ if (control & 0x80)
+ --src;
+ }
+
+ if (screen) {
+ // Discard the screen-copy routine preceding the SCR image.
+ if (start != base + 12 || end - start != 6912 || memory[base] != 0x21 ||
+ READ_LE_UINT16(memory.data() + base + 1) != start || READ_BE_UINT32(memory.data() + base + 3) != 0x11004001 ||
+ READ_LE_UINT16(memory.data() + base + 7) != 6912 || memory[base + 9] != 0xed ||
+ memory[base + 10] != 0xb0 || memory[base + 11] != 0xc9)
+ return false;
+ base = start;
+ }
+ data.assign(memory.begin() + base, memory.begin() + end);
+ return true;
+}
+
+Common::SeekableReadStream *openZxSpectrumFile(const Common::Path &name) {
+ Common::File file;
+ if (!file.open(name) || file.size() <= 0 || file.size() > 0x10000)
+ return nullptr;
+ Common::Array<byte> data;
+ data.resize(file.size());
+ if (file.read(data.data(), data.size()) != data.size() || !unpackZxSpectrumBlock(data))
+ return nullptr;
+ Common::MemoryReadStream stream(data.data(), data.size());
+ return stream.readStream(stream.size());
+}
+
bool extractZxSpectrumTapeFiles(Common::SeekableReadStream &stream, const char *prefix, ZxTapeFileList &files) {
files.clear();
@@ -156,6 +274,8 @@ bool extractZxSpectrumTapeFiles(Common::SeekableReadStream &stream, const char *
if (block.tap.size() >= 2) {
Common::Array<byte> body;
body.assign(block.tap.begin() + 1, block.tap.end() - 1);
+ if (!unpackZxSpectrumBlock(body))
+ return false;
if (body.size() >= 160 && READ_BE_UINT32(body.data()) == MKTAG('K', 'I', 'T', 'S') &&
READ_LE_UINT16(body.data() + 4) == body.size()) {
kitData = body;
diff --git a/engines/freescape/zx_tape.h b/engines/freescape/zx_tape.h
index 166775ee7d2..94aaa478bc3 100644
--- a/engines/freescape/zx_tape.h
+++ b/engines/freescape/zx_tape.h
@@ -42,6 +42,7 @@ struct ZxTapeFile {
typedef Common::Array<ZxTapeFile> ZxTapeFileList;
+Common::SeekableReadStream *openZxSpectrumFile(const Common::Path &name);
bool extractZxSpectrumTapeFiles(Common::SeekableReadStream &stream, const char *prefix, ZxTapeFileList &files);
bool matchZxSpectrumTapeFiles(const ZxTapeFileList &files, const ADGameDescription &desc, uint md5Bytes = 5000);
Common::Archive *makeZxSpectrumTapeArchive(const ADGameDescription &desc, const Common::Path &gamePath);
Commit: 54a6e9c6365c842e631f19b01c173eb14edba3ca
https://github.com/scummvm/scummvm/commit/54a6e9c6365c842e631f19b01c173eb14edba3ca
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-23T20:55:57+02:00
Commit Message:
EEM: workaround to avoid zero-bytes sound pops in eem1 mac (cd)
Changed paths:
engines/eem/audio.cpp
diff --git a/engines/eem/audio.cpp b/engines/eem/audio.cpp
index 2d4b760444a..4655c2b4806 100644
--- a/engines/eem/audio.cpp
+++ b/engines/eem/audio.cpp
@@ -43,6 +43,25 @@ AudioPlayer::AudioPlayer(EEMEngine *vm) :
_isMacintosh(vm && vm->isMacintosh()) {
}
+static Audio::SeekableAudioStream *makeMacSoundStream(Common::SeekableReadStream *stream) {
+ Audio::SeekableAudioStream *audio = Audio::makeMacSndStream(stream, DisposeAfterUse::YES);
+ if (!audio || audio->isStereo() || audio->getLength().totalNumberOfFrames() == 0)
+ return audio;
+
+ // The original Mac speech/SFX resources include a trailing 0x00 sample
+ // in their PCM length. In unsigned 8-bit audio this is full negative
+ // amplitude, not silence, and causes a pop at the end (bug #17223).
+ // Trim it here so both snd and decoded csnd are handled, without
+ // changing the shared decoder or the music instrument resources.
+ const Audio::Timestamp end = audio->getLength().addFrames(-1);
+ int16 lastSample;
+ if (audio->seek(end) && audio->readBuffer(&lastSample, 1) == 1 && lastSample == -32768)
+ return new Audio::SubSeekableAudioStream(audio, Audio::Timestamp(0, audio->getRate()), end);
+
+ audio->rewind();
+ return audio;
+}
+
struct MacSndResource {
const char *name;
uint16 id;
@@ -275,7 +294,7 @@ void AudioPlayer::playMacSnd(uint16 resourceId, Audio::SoundHandle &handle,
}
Audio::SeekableAudioStream *audioStream =
- Audio::makeMacSndStream(stream, DisposeAfterUse::YES);
+ makeMacSoundStream(stream);
if (!audioStream) {
delete stream;
warning("AudioPlayer: Mac snd resource %u is not playable", resourceId);
@@ -475,7 +494,7 @@ bool AudioPlayer::playMacMysterySound(uint num) {
}
Audio::SeekableAudioStream *audioStream = stream
- ? Audio::makeMacSndStream(stream, DisposeAfterUse::YES) : nullptr;
+ ? makeMacSoundStream(stream) : nullptr;
if (!audioStream) {
delete stream;
warning("AudioPlayer: Mac mystery sound resource %u is not playable",
More information about the Scummvm-git-logs
mailing list