[Scummvm-git-logs] scummvm master -> b22dba7b28bbc47c128048915fa9a7b12db931e5
neuromancer
noreply at scummvm.org
Sat Jun 6 15:34:24 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:
84dfb4fc43 COMMON: Allow adding files to SpectrumTapeArchive
b22dba7b28 FREESCAPE: add ZX Spectrum tape support
Commit: 84dfb4fc433293629240ef9938396b16e7079f64
https://github.com/scummvm/scummvm/commit/84dfb4fc433293629240ef9938396b16e7079f64
Author: Shadow Maker (shm at vtrd.in)
Date: 2026-06-06T17:34:19+02:00
Commit Message:
COMMON: Allow adding files to SpectrumTapeArchive
Changed paths:
common/formats/spectrum_tape.cpp
common/formats/spectrum_tape.h
diff --git a/common/formats/spectrum_tape.cpp b/common/formats/spectrum_tape.cpp
index 175146cbd3b..d18a460c5ba 100644
--- a/common/formats/spectrum_tape.cpp
+++ b/common/formats/spectrum_tape.cpp
@@ -471,15 +471,19 @@ SpectrumTapeArchive::SpectrumTapeArchive(const SpectrumTapeBlocks &blocks) {
if (!getTapBody(block, body, &flag) || flag == 0x00 || body.empty())
continue;
- ArchiveFile entry;
- entry.name = Path(getTapeFileName(pendingHeader, body, fileIndex++), Path::kNoSeparator);
- entry.data = body;
- _files.push_back(entry);
+ addFile(Path(getTapeFileName(pendingHeader, body, fileIndex++), Path::kNoSeparator), body);
pendingHeader.valid = false;
}
}
+void SpectrumTapeArchive::addFile(const Path &path, const Array<byte> &data) {
+ ArchiveFile entry;
+ entry.name = path;
+ entry.data = data;
+ _files.push_back(entry);
+}
+
bool SpectrumTapeArchive::hasFile(const Path &path) const {
for (const ArchiveFile &file : _files) {
if (file.name.equalsIgnoreCase(path))
diff --git a/common/formats/spectrum_tape.h b/common/formats/spectrum_tape.h
index ebb61d4c3ac..f72dccfa24e 100644
--- a/common/formats/spectrum_tape.h
+++ b/common/formats/spectrum_tape.h
@@ -42,8 +42,11 @@ bool parseSpectrumTape(SeekableReadStream &stream, SpectrumTapeBlocks &blocks);
class SpectrumTapeArchive : public Archive {
public:
+ SpectrumTapeArchive() {}
SpectrumTapeArchive(const SpectrumTapeBlocks &blocks);
+ void addFile(const Path &path, const Array<byte> &data);
+
bool hasFile(const Path &path) const override;
int listMembers(ArchiveMemberList &list) const override;
const ArchiveMemberPtr getMember(const Path &path) const override;
Commit: b22dba7b28bbc47c128048915fa9a7b12db931e5
https://github.com/scummvm/scummvm/commit/b22dba7b28bbc47c128048915fa9a7b12db931e5
Author: Shadow Maker (shm at vtrd.in)
Date: 2026-06-06T17:34:19+02:00
Commit Message:
FREESCAPE: add ZX Spectrum tape support
Changed paths:
A engines/freescape/zx_tape.cpp
A engines/freescape/zx_tape.h
engines/freescape/assets.cpp
engines/freescape/detection.cpp
engines/freescape/games/eclipse/zx.cpp
engines/freescape/module.mk
diff --git a/engines/freescape/assets.cpp b/engines/freescape/assets.cpp
index c02332fb6bb..c76219cedee 100644
--- a/engines/freescape/assets.cpp
+++ b/engines/freescape/assets.cpp
@@ -23,10 +23,13 @@
// available at https://github.com/TomHarte/Phantasma/ (MIT)
#include "common/file.h"
+#include "common/archive.h"
+#include "common/config-manager.h"
#include "common/compression/unzip.h"
#include "image/bmp.h"
#include "freescape/freescape.h"
+#include "freescape/zx_tape.h"
namespace Freescape {
@@ -125,6 +128,9 @@ void FreescapeEngine::loadDataBundle() {
if (versionData != expectedVersion)
error("Unexpected version number for freescape.dat: expecting '%s' but found '%s'", expectedVersion.c_str(), versionData);
free(versionData);
+
+ if (Common::Archive *archive = makeZxSpectrumTapeArchive(*_gameDescription, ConfMan.getPath("path")))
+ SearchMan.add("freescape-zx-tape", archive, 10);
}
Graphics::Surface *FreescapeEngine::loadBundledImage(const Common::String &name, bool appendRenderMode) {
diff --git a/engines/freescape/detection.cpp b/engines/freescape/detection.cpp
index afba0909dbf..b6db9b61304 100644
--- a/engines/freescape/detection.cpp
+++ b/engines/freescape/detection.cpp
@@ -24,6 +24,10 @@
#include "freescape/games/driller/driller.h"
#include "freescape/games/eclipse/eclipse.h"
+#include "freescape/zx_tape.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
namespace Freescape {
@@ -613,7 +617,7 @@ const ADGameDescription gameDescriptions[] = {
{
"totaleclipse2",
"",
- AD_ENTRY1s("totaleclipse.zx.data", "5e80cb6a518d5ab2192b845801b1a32e", 35661),
+ AD_ENTRY1s("totaleclipse2.zx.data", "5e80cb6a518d5ab2192b845801b1a32e", 35661),
Common::EN_ANY,
Common::kPlatformZX,
ADGF_NO_FLAGS,
@@ -1263,9 +1267,70 @@ public:
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
+ Common::Error identifyGame(DetectedGame &game, const void **descriptor) override;
+ DetectedGames detectGames(const Common::FSList &fslist, uint32 skipADFlags, bool skipIncomplete) override;
DetectedGame toDetectedGame(const ADDetectedGame &adGame, ADDetectedGameExtraInfo *extraInfo) const override;
+
+private:
+ ADDetectedGames detectZxTapeGames(const Common::FSList &fslist, uint32 skipADFlags) const;
};
+Common::Error FreescapeMetaEngineDetection::identifyGame(DetectedGame &game, const void **descriptor) {
+ Common::Error err = AdvancedMetaEngineDetection<ADGameDescription>::identifyGame(game, descriptor);
+ if (err.getCode() == Common::kNoError)
+ return err;
+
+ Common::Path path = ConfMan.hasKey("path") ? ConfMan.getPath("path") : Common::Path(".");
+ Common::FSNode dir(path);
+ Common::FSList files;
+ if (!dir.isDirectory() || !dir.getChildren(files, Common::FSNode::kListAll) || files.empty())
+ return err;
+
+ Common::String gameId = ConfMan.get("gameid");
+ ADDetectedGames tapeGames = detectZxTapeGames(files, 0);
+ for (const ADDetectedGame &tapeGame : tapeGames) {
+ if (tapeGame.desc->gameId == gameId) {
+ game = toDetectedGame(tapeGame, nullptr);
+ *descriptor = new ADDynamicGameDescription<ADGameDescription>(tapeGame.desc);
+ return Common::kNoError;
+ }
+ }
+
+ return err;
+}
+
+DetectedGames FreescapeMetaEngineDetection::detectGames(const Common::FSList &fslist, uint32 skipADFlags, bool skipIncomplete) {
+ DetectedGames detectedGames = AdvancedMetaEngineDetection::detectGames(fslist, skipADFlags, skipIncomplete);
+
+ ADDetectedGames tapeGames = detectZxTapeGames(fslist, skipADFlags);
+ for (const ADDetectedGame &game : tapeGames)
+ detectedGames.push_back(toDetectedGame(game, nullptr));
+
+ return detectedGames;
+}
+
+ADDetectedGames FreescapeMetaEngineDetection::detectZxTapeGames(const Common::FSList &fslist, uint32 skipADFlags) const {
+ ADDetectedGames detectedGames;
+
+ for (const Common::FSNode &node : fslist) {
+ Common::File file;
+ Common::String name = node.getName();
+ if ((name.hasSuffixIgnoreCase(".tap") || name.hasSuffixIgnoreCase(".tzx")) && file.open(node)) {
+ for (const ADGameDescription *desc = Freescape::gameDescriptions; desc->gameId; ++desc) {
+ if (!(desc->flags & skipADFlags) && desc->platform == Common::kPlatformZX) {
+ file.seek(0);
+ Freescape::ZxTapeFileList files;
+ if (Freescape::extractZxSpectrumTapeFiles(file, desc->gameId, files) &&
+ Freescape::matchZxSpectrumTapeFiles(files, *desc, _md5Bytes))
+ detectedGames.push_back(ADDetectedGame(desc));
+ }
+ }
+ }
+ }
+
+ return detectedGames;
+}
+
DetectedGame FreescapeMetaEngineDetection::toDetectedGame(const ADDetectedGame &adGame, ADDetectedGameExtraInfo *extraInfo) const {
DetectedGame game = AdvancedMetaEngineDetection::toDetectedGame(adGame);
diff --git a/engines/freescape/games/eclipse/zx.cpp b/engines/freescape/games/eclipse/zx.cpp
index 103e11d3760..dabb21644bc 100644
--- a/engines/freescape/games/eclipse/zx.cpp
+++ b/engines/freescape/games/eclipse/zx.cpp
@@ -86,24 +86,28 @@ void EclipseEngine::loadHeartFramesZX(Common::SeekableReadStream *file, int rest
void EclipseEngine::loadAssetsZXFullGame() {
Common::File file;
+ const char *prefix = isEclipse2() ? "totaleclipse2" : "totaleclipse";
+ Common::Path titleFile(Common::String::format("%s.zx.title", prefix));
+ Common::Path borderFile(Common::String::format("%s.zx.border", prefix));
+ Common::Path dataFile(Common::String::format("%s.zx.data", prefix));
- file.open("totaleclipse.zx.title");
+ file.open(titleFile);
if (file.isOpen()) {
_title = loadAndConvertScrImage(&file);
} else
- error("Unable to find totaleclipse.zx.title");
+ error("Unable to find %s", titleFile.toString().c_str());
file.close();
- file.open("totaleclipse.zx.border");
+ file.open(borderFile);
if (file.isOpen()) {
_border = loadAndConvertScrImage(&file);
} else
- error("Unable to find totaleclipse.zx.border");
+ error("Unable to find %s", borderFile.toString().c_str());
file.close();
- file.open("totaleclipse.zx.data");
+ file.open(dataFile);
if (!file.isOpen())
- error("Failed to open totaleclipse.zx.data");
+ error("Failed to open %s", dataFile.toString().c_str());
if (isEclipse2()) {
loadMessagesFixedSize(&file, 0x2ac, 16, 30);
diff --git a/engines/freescape/module.mk b/engines/freescape/module.mk
index a88d1003f37..57fcd93b830 100644
--- a/engines/freescape/module.mk
+++ b/engines/freescape/module.mk
@@ -72,7 +72,8 @@ MODULE_OBJS := \
sound/zx.o \
ui.o \
unpack.o \
- wb.o
+ wb.o \
+ zx_tape.o
ifdef USE_TINYGL
MODULE_OBJS += \
@@ -105,4 +106,5 @@ endif
include $(srcdir)/rules.mk
# Detection objects
-DETECT_OBJS += $(MODULE)/detection.o
+DETECT_OBJS += $(MODULE)/detection.o \
+ $(MODULE)/zx_tape.o
diff --git a/engines/freescape/zx_tape.cpp b/engines/freescape/zx_tape.cpp
new file mode 100644
index 00000000000..3190231fab1
--- /dev/null
+++ b/engines/freescape/zx_tape.cpp
@@ -0,0 +1,121 @@
+/* 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 "freescape/zx_tape.h"
+
+#include "common/file.h"
+#include "common/formats/spectrum_tape.h"
+#include "common/fs.h"
+#include "common/md5.h"
+#include "common/memstream.h"
+#include "engines/advancedDetector.h"
+
+namespace Freescape {
+
+bool extractZxSpectrumTapeFiles(Common::SeekableReadStream &stream, const char *prefix, ZxTapeFileList &files) {
+ files.clear();
+
+ Common::SpectrumTapeBlocks blocks;
+ if (!Common::parseSpectrumTape(stream, blocks))
+ return false;
+
+ Common::Array<byte> title;
+ Common::Array<byte> border;
+ Common::Array<byte> data;
+
+ for (const Common::SpectrumTapeBlock &block : blocks) {
+ if (block.tap.size() >= 2) {
+ Common::Array<byte> body;
+ body.assign(block.tap.begin() + 1, block.tap.end() - 1);
+ if (body.size() == 6912) {
+ title = border;
+ border = body;
+ } else if (body.size() >= data.size()) {
+ data = body;
+ }
+ }
+ }
+
+ const struct {
+ const char *suffix;
+ const Common::Array<byte> &payload;
+ } outputs[] = {
+ { "title", title },
+ { "border", border },
+ { "data", data }
+ };
+ for (uint i = 0; i < ARRAYSIZE(outputs); ++i) {
+ if (!outputs[i].payload.empty()) {
+ ZxTapeFile file;
+ file.name = Common::Path(Common::String::format("%s.zx.%s", prefix, outputs[i].suffix), Common::Path::kNoSeparator);
+ file.data = outputs[i].payload;
+ files.push_back(file);
+ }
+ }
+
+ return !data.empty();
+}
+
+bool matchZxSpectrumTapeFiles(const ZxTapeFileList &files, const ADGameDescription &desc, uint md5Bytes) {
+ bool matched = desc.platform == Common::kPlatformZX;
+
+ for (const ADGameFileDescription *fileDesc = desc.filesDescriptions; matched && fileDesc->fileName; ++fileDesc) {
+ bool fileMatched = false;
+ Common::Path fileName(fileDesc->fileName, Common::Path::kNoSeparator);
+ for (uint i = 0; !fileMatched && i < files.size(); ++i) {
+ fileMatched = files[i].name.equalsIgnoreCase(fileName) &&
+ (fileDesc->fileSize == AD_NO_SIZE || fileDesc->fileSize == files[i].data.size());
+ if (fileMatched && fileDesc->md5) {
+ Common::MemoryReadStream stream(files[i].data.data(), files[i].data.size());
+ fileMatched = Common::computeStreamMD5AsString(stream, md5Bytes) == fileDesc->md5;
+ }
+ }
+ matched = fileMatched;
+ }
+
+ return matched;
+}
+
+Common::Archive *makeZxSpectrumTapeArchive(const ADGameDescription &desc, const Common::Path &gamePath) {
+ Common::Archive *archive = nullptr;
+
+ Common::FSList files;
+ if (desc.platform == Common::kPlatformZX && Common::FSNode(gamePath).getChildren(files, Common::FSNode::kListFilesOnly)) {
+ for (const Common::FSNode &node : files) {
+ Common::File file;
+ Common::String name = node.getName();
+ if ((name.hasSuffixIgnoreCase(".tap") || name.hasSuffixIgnoreCase(".tzx")) && file.open(node)) {
+ ZxTapeFileList tapeFiles;
+ if (extractZxSpectrumTapeFiles(file, desc.gameId, tapeFiles) && matchZxSpectrumTapeFiles(tapeFiles, desc)) {
+ Common::SpectrumTapeArchive *tapeArchive = new Common::SpectrumTapeArchive();
+ for (const ZxTapeFile &tapeFile : tapeFiles)
+ tapeArchive->addFile(tapeFile.name, tapeFile.data);
+ archive = tapeArchive;
+ break;
+ }
+ }
+ }
+ }
+
+ return archive;
+}
+
+} // End of namespace Freescape
diff --git a/engines/freescape/zx_tape.h b/engines/freescape/zx_tape.h
new file mode 100644
index 00000000000..166775ee7d2
--- /dev/null
+++ b/engines/freescape/zx_tape.h
@@ -0,0 +1,51 @@
+/* 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/>.
+ *
+ */
+
+#ifndef FREESCAPE_ZX_TAPE_H
+#define FREESCAPE_ZX_TAPE_H
+
+#include "common/array.h"
+#include "common/path.h"
+
+namespace Common {
+class Archive;
+class FSNode;
+class SeekableReadStream;
+}
+
+struct ADGameDescription;
+
+namespace Freescape {
+
+struct ZxTapeFile {
+ Common::Path name;
+ Common::Array<byte> data;
+};
+
+typedef Common::Array<ZxTapeFile> ZxTapeFileList;
+
+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);
+
+} // End of namespace Freescape
+
+#endif
More information about the Scummvm-git-logs
mailing list