[Scummvm-git-logs] scummvm master -> 6b85a98f60734984c5eec9dbbeb70c3df4d04909

dreammaster noreply at scummvm.org
Sat May 30 09:30:36 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:
beb6b41d9f COMMON: Add ZX Spectrum tape archive support
0c02ac47ed GLK: SCOTT: Move ZX Spectrum loading to separate module
11534ae827 GLK: SCOTT: Detect and load ZX Spectrum tape images
6b85a98f60 COMMON: Name ZX Spectrum TZX block IDs


Commit: beb6b41d9fc0b242f0c38aad051c17c317dfa664
    https://github.com/scummvm/scummvm/commit/beb6b41d9fc0b242f0c38aad051c17c317dfa664
Author: Shadow Maker (shm at vtrd.in)
Date: 2026-05-30T19:30:31+10:00

Commit Message:
COMMON: Add ZX Spectrum tape archive support

Add a parser for ZX Spectrum TAP and TZX tape images that preserves parsed blocks for callers.

Expose tape data payloads through a Common::Archive wrapper. Header blocks are used to name the following payload and are not listed as files, so engines can inspect real tape contents through the standard archive API.

Changed paths:
  A common/formats/spectrum_tape.cpp
  A common/formats/spectrum_tape.h
    common/formats/module.mk


diff --git a/common/formats/module.mk b/common/formats/module.mk
index e550d5dcd78..ca225e8da93 100644
--- a/common/formats/module.mk
+++ b/common/formats/module.mk
@@ -11,6 +11,7 @@ MODULE_OBJS := \
 	po_parser.o \
 	prodos.o \
 	quicktime.o \
+	spectrum_tape.o \
 	winexe.o \
 	winexe_ne.o \
 	winexe_pe.o \
diff --git a/common/formats/spectrum_tape.cpp b/common/formats/spectrum_tape.cpp
new file mode 100644
index 00000000000..9881ef69fbc
--- /dev/null
+++ b/common/formats/spectrum_tape.cpp
@@ -0,0 +1,482 @@
+/* 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/formats/spectrum_tape.h"
+
+#include "common/endian.h"
+#include "common/memstream.h"
+#include "common/stream.h"
+#include "common/util.h"
+
+namespace Common {
+
+struct SpectrumTapeHeader {
+	bool valid = false;
+	byte type = 0;
+	String name;
+	uint16 size = 0;
+	uint16 param1 = 0;
+	uint16 param2 = 0;
+};
+
+static bool readBytes(SeekableReadStream &stream, Array<byte> &out, uint32 len) {
+	out.resize(len);
+	return len == 0 || stream.read(out.data(), len) == len;
+}
+
+static bool readPayload(SeekableReadStream &stream, Array<byte> &out, uint32 len) {
+	if (stream.pos() + len > stream.size())
+		return false;
+
+	return readBytes(stream, out, len);
+}
+
+static uint32 readUint24LE(const byte *data) {
+	return READ_LE_UINT16(data) | (data[2] << 16);
+}
+
+static bool appendBlock(SpectrumTapeBlocks &blocks, byte id, const Array<byte> &data, uint32 tapOffset = 0xffffffff) {
+	SpectrumTapeBlock block;
+	block.id = id;
+	block.data = data;
+
+	if (tapOffset != 0xffffffff) {
+		if (tapOffset > data.size())
+			return false;
+		block.tap.assign(data.begin() + tapOffset, data.end());
+	}
+
+	blocks.push_back(block);
+	return true;
+}
+
+static bool getTapBody(const SpectrumTapeBlock &block, Array<byte> &body, byte *flag = nullptr) {
+	if (block.tap.size() < 2)
+		return false;
+
+	if (flag)
+		*flag = block.tap[0];
+
+	body.assign(block.tap.begin() + 1, block.tap.end() - 1);
+	return true;
+}
+
+static SpectrumTapeHeader parseTapHeader(const SpectrumTapeBlock &block) {
+	SpectrumTapeHeader header;
+	Array<byte> body;
+	byte flag = 0xff;
+	if (!getTapBody(block, body, &flag) || flag != 0x00 || body.size() != 17)
+		return header;
+
+	header.valid = true;
+	header.type = body[0];
+	header.name = String(reinterpret_cast<const char *>(body.data() + 1), 10);
+	header.name.trim();
+	header.size = READ_LE_UINT16(body.data() + 11);
+	header.param1 = READ_LE_UINT16(body.data() + 13);
+	header.param2 = READ_LE_UINT16(body.data() + 15);
+	return header;
+}
+
+static String sanitizeTapeName(const String &name) {
+	String result;
+	for (uint i = 0; i < name.size(); ++i) {
+		char c = name[i];
+		if (isAlnum(c))
+			result += c;
+		else if (c == '_' || c == '-' || c == '.')
+			result += c;
+		else if (!result.empty() && result.lastChar() != '_')
+			result += '_';
+	}
+
+	while (!result.empty() && (result.lastChar() == '_' || result.lastChar() == '.'))
+		result.deleteLastChar();
+
+	return result;
+}
+
+static String getTapeFileName(const SpectrumTapeHeader &header, const Array<byte> &body, uint index) {
+	String base = sanitizeTapeName(header.name);
+	if (base.empty())
+		base = body.size() == 6912 ? "screen" : "data";
+
+	const char *extension = "bin";
+	if (body.size() == 6912)
+		extension = "scr";
+	else if (header.valid) {
+		switch (header.type) {
+		case 0:
+			extension = "bas";
+			break;
+		case 1:
+			extension = "num";
+			break;
+		case 2:
+			extension = "chr";
+			break;
+		case 3:
+			extension = "bin";
+			break;
+		default:
+			extension = "dat";
+			break;
+		}
+	}
+
+	return String::format("%03u-%s.%s", index, base.c_str(), extension);
+}
+
+static bool parseTap(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
+	while (stream.pos() < stream.size()) {
+		if (stream.pos() + 2 > stream.size())
+			return false;
+
+		uint16 len = stream.readUint16LE();
+		if (stream.err() || stream.pos() + len > stream.size())
+			return false;
+
+		SpectrumTapeBlock block;
+		block.id = 0x10;
+		block.data.resize(4);
+		WRITE_LE_UINT16(block.data.data(), 1000);
+		WRITE_LE_UINT16(block.data.data() + 2, len);
+		if (!readBytes(stream, block.tap, len))
+			return false;
+
+		blocks.push_back(block);
+	}
+
+	return true;
+}
+
+static bool readTzxDataBlock(SeekableReadStream &stream, SpectrumTapeBlocks &blocks, byte id, uint32 headerSize, uint32 lenOffset) {
+	Array<byte> data;
+	if (!readPayload(stream, data, headerSize))
+		return false;
+
+	uint32 len = readUint24LE(data.data() + lenOffset);
+	if (stream.pos() + len > stream.size())
+		return false;
+
+	uint32 offset = data.size();
+	data.resize(offset + len);
+	if (len != 0 && stream.read(data.data() + offset, len) != len)
+		return false;
+
+	return appendBlock(blocks, id, data, headerSize);
+}
+
+static bool readSizedBlock(SeekableReadStream &stream, SpectrumTapeBlocks &blocks, byte id, uint32 size) {
+	Array<byte> data;
+	if (!readPayload(stream, data, size))
+		return false;
+
+	return appendBlock(blocks, id, data);
+}
+
+static bool readLengthPrefixedBlock(SeekableReadStream &stream, SpectrumTapeBlocks &blocks, byte id, uint32 lengthSize, uint32 extraSize = 0) {
+	Array<byte> data;
+	if (!readPayload(stream, data, lengthSize))
+		return false;
+
+	uint32 len;
+	if (lengthSize == 1)
+		len = data[0];
+	else if (lengthSize == 2)
+		len = READ_LE_UINT16(data.data());
+	else
+		len = READ_LE_UINT32(data.data());
+
+	if (len < extraSize)
+		return false;
+
+	uint32 offset = data.size();
+	data.resize(offset + len - extraSize);
+	if (data.size() != offset && stream.read(data.data() + offset, data.size() - offset) != data.size() - offset)
+		return false;
+
+	return appendBlock(blocks, id, data);
+}
+
+static bool parseTzx(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
+	if (!stream.seek(10))
+		return false;
+
+	while (stream.pos() < stream.size()) {
+		byte id = stream.readByte();
+		if (stream.err())
+			return false;
+
+		switch (id) {
+		case 0x10: { // Standard Speed Data Block
+			Array<byte> data;
+			if (!readPayload(stream, data, 4))
+				return false;
+			uint16 len = READ_LE_UINT16(data.data() + 2);
+			uint32 offset = data.size();
+			data.resize(offset + len);
+			if (len != 0 && stream.read(data.data() + offset, len) != len)
+				return false;
+			if (!appendBlock(blocks, id, data, 4))
+				return false;
+			break;
+		}
+		case 0x11: // Turbo Speed Data Block
+			if (!readTzxDataBlock(stream, blocks, id, 0x12, 0x0f))
+				return false;
+			break;
+		case 0x12: // Pure Tone
+			if (!readSizedBlock(stream, blocks, id, 4))
+				return false;
+			break;
+		case 0x13: { // Pulse Sequence
+			byte count = stream.readByte();
+			if (stream.err())
+				return false;
+			Array<byte> data;
+			data.resize(1 + count * 2);
+			data[0] = count;
+			if (count != 0 && stream.read(data.data() + 1, count * 2) != count * 2)
+				return false;
+			if (!appendBlock(blocks, id, data))
+				return false;
+			break;
+		}
+		case 0x14: // Pure Data Block
+			if (!readTzxDataBlock(stream, blocks, id, 0x0a, 0x07))
+				return false;
+			break;
+		case 0x15: { // Direct Recording
+			Array<byte> data;
+			if (!readPayload(stream, data, 8))
+				return false;
+			uint32 len = readUint24LE(data.data() + 5);
+			uint32 offset = data.size();
+			data.resize(offset + len);
+			if (len != 0 && stream.read(data.data() + offset, len) != len)
+				return false;
+			if (!appendBlock(blocks, id, data))
+				return false;
+			break;
+		}
+		case 0x16: // C64 ROM type data
+		case 0x17: // C64 turbo tape data
+			if (!readLengthPrefixedBlock(stream, blocks, id, 4, 4))
+				return false;
+			break;
+		case 0x18: // CSW recording
+		case 0x19: // Generalized data
+			if (!readLengthPrefixedBlock(stream, blocks, id, 4))
+				return false;
+			break;
+		case 0x20: // Pause
+		case 0x23: // Jump to
+		case 0x24: // Loop start
+			if (!readSizedBlock(stream, blocks, id, 2))
+				return false;
+			break;
+		case 0x21: // Group start
+		case 0x30: // Text description
+			if (!readLengthPrefixedBlock(stream, blocks, id, 1))
+				return false;
+			break;
+		case 0x22: // Group end
+		case 0x25: // Loop end
+		case 0x27: // Return from sequence
+			if (!appendBlock(blocks, id, Array<byte>()))
+				return false;
+			break;
+		case 0x26: { // Call sequence
+			uint16 count = stream.readUint16LE();
+			if (stream.err())
+				return false;
+			Array<byte> data;
+			data.resize(2 + count * 2);
+			WRITE_LE_UINT16(data.data(), count);
+			if (count != 0 && stream.read(data.data() + 2, count * 2) != count * 2)
+				return false;
+			if (!appendBlock(blocks, id, data))
+				return false;
+			break;
+		}
+		case 0x28: // Select
+		case 0x32: // Archive info
+			if (!readLengthPrefixedBlock(stream, blocks, id, 2))
+				return false;
+			break;
+		case 0x2a: // Stop the tape (48k)
+			if (!readSizedBlock(stream, blocks, id, 4))
+				return false;
+			break;
+		case 0x2b: // Set signal level
+			if (!readSizedBlock(stream, blocks, id, 5))
+				return false;
+			break;
+		case 0x31: { // Message
+			Array<byte> data;
+			if (!readPayload(stream, data, 2))
+				return false;
+			uint32 offset = data.size();
+			data.resize(offset + data[1]);
+			if (data[1] != 0 && stream.read(data.data() + offset, data[1]) != data[1])
+				return false;
+			if (!appendBlock(blocks, id, data))
+				return false;
+			break;
+		}
+		case 0x33: { // Hardware type
+			byte count = stream.readByte();
+			if (stream.err())
+				return false;
+			Array<byte> data;
+			data.resize(1 + count * 3);
+			data[0] = count;
+			if (count != 0 && stream.read(data.data() + 1, count * 3) != count * 3)
+				return false;
+			if (!appendBlock(blocks, id, data))
+				return false;
+			break;
+		}
+		case 0x34: // Emulation info
+			if (!readSizedBlock(stream, blocks, id, 8))
+				return false;
+			break;
+		case 0x35: { // Custom info
+			Array<byte> data;
+			if (!readPayload(stream, data, 0x14))
+				return false;
+			uint32 len = READ_LE_UINT32(data.data() + 0x10);
+			uint32 offset = data.size();
+			data.resize(offset + len);
+			if (len != 0 && stream.read(data.data() + offset, len) != len)
+				return false;
+			if (!appendBlock(blocks, id, data))
+				return false;
+			break;
+		}
+		case 0x40: { // Snapshot
+			Array<byte> data;
+			if (!readPayload(stream, data, 4))
+				return false;
+			uint32 len = readUint24LE(data.data() + 1);
+			uint32 offset = data.size();
+			data.resize(offset + len);
+			if (len != 0 && stream.read(data.data() + offset, len) != len)
+				return false;
+			if (!appendBlock(blocks, id, data))
+				return false;
+			break;
+		}
+		case 0x4b: // Kansas City Standard
+			if (!readLengthPrefixedBlock(stream, blocks, id, 4))
+				return false;
+			break;
+		case 0x5a: // Glue
+			if (!readSizedBlock(stream, blocks, id, 9))
+				return false;
+			break;
+		default:
+			return false;
+		}
+	}
+
+	return true;
+}
+
+bool parseSpectrumTape(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
+	blocks.clear();
+
+	if (!stream.seek(0) || stream.size() < 2)
+		return false;
+
+	byte signature[8] = {};
+	uint32 signatureSize = MIN<uint32>(sizeof(signature), stream.size());
+	if (stream.read(signature, signatureSize) != signatureSize)
+		return false;
+
+	if (!stream.seek(0))
+		return false;
+
+	if (signatureSize == sizeof(signature) && !memcmp(signature, "ZXTape!\x1a", 8))
+		return parseTzx(stream, blocks);
+
+	return parseTap(stream, blocks);
+}
+
+SpectrumTapeArchive::SpectrumTapeArchive(const SpectrumTapeBlocks &blocks) {
+	SpectrumTapeHeader pendingHeader;
+	uint fileIndex = 1;
+
+	for (const SpectrumTapeBlock &block : blocks) {
+		SpectrumTapeHeader header = parseTapHeader(block);
+		if (header.valid) {
+			pendingHeader = header;
+			continue;
+		}
+
+		Array<byte> body;
+		byte flag = 0x00;
+		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);
+
+		pendingHeader.valid = false;
+	}
+}
+
+bool SpectrumTapeArchive::hasFile(const Path &path) const {
+	for (const ArchiveFile &file : _files) {
+		if (file.name.equalsIgnoreCase(path))
+			return true;
+	}
+
+	return false;
+}
+
+int SpectrumTapeArchive::listMembers(ArchiveMemberList &list) const {
+	for (const ArchiveFile &file : _files)
+		list.push_back(ArchiveMemberList::value_type(new GenericArchiveMember(file.name, *this)));
+
+	return _files.size();
+}
+
+const ArchiveMemberPtr SpectrumTapeArchive::getMember(const Path &path) const {
+	if (!hasFile(path))
+		return ArchiveMemberPtr();
+
+	return ArchiveMemberPtr(new GenericArchiveMember(path, *this));
+}
+
+SeekableReadStream *SpectrumTapeArchive::createReadStreamForMember(const Path &path) const {
+	for (const ArchiveFile &file : _files) {
+		if (file.name.equalsIgnoreCase(path))
+			return new MemoryReadStream(file.data.data(), file.data.size(), DisposeAfterUse::NO);
+	}
+
+	return nullptr;
+}
+
+} // End of namespace Common
diff --git a/common/formats/spectrum_tape.h b/common/formats/spectrum_tape.h
new file mode 100644
index 00000000000..ebb61d4c3ac
--- /dev/null
+++ b/common/formats/spectrum_tape.h
@@ -0,0 +1,63 @@
+/* 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 COMMON_FORMATS_SPECTRUM_TAPE_H
+#define COMMON_FORMATS_SPECTRUM_TAPE_H
+
+#include "common/array.h"
+#include "common/archive.h"
+#include "common/str.h"
+
+namespace Common {
+
+class SeekableReadStream;
+
+struct SpectrumTapeBlock {
+	byte id;
+	Array<byte> data;
+	Array<byte> tap;
+};
+
+typedef Array<SpectrumTapeBlock> SpectrumTapeBlocks;
+
+bool parseSpectrumTape(SeekableReadStream &stream, SpectrumTapeBlocks &blocks);
+
+class SpectrumTapeArchive : public Archive {
+public:
+	SpectrumTapeArchive(const SpectrumTapeBlocks &blocks);
+
+	bool hasFile(const Path &path) const override;
+	int listMembers(ArchiveMemberList &list) const override;
+	const ArchiveMemberPtr getMember(const Path &path) const override;
+	SeekableReadStream *createReadStreamForMember(const Path &path) const override;
+
+private:
+	struct ArchiveFile {
+		Path name;
+		Array<byte> data;
+	};
+
+	Array<ArchiveFile> _files;
+};
+
+} // End of namespace Common
+
+#endif


Commit: 0c02ac47ed035ea9b4b6a5deb369dd6334d956af
    https://github.com/scummvm/scummvm/commit/0c02ac47ed035ea9b4b6a5deb369dd6334d956af
Author: Shadow Maker (shm at vtrd.in)
Date: 2026-05-30T19:30:31+10:00

Commit Message:
GLK: SCOTT: Move ZX Spectrum loading to separate module

Move the existing ZX Spectrum snapshot loading code out of load_game.cpp and into a dedicated Scott loader module.

Register the new object in the GLK build so later Scott tape handling can reuse the same loader without keeping the snapshot path in the generic game file loader.

Changed paths:
  A engines/glk/scott/load_zx_spectrum.cpp
  A engines/glk/scott/load_zx_spectrum.h
    engines/glk/module.mk
    engines/glk/scott/load_game.cpp


diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 11511e31716..a067b26207e 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -250,6 +250,7 @@ MODULE_OBJS := \
 	scott/layout_text.o \
 	scott/line_drawing.o \
 	scott/load_ti99_4a.o \
+	scott/load_zx_spectrum.o \
 	scott/resource.o \
 	scott/restore_state.o \
 	scott/ring_buffer.o \
@@ -393,5 +394,6 @@ DETECT_OBJS += $(MODULE)/zcode/detection.o
 
 # Dependencies of detection objects
 DETECT_OBJS += $(MODULE)/blorb.o
+DETECT_OBJS += $(MODULE)/scott/load_zx_spectrum.o
 DETECT_OBJS += $(MODULE)/advsys/game.o
 endif
diff --git a/engines/glk/scott/load_game.cpp b/engines/glk/scott/load_game.cpp
index 629cb9f1d45..ea6862c9d8d 100644
--- a/engines/glk/scott/load_game.cpp
+++ b/engines/glk/scott/load_game.cpp
@@ -35,7 +35,6 @@
 #include "glk/scott/globals.h"
 #include "glk/scott/command_parser.h"
 #include "glk/scott/decompress_text.h"
-#include "glk/scott/decompress_z80.h"
 #include "glk/scott/detection.h"
 #include "glk/scott/detection_tables.h"
 #include "glk/scott/game_info.h"
@@ -50,34 +49,11 @@
 #include "glk/scott/gremlins.h"
 #include "glk/scott/seas_of_blood.h"
 #include "glk/scott/load_ti99_4a.h"
+#include "glk/scott/load_zx_spectrum.h"
 
 namespace Glk {
 namespace Scott {
 
-void loadZXSpectrum(Common::SeekableReadStream *f, Common::String md5) {
-	_G(_entireFile) = new uint8_t[_G(_fileLength)];
-	size_t result = f->read(_G(_entireFile), _G(_fileLength));
-	if (result != _G(_fileLength))
-		g_scott->fatal("File empty or read error!");
-
-	uint8_t *uncompressed = decompressZ80(_G(_entireFile), _G(_fileLength));
-	if (uncompressed != nullptr) {
-		delete[] _G(_entireFile);
-		_G(_entireFile) = uncompressed;
-		_G(_fileLength) = 0xc000;
-	}
-
-	int offset;
-	DictionaryType dict_type = getId(&offset);
-	if (dict_type == NOT_A_GAME)
-		return;
-
-	int index = _G(_md5Index)[md5];
-	if (tryLoading(_G(_games)[index], offset, 0)) {
-		_G(_game) = &_G(_games)[index];
-	}
-}
-
 void loadC64(Common::SeekableReadStream* f, Common::String md5) {
 	_G(_entireFile) = new uint8_t[_G(_fileLength)];
 	size_t result = f->read(_G(_entireFile), _G(_fileLength));
diff --git a/engines/glk/scott/load_zx_spectrum.cpp b/engines/glk/scott/load_zx_spectrum.cpp
new file mode 100644
index 00000000000..64de16dfc60
--- /dev/null
+++ b/engines/glk/scott/load_zx_spectrum.cpp
@@ -0,0 +1,272 @@
+/* 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/>.
+ *
+ */
+
+/*
+ * Based on ScottFree interpreter version 1.14 developed by Swansea
+ * University Computer Society without disassembly of any other game
+ * drivers, only of game databases as permitted by EEC law (for purposes
+ * of compatibility).
+ *
+ * Licensed under GPLv2
+ *
+ * https://github.com/angstsmurf/spatterlight/tree/master/terps/scott
+ */
+
+#include "common/formats/spectrum_tape.h"
+#include "common/md5.h"
+#include "common/memstream.h"
+#include "common/system.h"
+#include "graphics/managed_surface.h"
+#include "glk/window_graphics.h"
+#include "glk/scott/decompress_z80.h"
+#include "glk/scott/globals.h"
+#include "glk/scott/load_zx_spectrum.h"
+#include "glk/scott/resource.h"
+#include "glk/scott/scott.h"
+#include "image/scr.h"
+
+namespace Glk {
+namespace Scott {
+
+static Common::Array<byte> g_zxSpectrumTapeTitleScreen;
+
+struct ZXSpectrumTapeData {
+	Common::Array<byte> screen;
+	Common::Array<byte> code;
+};
+
+static bool extractZXSpectrumTapeData(Common::SeekableReadStream &stream, ZXSpectrumTapeData &tape) {
+	Common::SpectrumTapeBlocks blocks;
+	if (!Common::parseSpectrumTape(stream, blocks))
+		return false;
+
+	Common::SpectrumTapeArchive archive(blocks);
+	Common::ArchiveMemberList files;
+	archive.listMembers(files);
+
+	tape.screen.clear();
+	tape.code.clear();
+	for (const Common::ArchiveMemberPtr &file : files) {
+		Common::ScopedPtr<Common::SeekableReadStream> payload(file->createReadStream());
+		if (!payload)
+			continue;
+
+		Common::Array<byte> data;
+		data.resize(payload->size());
+		if (!data.empty() && payload->read(data.data(), data.size()) != data.size())
+			return false;
+
+		if (file->getPathInArchive().baseName().hasSuffixIgnoreCase(".scr"))
+			tape.screen = data;
+		else if (data.size() >= tape.code.size())
+			tape.code = data;
+	}
+
+	return !tape.code.empty();
+}
+
+Common::String computeZXSpectrumTapeCodeMD5(Common::SeekableReadStream &stream, uint32 *codeSize) {
+	ZXSpectrumTapeData tape;
+	if (!extractZXSpectrumTapeData(stream, tape))
+		return Common::String();
+
+	if (codeSize)
+		*codeSize = tape.code.size();
+
+	Common::MemoryReadStream codeStream(tape.code.data(), tape.code.size());
+	return Common::computeStreamMD5AsString(codeStream);
+}
+
+static void loadZXSpectrumGame(Common::String md5) {
+	int offset;
+	DictionaryType dict_type = getId(&offset);
+	if (dict_type == NOT_A_GAME)
+		return;
+
+	int index = _G(_md5Index)[md5];
+	if (tryLoading(_G(_games)[index], offset, 0)) {
+		_G(_game) = &_G(_games)[index];
+	}
+}
+
+void loadZXSpectrum(Common::SeekableReadStream *f, Common::String md5) {
+	g_zxSpectrumTapeTitleScreen.clear();
+
+	_G(_entireFile) = new uint8_t[_G(_fileLength)];
+	size_t result = f->read(_G(_entireFile), _G(_fileLength));
+	if (result != _G(_fileLength))
+		g_scott->fatal("File empty or read error!");
+
+	uint8_t *uncompressed = decompressZ80(_G(_entireFile), _G(_fileLength));
+	if (uncompressed != nullptr) {
+		delete[] _G(_entireFile);
+		_G(_entireFile) = uncompressed;
+		_G(_fileLength) = 0xc000;
+	}
+
+	loadZXSpectrumGame(md5);
+}
+
+void loadZXSpectrumTape(Common::SeekableReadStream *f) {
+	g_zxSpectrumTapeTitleScreen.clear();
+
+	ZXSpectrumTapeData tape;
+	if (!extractZXSpectrumTapeData(*f, tape))
+		return;
+
+	g_zxSpectrumTapeTitleScreen = tape.screen;
+
+	_G(_fileLength) = tape.code.size();
+	_G(_entireFile) = new uint8_t[_G(_fileLength)];
+	memcpy(_G(_entireFile), tape.code.data(), _G(_fileLength));
+
+	Common::MemoryReadStream codeStream(_G(_entireFile), _G(_fileLength));
+	Common::String md5 = Common::computeStreamMD5AsString(codeStream);
+
+	loadZXSpectrumGame(md5);
+}
+
+static Graphics::ManagedSurface *decodeZXSpectrumTitleScreen() {
+	if (g_zxSpectrumTapeTitleScreen.size() != 6912)
+		return nullptr;
+
+	Common::Array<byte> titleScreen = g_zxSpectrumTapeTitleScreen;
+	if (CURRENT_GAME == HULK) {
+		byte *attrs = titleScreen.data() + 6144;
+		for (int row = 22; row < 24; row++) {
+			if (((attrs[row * 32] >> 3) & 0x07) == 7) {
+				for (int col = 0; col < 32; col++)
+					attrs[row * 32 + col] &= ~0x38;
+			}
+		}
+	}
+
+	Common::MemoryReadStream stream(titleScreen.data(), titleScreen.size());
+	::Image::ScrDecoder decoder;
+	if (!decoder.loadStream(stream))
+		return nullptr;
+
+	const Graphics::Surface *decoded = decoder.getSurface();
+	Graphics::ManagedSurface *surface = new Graphics::ManagedSurface();
+	surface->create(320, 200, g_system->getScreenFormat());
+	surface->fillRect(Common::Rect(0, 0, surface->w, surface->h), surface->format.RGBToColor(0, 0, 0));
+	surface->simpleBlitFrom(*decoded, Common::Point((surface->w - decoded->w) / 2, (surface->h - decoded->h) / 2),
+		Graphics::FLIP_NONE, false, 255, &decoder.getPalette());
+	return surface;
+}
+
+static void drawZXSpectrumTitleScreen(winid_t win, const Graphics::ManagedSurface &surface, int visibleLines) {
+	GraphicsWindow *gfxWin = dynamic_cast<GraphicsWindow *>(win);
+	if (!gfxWin || !gfxWin->_surface)
+		return;
+
+	g_scott->glk_window_clear(win);
+
+	const int srcWidth = surface.w;
+	const int srcHeight = surface.h;
+	int width = gfxWin->_w;
+	int height = width * srcHeight / srcWidth;
+	if (height > (int)gfxWin->_h) {
+		height = gfxWin->_h;
+		width = height * srcWidth / srcHeight;
+	}
+
+	if (width <= 0 || height <= 0)
+		return;
+
+	const int x = ((int)gfxWin->_w - width) / 2;
+	const int y = ((int)gfxWin->_h - height) / 2;
+	const int visibleHeight = CLIP<int>(visibleLines, 0, srcHeight) * height / srcHeight;
+	if (visibleHeight <= 0)
+		return;
+
+	if (width == srcWidth && height == srcHeight) {
+		gfxWin->_surface->blitFrom(surface, Common::Rect(0, 0, srcWidth, visibleHeight), Common::Point(x, y));
+	} else {
+		Graphics::ManagedSurface *scaled = surface.scale(width, height);
+		if (scaled) {
+			gfxWin->_surface->blitFrom(*scaled, Common::Rect(0, 0, width, visibleHeight), Common::Point(x, y));
+			scaled->free();
+			delete scaled;
+		}
+	}
+}
+
+void showZXSpectrumTapeTitleScreen() {
+	if (!g_scott->glk_gestalt(gestalt_Graphics, 0) || g_zxSpectrumTapeTitleScreen.empty())
+		return;
+
+	Graphics::ManagedSurface *surface = decodeZXSpectrumTitleScreen();
+	if (!surface)
+		return;
+
+	winid_t win = g_scott->glk_window_open(_G(_bottomWindow), winmethod_Above | winmethod_Proportional,
+		100, wintype_Graphics, GLK_GRAPHICS_ROCK);
+	if (!win) {
+		surface->free();
+		delete surface;
+		return;
+	}
+
+	g_scott->glk_window_set_background_color(win, 0);
+	uint graphWidth, graphHeight;
+	g_scott->glk_window_get_size(win, &graphWidth, &graphHeight);
+	uint targetHeight = graphWidth * surface->h / surface->w;
+	if (targetHeight > graphHeight)
+		targetHeight = graphHeight;
+	winid_t parent = g_scott->glk_window_get_parent(win);
+	if (parent)
+		g_scott->glk_window_set_arrangement(parent, winmethod_Above | winmethod_Fixed, targetHeight, win);
+	int visibleLines = 0;
+	drawZXSpectrumTitleScreen(win, *surface, visibleLines);
+	g_scott->glk_request_char_event(_G(_bottomWindow));
+	g_scott->glk_request_mouse_event(win);
+	g_scott->glk_request_timer_events(15);
+
+	event_t ev;
+	int frames = 0;
+	while (frames < 60 * 6 && !g_vm->shouldQuit()) {
+		g_scott->glk_select(&ev);
+		if (ev.type == evtype_CharInput || ev.type == evtype_MouseInput)
+			break;
+		if (ev.type == evtype_Arrange)
+			drawZXSpectrumTitleScreen(win, *surface, visibleLines);
+		else if (ev.type == evtype_Timer) {
+			if (visibleLines < surface->h) {
+				visibleLines = MIN<int>(visibleLines + 3, surface->h);
+				drawZXSpectrumTitleScreen(win, *surface, visibleLines);
+			}
+			frames++;
+		} else
+			g_scott->updates(ev);
+	}
+
+	g_scott->glk_request_timer_events(0);
+	g_scott->glk_cancel_char_event(_G(_bottomWindow));
+	g_scott->glk_cancel_mouse_event(win);
+	g_scott->glk_window_close(win, nullptr);
+
+	surface->free();
+	delete surface;
+}
+
+} // End of namespace Scott
+} // End of namespace Glk
diff --git a/engines/glk/scott/load_zx_spectrum.h b/engines/glk/scott/load_zx_spectrum.h
new file mode 100644
index 00000000000..b8e70ec9ce1
--- /dev/null
+++ b/engines/glk/scott/load_zx_spectrum.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/>.
+ *
+ */
+
+/*
+ * Based on ScottFree interpreter version 1.14 developed by Swansea
+ * University Computer Society without disassembly of any other game
+ * drivers, only of game databases as permitted by EEC law (for purposes
+ * of compatibility).
+ *
+ * Licensed under GPLv2
+ *
+ * https://github.com/angstsmurf/spatterlight/tree/master/terps/scott
+ */
+
+#ifndef GLK_SCOTT_LOAD_ZX_SPECTRUM_H
+#define GLK_SCOTT_LOAD_ZX_SPECTRUM_H
+
+#include "common/str.h"
+#include "common/stream.h"
+#include "glk/scott/types.h"
+
+namespace Glk {
+namespace Scott {
+
+void loadZXSpectrum(Common::SeekableReadStream *f, Common::String md5);
+void loadZXSpectrumTape(Common::SeekableReadStream *f);
+void showZXSpectrumTapeTitleScreen();
+Common::String computeZXSpectrumTapeCodeMD5(Common::SeekableReadStream &stream, uint32 *codeSize = nullptr);
+
+} // End of namespace Scott
+} // End of namespace Glk
+
+#endif


Commit: 11534ae827abe3ad324d00f05fc3439b93056f84
    https://github.com/scummvm/scummvm/commit/11534ae827abe3ad324d00f05fc3439b93056f84
Author: Shadow Maker (shm at vtrd.in)
Date: 2026-05-30T19:30:31+10:00

Commit Message:
GLK: SCOTT: Detect and load ZX Spectrum tape images

Recognize TAP and TZX files for the Scott subengine by hashing the extracted code payload from the common Spectrum tape archive.

Load tape images through the ZX Spectrum loader and show a SCREEN$ archive member as the Hulk startup title screen when one is present.

Changed paths:
    engines/glk/scott/detection.cpp
    engines/glk/scott/detection_tables.h
    engines/glk/scott/globals.cpp
    engines/glk/scott/load_game.cpp
    engines/glk/scott/scott.cpp


diff --git a/engines/glk/scott/detection.cpp b/engines/glk/scott/detection.cpp
index a565c97d9c2..855cd393eaa 100644
--- a/engines/glk/scott/detection.cpp
+++ b/engines/glk/scott/detection.cpp
@@ -36,6 +36,7 @@
 #include "glk/blorb.h"
 #include "glk/scott/detection.h"
 #include "glk/scott/detection_tables.h"
+#include "glk/scott/load_zx_spectrum.h"
 
 namespace Glk {
 namespace Scott {
@@ -59,7 +60,7 @@ GameDescriptor ScottMetaEngine::findGame(const char *gameId) {
 }
 
 bool ScottMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
-	const char *const EXTENSIONS[] = {".z80", ".saga", ".dat", ".D64", ".T64", "fiad", nullptr};
+	const char *const EXTENSIONS[] = {".z80", ".tap", ".tzx", ".saga", ".dat", ".D64", ".T64", "fiad", nullptr};
 
 	// Loop through the files of the folder
 	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
@@ -78,12 +79,21 @@ bool ScottMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &g
 		if (!gameFile.open(*file))
 			continue;
 		Common::String md5;
-		if (filename.hasSuffixIgnoreCase(".D64"))
+		size_t filesize = (size_t)gameFile.size();
+		if (filename.hasSuffixIgnoreCase(".tap") || filename.hasSuffixIgnoreCase(".tzx")) {
+			uint32 codeSize = 0;
+			md5 = computeZXSpectrumTapeCodeMD5(gameFile, &codeSize);
+			filesize = codeSize;
+			if (md5.empty()) {
+				gameFile.close();
+				continue;
+			}
+		} else if (filename.hasSuffixIgnoreCase(".D64")) {
 			md5 = Common::computeStreamMD5AsString(gameFile);
-		else
+		} else {
 			md5 = Common::computeStreamMD5AsString(gameFile, 5000);
+		}
 
-		size_t filesize = (size_t)gameFile.size();
 		gameFile.seek(0);
 		isBlorb = Blorb::isBlorb(gameFile, ID_SAAI);
 		gameFile.close();
@@ -100,6 +110,7 @@ bool ScottMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &g
 
 			// ignore possible variants for common extensions to prevent flooding in mass-add
 			if (!isBlorb && (filename.hasSuffixIgnoreCase(".z80") || filename.hasSuffixIgnoreCase(".dat") ||
+				filename.hasSuffixIgnoreCase(".tap") || filename.hasSuffixIgnoreCase(".tzx") ||
 				filename.hasSuffixIgnoreCase(".d64") || filename.hasSuffixIgnoreCase(".t64")))
 				continue;
 
diff --git a/engines/glk/scott/detection_tables.h b/engines/glk/scott/detection_tables.h
index d7d5d3ec78d..9b8e0bcba34 100644
--- a/engines/glk/scott/detection_tables.h
+++ b/engines/glk/scott/detection_tables.h
@@ -186,7 +186,7 @@ const GlkDetectionEntry SCOTT_GAMES[] = {
 	DT_ENTRY0("ghostking",		  "28d433d9f5d2de99dac460c5e1dfa9c5", 14206),
 
 	// ZX Spectrum games
-	DT_ENTRYP1("marveladventure", "ZXSpectrum", "0eec511d3cde815c73e5464ab0cdbef9", 40727, Common::kPlatformZX),
+	DT_ENTRYP1("marveladventure", "ZXSpectrum", "f669f2e3320b73c942dfab4cf5ad9a96", 41040, Common::kPlatformZX),
 
 	// 11 Mysterious Adventures
 	DT_ENTRYP1("goldenbaton",	"ZXSpectrum", "cb7dadc9d5f8bce453b9139265e4dd7d", 32060, Common::kPlatformZX),
diff --git a/engines/glk/scott/globals.cpp b/engines/glk/scott/globals.cpp
index f2d446aa289..babad0c5f92 100644
--- a/engines/glk/scott/globals.cpp
+++ b/engines/glk/scott/globals.cpp
@@ -106,7 +106,7 @@ Globals::Globals() : _sys(MAX_SYSMESS), _directions(NUMBER_OF_DIRECTIONS), _extr
 	_md5Index.setVal("84d5fbb16a37e495abf09d191fd8b1a2", 16); // perseus
 	_md5Index.setVal("afde056c152de79ea20453c42a2d08af", 18); // 10indians
 	_md5Index.setVal("6c6fbbbb50032463a6ea71c6750ea1f5", 20); // waxworks11
-	_md5Index.setVal("0eec511d3cde815c73e5464ab0cdbef9" ,22); // marveladventure
+	_md5Index.setVal("f669f2e3320b73c942dfab4cf5ad9a96", 22); // marveladventure
 	_md5Index.setVal("b4d8fc4eabed4f2400717303561ad0fa",  0); // misadv1
 	_md5Index.setVal("3ce5ea1a0473244bf469fd3c51f1dc48",  6); // midadv2
 	_md5Index.setVal("10109d9776b9372f9c768b53a664b113", 12); // robin of sherwood
diff --git a/engines/glk/scott/load_game.cpp b/engines/glk/scott/load_game.cpp
index ea6862c9d8d..7bc9696d208 100644
--- a/engines/glk/scott/load_game.cpp
+++ b/engines/glk/scott/load_game.cpp
@@ -88,25 +88,31 @@ void loadGameFile(Common::SeekableReadStream *f) {
 	_G(_game) = &_G(_fallbackGame);
 
 	Common::String md5 = g_vm->getGameMD5();
-	const GlkDetectionEntry *p = SCOTT_GAMES;
-
-	while (p->_md5) {
-		if (md5.equalsC(p->_md5)) {
-			if (!scumm_stricmp(p->_extra, "")) {
-				_G(_fallbackGame)._gameID = SCOTTFREE;
-				break;
-			} else if (!scumm_stricmp(p->_extra, "ZXSpectrum")) {
-				loadZXSpectrum(f, md5);
-				break;
-			} else if (!scumm_stricmp(p->_extra, "C64")) {
-				loadC64(f, md5);
-				break;
-			} else {
-				loadTI994A(f);
-				break;
+	Common::String filename = g_vm->getFilename();
+
+	if (filename.hasSuffixIgnoreCase(".tap") || filename.hasSuffixIgnoreCase(".tzx")) {
+		loadZXSpectrumTape(f);
+	} else {
+		const GlkDetectionEntry *p = SCOTT_GAMES;
+
+		while (p->_md5) {
+			if (md5.equalsC(p->_md5)) {
+				if (!scumm_stricmp(p->_extra, "")) {
+					_G(_fallbackGame)._gameID = SCOTTFREE;
+					break;
+				} else if (!scumm_stricmp(p->_extra, "ZXSpectrum")) {
+					loadZXSpectrum(f, md5);
+					break;
+				} else if (!scumm_stricmp(p->_extra, "C64")) {
+					loadC64(f, md5);
+					break;
+				} else {
+					loadTI994A(f);
+					break;
+				}
 			}
+			++p;
 		}
-		++p;
 	}
 
 	if (CURRENT_GAME == SCOTTFREE || CURRENT_GAME == TI994A)
diff --git a/engines/glk/scott/scott.cpp b/engines/glk/scott/scott.cpp
index 895a478e938..4a7ffac8926 100644
--- a/engines/glk/scott/scott.cpp
+++ b/engines/glk/scott/scott.cpp
@@ -38,6 +38,7 @@
 #include "glk/scott/command_parser.h"
 #include "glk/scott/definitions.h"
 #include "glk/scott/load_game.h"
+#include "glk/scott/load_zx_spectrum.h"
 #include "glk/scott/game_info.h"
 #include "glk/scott/globals.h"
 #include "glk/scott/hulk.h"
@@ -122,6 +123,8 @@ void Scott::runGame() {
 		_topHeight = 10;
 	}
 
+	showZXSpectrumTapeTitleScreen();
+
 	if (CURRENT_GAME == TI994A) {
 		display(_G(_bottomWindow), "In this adventure, you may abbreviate any word \
 				by typing its first %d letters, and directions by typing \


Commit: 6b85a98f60734984c5eec9dbbeb70c3df4d04909
    https://github.com/scummvm/scummvm/commit/6b85a98f60734984c5eec9dbbeb70c3df4d04909
Author: Shadow Maker (shm at vtrd.in)
Date: 2026-05-30T19:30:31+10:00

Commit Message:
COMMON: Name ZX Spectrum TZX block IDs

Replace direct byte values in the TZX parser switch with named enum values, keeping the block descriptions next to the corresponding cases.

Changed paths:
    common/formats/spectrum_tape.cpp


diff --git a/common/formats/spectrum_tape.cpp b/common/formats/spectrum_tape.cpp
index 9881ef69fbc..56de1870cf1 100644
--- a/common/formats/spectrum_tape.cpp
+++ b/common/formats/spectrum_tape.cpp
@@ -37,6 +37,39 @@ struct SpectrumTapeHeader {
 	uint16 param2 = 0;
 };
 
+enum TzxBlockId {
+	kTzxBlockStandardSpeedData = 0x10,
+	kTzxBlockTurboSpeedData = 0x11,
+	kTzxBlockPureTone = 0x12,
+	kTzxBlockPulseSequence = 0x13,
+	kTzxBlockPureData = 0x14,
+	kTzxBlockDirectRecording = 0x15,
+	kTzxBlockC64RomData = 0x16,
+	kTzxBlockC64TurboTapeData = 0x17,
+	kTzxBlockCswRecording = 0x18,
+	kTzxBlockGeneralizedData = 0x19,
+	kTzxBlockPause = 0x20,
+	kTzxBlockGroupStart = 0x21,
+	kTzxBlockGroupEnd = 0x22,
+	kTzxBlockJumpTo = 0x23,
+	kTzxBlockLoopStart = 0x24,
+	kTzxBlockLoopEnd = 0x25,
+	kTzxBlockCallSequence = 0x26,
+	kTzxBlockReturnFromSequence = 0x27,
+	kTzxBlockSelect = 0x28,
+	kTzxBlockStopTape48k = 0x2a,
+	kTzxBlockSetSignalLevel = 0x2b,
+	kTzxBlockTextDescription = 0x30,
+	kTzxBlockMessage = 0x31,
+	kTzxBlockArchiveInfo = 0x32,
+	kTzxBlockHardwareType = 0x33,
+	kTzxBlockEmulationInfo = 0x34,
+	kTzxBlockCustomInfo = 0x35,
+	kTzxBlockSnapshot = 0x40,
+	kTzxBlockKansasCityStandard = 0x4b,
+	kTzxBlockGlue = 0x5a
+};
+
 static bool readBytes(SeekableReadStream &stream, Array<byte> &out, uint32 len) {
 	out.resize(len);
 	return len == 0 || stream.read(out.data(), len) == len;
@@ -227,7 +260,7 @@ static bool parseTzx(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
 			return false;
 
 		switch (id) {
-		case 0x10: { // Standard Speed Data Block
+		case kTzxBlockStandardSpeedData: { // Standard Speed Data Block
 			Array<byte> data;
 			if (!readPayload(stream, data, 4))
 				return false;
@@ -240,15 +273,15 @@ static bool parseTzx(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
 				return false;
 			break;
 		}
-		case 0x11: // Turbo Speed Data Block
+		case kTzxBlockTurboSpeedData: // Turbo Speed Data Block
 			if (!readTzxDataBlock(stream, blocks, id, 0x12, 0x0f))
 				return false;
 			break;
-		case 0x12: // Pure Tone
+		case kTzxBlockPureTone: // Pure Tone
 			if (!readSizedBlock(stream, blocks, id, 4))
 				return false;
 			break;
-		case 0x13: { // Pulse Sequence
+		case kTzxBlockPulseSequence: { // Pulse Sequence
 			byte count = stream.readByte();
 			if (stream.err())
 				return false;
@@ -261,11 +294,11 @@ static bool parseTzx(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
 				return false;
 			break;
 		}
-		case 0x14: // Pure Data Block
+		case kTzxBlockPureData: // Pure Data Block
 			if (!readTzxDataBlock(stream, blocks, id, 0x0a, 0x07))
 				return false;
 			break;
-		case 0x15: { // Direct Recording
+		case kTzxBlockDirectRecording: { // Direct Recording
 			Array<byte> data;
 			if (!readPayload(stream, data, 8))
 				return false;
@@ -278,34 +311,34 @@ static bool parseTzx(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
 				return false;
 			break;
 		}
-		case 0x16: // C64 ROM type data
-		case 0x17: // C64 turbo tape data
+		case kTzxBlockC64RomData: // C64 ROM type data
+		case kTzxBlockC64TurboTapeData: // C64 turbo tape data
 			if (!readLengthPrefixedBlock(stream, blocks, id, 4, 4))
 				return false;
 			break;
-		case 0x18: // CSW recording
-		case 0x19: // Generalized data
+		case kTzxBlockCswRecording: // CSW recording
+		case kTzxBlockGeneralizedData: // Generalized data
 			if (!readLengthPrefixedBlock(stream, blocks, id, 4))
 				return false;
 			break;
-		case 0x20: // Pause
-		case 0x23: // Jump to
-		case 0x24: // Loop start
+		case kTzxBlockPause: // Pause
+		case kTzxBlockJumpTo: // Jump to
+		case kTzxBlockLoopStart: // Loop start
 			if (!readSizedBlock(stream, blocks, id, 2))
 				return false;
 			break;
-		case 0x21: // Group start
-		case 0x30: // Text description
+		case kTzxBlockGroupStart: // Group start
+		case kTzxBlockTextDescription: // Text description
 			if (!readLengthPrefixedBlock(stream, blocks, id, 1))
 				return false;
 			break;
-		case 0x22: // Group end
-		case 0x25: // Loop end
-		case 0x27: // Return from sequence
+		case kTzxBlockGroupEnd: // Group end
+		case kTzxBlockLoopEnd: // Loop end
+		case kTzxBlockReturnFromSequence: // Return from sequence
 			if (!appendBlock(blocks, id, Array<byte>()))
 				return false;
 			break;
-		case 0x26: { // Call sequence
+		case kTzxBlockCallSequence: { // Call sequence
 			uint16 count = stream.readUint16LE();
 			if (stream.err())
 				return false;
@@ -318,20 +351,20 @@ static bool parseTzx(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
 				return false;
 			break;
 		}
-		case 0x28: // Select
-		case 0x32: // Archive info
+		case kTzxBlockSelect: // Select
+		case kTzxBlockArchiveInfo: // Archive info
 			if (!readLengthPrefixedBlock(stream, blocks, id, 2))
 				return false;
 			break;
-		case 0x2a: // Stop the tape (48k)
+		case kTzxBlockStopTape48k: // Stop the tape (48k)
 			if (!readSizedBlock(stream, blocks, id, 4))
 				return false;
 			break;
-		case 0x2b: // Set signal level
+		case kTzxBlockSetSignalLevel: // Set signal level
 			if (!readSizedBlock(stream, blocks, id, 5))
 				return false;
 			break;
-		case 0x31: { // Message
+		case kTzxBlockMessage: { // Message
 			Array<byte> data;
 			if (!readPayload(stream, data, 2))
 				return false;
@@ -343,7 +376,7 @@ static bool parseTzx(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
 				return false;
 			break;
 		}
-		case 0x33: { // Hardware type
+		case kTzxBlockHardwareType: { // Hardware type
 			byte count = stream.readByte();
 			if (stream.err())
 				return false;
@@ -356,11 +389,11 @@ static bool parseTzx(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
 				return false;
 			break;
 		}
-		case 0x34: // Emulation info
+		case kTzxBlockEmulationInfo: // Emulation info
 			if (!readSizedBlock(stream, blocks, id, 8))
 				return false;
 			break;
-		case 0x35: { // Custom info
+		case kTzxBlockCustomInfo: { // Custom info
 			Array<byte> data;
 			if (!readPayload(stream, data, 0x14))
 				return false;
@@ -373,7 +406,7 @@ static bool parseTzx(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
 				return false;
 			break;
 		}
-		case 0x40: { // Snapshot
+		case kTzxBlockSnapshot: { // Snapshot
 			Array<byte> data;
 			if (!readPayload(stream, data, 4))
 				return false;
@@ -386,11 +419,11 @@ static bool parseTzx(SeekableReadStream &stream, SpectrumTapeBlocks &blocks) {
 				return false;
 			break;
 		}
-		case 0x4b: // Kansas City Standard
+		case kTzxBlockKansasCityStandard: // Kansas City Standard
 			if (!readLengthPrefixedBlock(stream, blocks, id, 4))
 				return false;
 			break;
-		case 0x5a: // Glue
+		case kTzxBlockGlue: // Glue
 			if (!readSizedBlock(stream, blocks, id, 9))
 				return false;
 			break;




More information about the Scummvm-git-logs mailing list