[Scummvm-git-logs] scummvm-tools master -> 0ea168367c700e7a7932b866252e7a34f12b1f82

mgerhardy noreply at scummvm.org
Tue Jun 30 09:54:01 UTC 2026


This automated email contains information about 14 new commits which have been
pushed to the 'scummvm-tools' repo located at https://api.github.com/repos/scummvm/scummvm-tools .

Summary:
435b25b3c6 MACS2: extract translations
5f511eeda5 MACS2: fixed demacs opcode handling
347677f432 MACS2: fixed opcode name
724d33e579 MACS2: also extract the object code
c4a54217e4 MACS2: extract item images
87b839042c MACS2: add makefile targets
5e654c9331 MACS2: also extract object strings
c88685afca MACS2: extract more resources from game files
1a9a9c5be9 MACS2: also extract the object code
25ef57a602 MACS2: also allow to hand over the game dir
3d1c0aaed9 MACS2: extract amiga and allow to generate c code
aa253c1d3d MASC2: added missing texts for addOverlayTextEntry
1482504727 MACS2: renamed more objects and fixed changeAnimation
0ea168367c MACS2: fixed unused var warning


Commit: 435b25b3c63df1957ab5573883b83d5615bcdae4
    https://github.com/scummvm/scummvm-tools/commit/435b25b3c63df1957ab5573883b83d5615bcdae4
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: extract translations

Changed paths:
  A engines/macs2/TRANSLATE.md
  A engines/macs2/create_macs2_translation.cpp


diff --git a/engines/macs2/TRANSLATE.md b/engines/macs2/TRANSLATE.md
new file mode 100644
index 00000000..08cb74ce
--- /dev/null
+++ b/engines/macs2/TRANSLATE.md
@@ -0,0 +1,29 @@
+# MACS2 Tools
+
+Tools for the MACS2 engine (Schatz im Silbersee).
+
+## Translation Workflow
+
+The game was originally released in German only. Translations are managed via standard PO files.
+
+### 1. Extract strings to a PO template
+
+```bash
+create_macs2_translation extract RESOURCE.MCS macs2.pot
+```
+
+This produces a `.pot` file with all game strings grouped by dialog context. The format uses `msgctxt` to identify the source (scene or object) and `\n` to separate lines within a dialog unit.
+
+The `\n` separators correspond to individual display lines in the game. Keep the same number of lines as the original.
+
+### 3. Pack into binary
+
+```bash
+create_macs2_translation pack en.po macs2_translation.dat
+```
+
+### 4. Install
+
+Place `macs2_translation.dat` in the game directory alongside `RESOURCE.MCS`. ScummVM will detect the translation and offer the translated language variant.
+
+The file is also distributed via `dists/engine-data/macs2_translation.dat` in the ScummVM source tree.
diff --git a/engines/macs2/create_macs2_translation.cpp b/engines/macs2/create_macs2_translation.cpp
new file mode 100644
index 00000000..f46ea2ae
--- /dev/null
+++ b/engines/macs2/create_macs2_translation.cpp
@@ -0,0 +1,632 @@
+/* ScummVM Tools
+ *
+ * ScummVM Tools 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/>.
+ *
+ */
+
+/*
+ * MACS2 Translation File Creator
+ *
+ * Workflow:
+ *   1. Extract: create_macs2_translation extract <RESOURCE.MCS> <output.pot>
+ *   2. Translate the .po file (Poedit, Weblate, etc.)
+ *   3. Pack:    create_macs2_translation pack <translated.po> <macs2_translation.dat>
+ *
+ * PO format:
+ *   msgctxt "scene:2"  (or "object:42")
+ *   msgid "line1\nline2\nline3"
+ *   msgstr "translated1\ntranslated2\ntranslated3"
+ *
+ * Each msgid groups consecutive strings that form one dialog/description unit.
+ * The \n separates individual lines that the engine displays separately.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+
+static FILE *resFile = nullptr;
+
+static uint32_t readU32(FILE *f) {
+	uint8_t buf[4];
+	fread(buf, 1, 4, f);
+	return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+}
+
+static uint16_t readU16(FILE *f) {
+	uint8_t buf[2];
+	fread(buf, 1, 2, f);
+	return buf[0] | (buf[1] << 8);
+}
+
+static void writeU32(FILE *f, uint32_t v) {
+	uint8_t buf[4] = {(uint8_t)(v & 0xFF), (uint8_t)((v >> 8) & 0xFF),
+	                  (uint8_t)((v >> 16) & 0xFF), (uint8_t)((v >> 24) & 0xFF)};
+	fwrite(buf, 1, 4, f);
+}
+
+static void writeU16(FILE *f, uint16_t v) {
+	uint8_t buf[2] = {(uint8_t)(v & 0xFF), (uint8_t)((v >> 8) & 0xFF)};
+	fwrite(buf, 1, 2, f);
+}
+
+// CP850 to UTF-8
+static std::string cp850ToUtf8(const std::string &s) {
+	static const uint16_t cp850map[128] = {
+		0x00C7,0x00FC,0x00E9,0x00E2,0x00E4,0x00E0,0x00E5,0x00E7,
+		0x00EA,0x00EB,0x00E8,0x00EF,0x00EE,0x00EC,0x00C4,0x00C5,
+		0x00C9,0x00E6,0x00C6,0x00F4,0x00F6,0x00F2,0x00FB,0x00F9,
+		0x00FF,0x00D6,0x00DC,0x00F8,0x00A3,0x00D8,0x00D7,0x0192,
+		0x00E1,0x00ED,0x00F3,0x00FA,0x00F1,0x00D1,0x00AA,0x00BA,
+		0x00BF,0x00AE,0x00AC,0x00BD,0x00BC,0x00A1,0x00AB,0x00BB,
+		0x2591,0x2592,0x2593,0x2502,0x2524,0x00C1,0x00C2,0x00C0,
+		0x00A9,0x2563,0x2551,0x2557,0x255D,0x00A2,0x00A5,0x2510,
+		0x2514,0x2534,0x252C,0x251C,0x2500,0x253C,0x00E3,0x00C3,
+		0x255A,0x2554,0x2569,0x2566,0x2560,0x2550,0x256C,0x00A4,
+		0x00F0,0x00D0,0x00CA,0x00CB,0x00C8,0x0131,0x00CD,0x00CE,
+		0x00CF,0x2518,0x250C,0x2588,0x2584,0x00A6,0x00CC,0x2580,
+		0x00D3,0x00DF,0x00D4,0x00D2,0x00F5,0x00D5,0x00B5,0x00FE,
+		0x00DE,0x00DA,0x00DB,0x00D9,0x00FD,0x00DD,0x00AF,0x00B4,
+		0x00AD,0x00B1,0x2017,0x00BE,0x00B6,0x00A7,0x00F7,0x00B8,
+		0x00B0,0x00A8,0x00B7,0x00B9,0x00B3,0x00B2,0x25A0,0x00A0,
+	};
+	std::string out;
+	for (unsigned char c : s) {
+		if (c < 0x80) {
+			out += (char)c;
+		} else {
+			uint16_t u = cp850map[c - 0x80];
+			if (u < 0x80) {
+				out += (char)u;
+			} else if (u < 0x800) {
+				out += (char)(0xC0 | (u >> 6));
+				out += (char)(0x80 | (u & 0x3F));
+			} else {
+				out += (char)(0xE0 | (u >> 12));
+				out += (char)(0x80 | ((u >> 6) & 0x3F));
+				out += (char)(0x80 | (u & 0x3F));
+			}
+		}
+	}
+	return out;
+}
+
+static std::string decryptString(const uint8_t *data, uint16_t length) {
+	std::string result;
+	for (int i = 1; i <= length; i++) {
+		uint8_t x = (uint8_t)(i * i * 0x0C);
+		uint8_t y = (uint8_t)(data[i - 1] ^ i);
+		uint8_t r = (uint8_t)(x ^ y);
+		result += (char)r;
+	}
+	return result;
+}
+
+static std::vector<std::string> readStringsFromBlob(uint32_t blobOffset) {
+	std::vector<std::string> result;
+	fseek(resFile, blobOffset, SEEK_SET);
+	uint16_t totalSize = readU16(resFile);
+	if (totalSize == 0)
+		return result;
+	std::vector<uint8_t> data(totalSize);
+	fread(data.data(), 1, totalSize, resFile);
+	uint32_t pos = 0;
+	while (pos + 2 <= totalSize) {
+		uint16_t len = data[pos] | (data[pos + 1] << 8);
+		pos += 2;
+		if (len == 0 || pos + len > totalSize)
+			break;
+		result.push_back(decryptString(data.data() + pos, len));
+		pos += len;
+	}
+	return result;
+}
+
+// Compute byte offset -> string index mapping for a blob
+static std::vector<uint32_t> computeStringOffsets(uint32_t blobOffset) {
+	std::vector<uint32_t> offsets; // offsets[stringIndex] = byte offset
+	fseek(resFile, blobOffset, SEEK_SET);
+	uint16_t totalSize = readU16(resFile);
+	if (totalSize == 0)
+		return offsets;
+	std::vector<uint8_t> data(totalSize);
+	fread(data.data(), 1, totalSize, resFile);
+	uint32_t pos = 0;
+	while (pos + 2 <= totalSize) {
+		offsets.push_back(pos);
+		uint16_t len = data[pos] | (data[pos + 1] << 8);
+		pos += 2;
+		if (len == 0 || pos + len > totalSize)
+			break;
+		pos += len;
+	}
+	return offsets;
+}
+
+// A string reference found in a script: (byteOffset, numLines)
+struct StringRef {
+	uint16_t offset;
+	uint16_t numLines;
+};
+
+// Parse a scene/object script to find all string references (offset, numLines)
+static std::vector<StringRef> parseScriptStringRefs(uint32_t scriptDataOffset) {
+	std::vector<StringRef> refs;
+	fseek(resFile, scriptDataOffset, SEEK_SET);
+
+	// Skip 0x80 bytes of special anim offsets
+	fseek(resFile, 0x80, SEEK_CUR);
+	uint16_t scriptSize = readU16(resFile);
+	if (scriptSize == 0)
+		return refs;
+
+	std::vector<uint8_t> script(scriptSize);
+	fread(script.data(), 1, scriptSize, resFile);
+
+	uint32_t pos = 0;
+	while (pos + 1 < scriptSize) {
+		uint8_t opcode = script[pos++];
+		if (opcode == 0x00)
+			continue;
+		if (pos >= scriptSize)
+			break;
+		uint8_t length = script[pos++];
+		uint32_t endPos = pos + length;
+		if (endPos > scriptSize)
+			break;
+
+		// Opcodes that contain string references:
+		// 0x0A (printStringLeft): 2x value(3) + offset(2) + numLines(2) = 10
+		// 0x0D (dialogue): 4x value(3) + offset(2) + numLines(2) = 16
+		// 0x16 (addDialogueChoice): 1x value(3) + offset(2) + numLines(2) = 7
+		// 0x30 (printStringRight): same as 0x0A = 10
+		// 0x3A (overlayText): 3x value(3) + offset(2) + numLines(2) = 13
+
+		if (opcode == 0x0A || opcode == 0x30) {
+			// 2x value(3) + uint16 offset + uint16 numLines = 10
+			if (length >= 10) {
+				uint16_t off = script[pos + 6] | (script[pos + 7] << 8);
+				uint16_t num = script[pos + 8] | (script[pos + 9] << 8);
+				if (num > 0 && num < 200)
+					refs.push_back({off, num});
+			}
+		} else if (opcode == 0x0D) {
+			// 4x value(3) + uint16 offset + uint16 numLines
+			if (length >= 16) {
+				uint16_t off = script[pos + 12] | (script[pos + 13] << 8);
+				uint16_t num = script[pos + 14] | (script[pos + 15] << 8);
+				if (num > 0 && num < 200)
+					refs.push_back({off, num});
+			}
+		} else if (opcode == 0x16) {
+			// 1x value(3) + uint16 offset + uint16 numLines
+			if (length >= 7) {
+				uint16_t off = script[pos + 3] | (script[pos + 4] << 8);
+				uint16_t num = script[pos + 5] | (script[pos + 6] << 8);
+				if (num > 0 && num < 200)
+					refs.push_back({off, num});
+			}
+		} else if (opcode == 0x3A) {
+			// 3x value(3) + uint16 offset + uint16 entryType
+			// Here entryType is numLines=1 always, offset references 1 string
+			if (length >= 13) {
+				uint16_t off = script[pos + 9] | (script[pos + 10] << 8);
+				refs.push_back({off, 1});
+			}
+		}
+
+		pos = endPos;
+	}
+	return refs;
+}
+
+// Group string indices by their dialog references
+// Returns vector of (startIndex, count) pairs, sorted by startIndex
+static std::vector<std::pair<int, int>> groupStrings(
+		const std::vector<uint32_t> &stringOffsets,
+		const std::vector<StringRef> &scriptRefs,
+		int totalStrings) {
+
+	// Map byte offset -> string index
+	std::map<uint32_t, int> offsetToIndex;
+	for (int i = 0; i < (int)stringOffsets.size(); i++)
+		offsetToIndex[stringOffsets[i]] = i;
+
+	// Collect which string indices are the start of a group
+	std::set<int> groupStarts;
+	std::map<int, int> groupSizes; // startIndex -> numLines
+
+	for (const auto &ref : scriptRefs) {
+		auto it = offsetToIndex.find(ref.offset);
+		if (it != offsetToIndex.end()) {
+			int idx = it->second;
+			groupStarts.insert(idx);
+			// Keep the largest numLines for this start
+			if (groupSizes.find(idx) == groupSizes.end() || ref.numLines > (uint16_t)groupSizes[idx])
+				groupSizes[idx] = ref.numLines;
+		}
+	}
+
+	// Build groups. Strings not referenced by any opcode get grouped with
+	// the nearest preceding group start, or form their own single-line group.
+	std::vector<std::pair<int, int>> groups;
+	if (groupStarts.empty()) {
+		// No script refs found - put all strings in one group
+		if (totalStrings > 0)
+			groups.push_back({0, totalStrings});
+		return groups;
+	}
+
+	for (auto it = groupStarts.begin(); it != groupStarts.end(); ++it) {
+		int start = *it;
+		int count = groupSizes[start];
+		if (start + count > totalStrings)
+			count = totalStrings - start;
+		groups.push_back({start, count});
+	}
+
+	// Add any orphan strings (not covered by any group) as single-line entries
+	std::set<int> covered;
+	for (const auto &g : groups)
+		for (int i = g.first; i < g.first + g.second; i++)
+			covered.insert(i);
+
+	for (int i = 0; i < totalStrings; i++) {
+		if (covered.find(i) == covered.end())
+			groups.push_back({i, 1});
+	}
+
+	// Sort by start index
+	std::sort(groups.begin(), groups.end());
+
+	// Remove duplicates/overlaps
+	std::vector<std::pair<int, int>> merged;
+	for (const auto &g : groups) {
+		if (!merged.empty() && g.first < merged.back().first + merged.back().second)
+			continue; // skip overlap
+		merged.push_back(g);
+	}
+	return merged;
+}
+
+static std::string poEscape(const std::string &s) {
+	std::string out;
+	for (char c : s) {
+		if (c == '\\') out += "\\\\";
+		else if (c == '"') out += "\\\"";
+		else if (c == '\n') out += "\\n";
+		else out += c;
+	}
+	return out;
+}
+
+static std::string poUnescape(const std::string &s) {
+	std::string out;
+	for (size_t i = 0; i < s.size(); i++) {
+		if (s[i] == '\\' && i + 1 < s.size()) {
+			if (s[i + 1] == 'n') { out += '\n'; i++; }
+			else if (s[i + 1] == '\\') { out += '\\'; i++; }
+			else if (s[i + 1] == '"') { out += '"'; i++; }
+			else out += s[i];
+		} else {
+			out += s[i];
+		}
+	}
+	return out;
+}
+
+static void writePoEntry(FILE *out, const char *ctx, int startIdx,
+                         const std::vector<std::string> &strings, int offset, int count) {
+	fprintf(out, "msgctxt \"%s:%d\"\n", ctx, startIdx);
+	fprintf(out, "msgid \"\"\n");
+	for (int i = 0; i < count; i++) {
+		std::string line = poEscape(cp850ToUtf8(strings[offset + i]));
+		if (i < count - 1)
+			fprintf(out, "\"%s\\n\"\n", line.c_str());
+		else
+			fprintf(out, "\"%s\"\n", line.c_str());
+	}
+	fprintf(out, "msgstr \"\"\n\n");
+}
+
+static int doExtract(const char *resPath, const char *outPath) {
+	resFile = fopen(resPath, "rb");
+	if (!resFile) {
+		fprintf(stderr, "Error: Cannot open '%s'\n", resPath);
+		return 1;
+	}
+
+	FILE *out = fopen(outPath, "w");
+	if (!out) {
+		fprintf(stderr, "Error: Cannot create '%s'\n", outPath);
+		fclose(resFile);
+		return 1;
+	}
+
+	fprintf(out, "# MACS2 - Schatz im Silbersee translation file\n");
+	fprintf(out, "# Copyright (C) ScummVM Team\n");
+	fprintf(out, "#\n");
+	fprintf(out, "msgid \"\"\n");
+	fprintf(out, "msgstr \"\"\n");
+	fprintf(out, "\"Project-Id-Version: macs2\\n\"\n");
+	fprintf(out, "\"Report-Msgid-Bugs-To: scummvm-devel at lists.scummvm.org\\n\"\n");
+	fprintf(out, "\"MIME-Version: 1.0\\n\"\n");
+	fprintf(out, "\"Content-Type: text/plain; charset=UTF-8\\n\"\n");
+	fprintf(out, "\"Content-Transfer-Encoding: 8bit\\n\"\n\n");
+
+	int totalEntries = 0;
+
+	// Extract scene strings grouped by dialog
+	for (int scene = 1; scene <= 512; scene++) {
+		// Get strings offset
+		uint32_t strOff;
+		fseek(resFile, (uint32_t)scene * 0xC + 0xC + 0x4 - 0x4, SEEK_SET);
+		strOff = readU32(resFile);
+		if (strOff == 0)
+			continue;
+
+		// Get script/data offset
+		fseek(resFile, (uint32_t)scene * 0xC + 0xC + 0x4 - 0x8, SEEK_SET);
+		uint32_t dataOff = readU32(resFile);
+
+		std::vector<std::string> strings = readStringsFromBlob(strOff);
+		if (strings.empty())
+			continue;
+
+		std::vector<uint32_t> offsets = computeStringOffsets(strOff);
+		std::vector<StringRef> refs;
+		if (dataOff != 0)
+			refs = parseScriptStringRefs(dataOff);
+
+		auto groups = groupStrings(offsets, refs, (int)strings.size());
+
+		char ctx[64];
+		snprintf(ctx, sizeof(ctx), "scene:%d", scene);
+		for (const auto &g : groups) {
+			writePoEntry(out, ctx, g.first, strings, g.first, g.second);
+			totalEntries++;
+		}
+	}
+
+	// Extract object strings grouped by dialog
+	for (int obj = 1; obj <= 512; obj++) {
+		// Engine formula: index * 0xC + 0xC + 0x4 + 0x17FC = strings offset address
+		uint32_t strAddr = (uint32_t)obj * 0xC + 0xC + 0x4 + 0x17FC;
+
+		fseek(resFile, strAddr, SEEK_SET);
+		uint32_t strOff = readU32(resFile);
+		if (strOff == 0)
+			continue;
+
+		// Script/data offset is 4 bytes before strings offset in the table
+		fseek(resFile, strAddr - 4, SEEK_SET);
+		uint32_t dataOff = readU32(resFile);
+
+		std::vector<std::string> strings = readStringsFromBlob(strOff);
+		if (strings.empty())
+			continue;
+
+		std::vector<uint32_t> offsets = computeStringOffsets(strOff);
+		std::vector<StringRef> refs;
+		if (dataOff != 0)
+			refs = parseScriptStringRefs(dataOff);
+
+		auto groups = groupStrings(offsets, refs, (int)strings.size());
+
+		char ctx[64];
+		snprintf(ctx, sizeof(ctx), "object:%d", obj);
+		for (const auto &g : groups) {
+			writePoEntry(out, ctx, g.first, strings, g.first, g.second);
+			totalEntries++;
+		}
+	}
+
+	fclose(out);
+	fclose(resFile);
+	printf("Extracted %d dialog entries to %s\n", totalEntries, outPath);
+	return 0;
+}
+
+struct TranslationBlock {
+	uint16_t id;
+	std::vector<std::string> strings;
+};
+
+static int doPack(const char *poPath, const char *outPath) {
+	FILE *in = fopen(poPath, "r");
+	if (!in) {
+		fprintf(stderr, "Error: Cannot open '%s'\n", poPath);
+		return 1;
+	}
+
+	// Parse PO: msgctxt "scene:N:startIdx" or "object:N:startIdx"
+	// msgstr contains \n-separated translated lines
+	std::map<uint16_t, std::map<int, std::vector<std::string>>> sceneStrings;
+	std::map<uint16_t, std::map<int, std::vector<std::string>>> objectStrings;
+
+	char line[8192];
+	bool isScene = false;
+	uint16_t currentId = 0;
+	int currentStartIdx = 0;
+	bool hasCtx = false;
+	bool inMsgstr = false;
+	bool inMsgid = false;
+	std::string currentMsgstr;
+
+	auto flushEntry = [&]() {
+		if (hasCtx && !currentMsgstr.empty()) {
+			std::string text = poUnescape(currentMsgstr);
+			std::vector<std::string> lines;
+			size_t start = 0;
+			while (start < text.size()) {
+				size_t nl = text.find('\n', start);
+				if (nl == std::string::npos) {
+					lines.push_back(text.substr(start));
+					break;
+				}
+				lines.push_back(text.substr(start, nl - start));
+				start = nl + 1;
+			}
+			if (!lines.empty()) {
+				if (isScene)
+					sceneStrings[currentId][currentStartIdx] = lines;
+				else
+					objectStrings[currentId][currentStartIdx] = lines;
+			}
+		}
+		hasCtx = false;
+		currentMsgstr.clear();
+		inMsgstr = false;
+		inMsgid = false;
+	};
+
+	while (fgets(line, sizeof(line), in)) {
+		size_t len = strlen(line);
+		while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+			line[--len] = '\0';
+
+		if (strncmp(line, "msgctxt \"", 9) == 0) {
+			flushEntry();
+			int id = 0, idx = 0;
+			if (sscanf(line + 9, "scene:%d:%d", &id, &idx) == 2) {
+				isScene = true; currentId = (uint16_t)id; currentStartIdx = idx; hasCtx = true;
+			} else if (sscanf(line + 9, "object:%d:%d", &id, &idx) == 2) {
+				isScene = false; currentId = (uint16_t)id; currentStartIdx = idx; hasCtx = true;
+			}
+			continue;
+		}
+		if (strncmp(line, "msgstr ", 7) == 0) {
+			inMsgstr = true; inMsgid = false;
+			char *s = strchr(line + 7, '"');
+			if (s) { s++; char *e = strrchr(s, '"'); if (e) currentMsgstr = std::string(s, e - s); }
+			continue;
+		}
+		if (strncmp(line, "msgid ", 6) == 0) { inMsgid = true; inMsgstr = false; continue; }
+		if (line[0] == '"' && inMsgstr) {
+			char *s = line + 1; char *e = strrchr(s, '"');
+			if (e) currentMsgstr += std::string(s, e - s);
+			continue;
+		}
+		if (line[0] == '"' && inMsgid) { continue; } // skip msgid continuation
+		if (line[0] == '\0') flushEntry();
+	}
+	flushEntry();
+	fclose(in);
+
+	// Build translation blocks - flatten indexed groups into sequential strings
+	std::vector<TranslationBlock> sceneBlocks;
+	for (auto &kv : sceneStrings) {
+		TranslationBlock block;
+		block.id = kv.first;
+		// Find max string index
+		int maxIdx = 0;
+		for (auto &gv : kv.second)
+			for (int i = 0; i < (int)gv.second.size(); i++)
+				if (gv.first + i > maxIdx) maxIdx = gv.first + i;
+		block.strings.resize(maxIdx + 1);
+		for (auto &gv : kv.second)
+			for (int i = 0; i < (int)gv.second.size(); i++)
+				block.strings[gv.first + i] = gv.second[i];
+		sceneBlocks.push_back(block);
+	}
+
+	std::vector<TranslationBlock> objectBlocks;
+	for (auto &kv : objectStrings) {
+		TranslationBlock block;
+		block.id = kv.first;
+		int maxIdx = 0;
+		for (auto &gv : kv.second)
+			for (int i = 0; i < (int)gv.second.size(); i++)
+				if (gv.first + i > maxIdx) maxIdx = gv.first + i;
+		block.strings.resize(maxIdx + 1);
+		for (auto &gv : kv.second)
+			for (int i = 0; i < (int)gv.second.size(); i++)
+				block.strings[gv.first + i] = gv.second[i];
+		objectBlocks.push_back(block);
+	}
+
+	// Write binary DAT
+	FILE *out = fopen(outPath, "wb");
+	if (!out) { fprintf(stderr, "Error: Cannot create '%s'\n", outPath); return 1; }
+
+	fwrite("MCS2", 1, 4, out);
+	writeU16(out, 1);
+	writeU16(out, (uint16_t)sceneBlocks.size());
+	writeU16(out, (uint16_t)objectBlocks.size());
+
+	long indexStart = ftell(out);
+	uint32_t indexSize = ((uint32_t)sceneBlocks.size() + (uint32_t)objectBlocks.size()) * 8;
+	for (uint32_t i = 0; i < indexSize; i++) fputc(0, out);
+
+	std::vector<uint32_t> sceneOffsets;
+	for (const auto &block : sceneBlocks) {
+		sceneOffsets.push_back((uint32_t)ftell(out));
+		for (const auto &s : block.strings) {
+			writeU16(out, (uint16_t)s.size());
+			if (!s.empty()) fwrite(s.data(), 1, s.size(), out);
+		}
+	}
+	std::vector<uint32_t> objectOffsets;
+	for (const auto &block : objectBlocks) {
+		objectOffsets.push_back((uint32_t)ftell(out));
+		for (const auto &s : block.strings) {
+			writeU16(out, (uint16_t)s.size());
+			if (!s.empty()) fwrite(s.data(), 1, s.size(), out);
+		}
+	}
+
+	fseek(out, indexStart, SEEK_SET);
+	for (size_t i = 0; i < sceneBlocks.size(); i++) {
+		writeU16(out, sceneBlocks[i].id);
+		writeU16(out, (uint16_t)sceneBlocks[i].strings.size());
+		writeU32(out, sceneOffsets[i]);
+	}
+	for (size_t i = 0; i < objectBlocks.size(); i++) {
+		writeU16(out, objectBlocks[i].id);
+		writeU16(out, (uint16_t)objectBlocks[i].strings.size());
+		writeU32(out, objectOffsets[i]);
+	}
+
+	fclose(out);
+	printf("Packed %zu scene + %zu object blocks into %s\n",
+	       sceneBlocks.size(), objectBlocks.size(), outPath);
+	return 0;
+}
+
+static void printHelp(const char *bin) {
+	printf("MACS2 Translation File Creator\n\n");
+	printf("  %s extract <RESOURCE.MCS> <macs2.pot>\n", bin);
+	printf("  %s pack <translated.po> <macs2_translation.dat>\n", bin);
+}
+
+int main(int argc, char **argv) {
+	if (argc < 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+		printHelp(argv[0]);
+		return 1;
+	}
+	if (!strcmp(argv[1], "extract")) return doExtract(argv[2], argv[3]);
+	if (!strcmp(argv[1], "pack")) return doPack(argv[2], argv[3]);
+	printHelp(argv[0]);
+	return 1;
+}


Commit: 5f511eeda5067aa87ce50c7f1ce2e20f6cc5bda6
    https://github.com/scummvm/scummvm-tools/commit/5f511eeda5067aa87ce50c7f1ce2e20f6cc5bda6
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: fixed demacs opcode handling

Changed paths:
    engines/macs2/create_macs2_translation.cpp
    engines/macs2/demacs2.cpp


diff --git a/engines/macs2/create_macs2_translation.cpp b/engines/macs2/create_macs2_translation.cpp
index f46ea2ae..54aec097 100644
--- a/engines/macs2/create_macs2_translation.cpp
+++ b/engines/macs2/create_macs2_translation.cpp
@@ -19,7 +19,7 @@
  *
  */
 
-/*
+/**
  * MACS2 Translation File Creator
  *
  * Workflow:
@@ -36,15 +36,15 @@
  * The \n separates individual lines that the engine displays separately.
  */
 
+#include <algorithm>
+#include <map>
+#include <set>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <algorithm>
 #include <string>
 #include <vector>
-#include <map>
-#include <set>
 
 static FILE *resFile = nullptr;
 
@@ -62,7 +62,7 @@ static uint16_t readU16(FILE *f) {
 
 static void writeU32(FILE *f, uint32_t v) {
 	uint8_t buf[4] = {(uint8_t)(v & 0xFF), (uint8_t)((v >> 8) & 0xFF),
-	                  (uint8_t)((v >> 16) & 0xFF), (uint8_t)((v >> 24) & 0xFF)};
+					  (uint8_t)((v >> 16) & 0xFF), (uint8_t)((v >> 24) & 0xFF)};
 	fwrite(buf, 1, 4, f);
 }
 
@@ -74,22 +74,134 @@ static void writeU16(FILE *f, uint16_t v) {
 // CP850 to UTF-8
 static std::string cp850ToUtf8(const std::string &s) {
 	static const uint16_t cp850map[128] = {
-		0x00C7,0x00FC,0x00E9,0x00E2,0x00E4,0x00E0,0x00E5,0x00E7,
-		0x00EA,0x00EB,0x00E8,0x00EF,0x00EE,0x00EC,0x00C4,0x00C5,
-		0x00C9,0x00E6,0x00C6,0x00F4,0x00F6,0x00F2,0x00FB,0x00F9,
-		0x00FF,0x00D6,0x00DC,0x00F8,0x00A3,0x00D8,0x00D7,0x0192,
-		0x00E1,0x00ED,0x00F3,0x00FA,0x00F1,0x00D1,0x00AA,0x00BA,
-		0x00BF,0x00AE,0x00AC,0x00BD,0x00BC,0x00A1,0x00AB,0x00BB,
-		0x2591,0x2592,0x2593,0x2502,0x2524,0x00C1,0x00C2,0x00C0,
-		0x00A9,0x2563,0x2551,0x2557,0x255D,0x00A2,0x00A5,0x2510,
-		0x2514,0x2534,0x252C,0x251C,0x2500,0x253C,0x00E3,0x00C3,
-		0x255A,0x2554,0x2569,0x2566,0x2560,0x2550,0x256C,0x00A4,
-		0x00F0,0x00D0,0x00CA,0x00CB,0x00C8,0x0131,0x00CD,0x00CE,
-		0x00CF,0x2518,0x250C,0x2588,0x2584,0x00A6,0x00CC,0x2580,
-		0x00D3,0x00DF,0x00D4,0x00D2,0x00F5,0x00D5,0x00B5,0x00FE,
-		0x00DE,0x00DA,0x00DB,0x00D9,0x00FD,0x00DD,0x00AF,0x00B4,
-		0x00AD,0x00B1,0x2017,0x00BE,0x00B6,0x00A7,0x00F7,0x00B8,
-		0x00B0,0x00A8,0x00B7,0x00B9,0x00B3,0x00B2,0x25A0,0x00A0,
+		0x00C7,
+		0x00FC,
+		0x00E9,
+		0x00E2,
+		0x00E4,
+		0x00E0,
+		0x00E5,
+		0x00E7,
+		0x00EA,
+		0x00EB,
+		0x00E8,
+		0x00EF,
+		0x00EE,
+		0x00EC,
+		0x00C4,
+		0x00C5,
+		0x00C9,
+		0x00E6,
+		0x00C6,
+		0x00F4,
+		0x00F6,
+		0x00F2,
+		0x00FB,
+		0x00F9,
+		0x00FF,
+		0x00D6,
+		0x00DC,
+		0x00F8,
+		0x00A3,
+		0x00D8,
+		0x00D7,
+		0x0192,
+		0x00E1,
+		0x00ED,
+		0x00F3,
+		0x00FA,
+		0x00F1,
+		0x00D1,
+		0x00AA,
+		0x00BA,
+		0x00BF,
+		0x00AE,
+		0x00AC,
+		0x00BD,
+		0x00BC,
+		0x00A1,
+		0x00AB,
+		0x00BB,
+		0x2591,
+		0x2592,
+		0x2593,
+		0x2502,
+		0x2524,
+		0x00C1,
+		0x00C2,
+		0x00C0,
+		0x00A9,
+		0x2563,
+		0x2551,
+		0x2557,
+		0x255D,
+		0x00A2,
+		0x00A5,
+		0x2510,
+		0x2514,
+		0x2534,
+		0x252C,
+		0x251C,
+		0x2500,
+		0x253C,
+		0x00E3,
+		0x00C3,
+		0x255A,
+		0x2554,
+		0x2569,
+		0x2566,
+		0x2560,
+		0x2550,
+		0x256C,
+		0x00A4,
+		0x00F0,
+		0x00D0,
+		0x00CA,
+		0x00CB,
+		0x00C8,
+		0x0131,
+		0x00CD,
+		0x00CE,
+		0x00CF,
+		0x2518,
+		0x250C,
+		0x2588,
+		0x2584,
+		0x00A6,
+		0x00CC,
+		0x2580,
+		0x00D3,
+		0x00DF,
+		0x00D4,
+		0x00D2,
+		0x00F5,
+		0x00D5,
+		0x00B5,
+		0x00FE,
+		0x00DE,
+		0x00DA,
+		0x00DB,
+		0x00D9,
+		0x00FD,
+		0x00DD,
+		0x00AF,
+		0x00B4,
+		0x00AD,
+		0x00B1,
+		0x2017,
+		0x00BE,
+		0x00B6,
+		0x00A7,
+		0x00F7,
+		0x00B8,
+		0x00B0,
+		0x00A8,
+		0x00B7,
+		0x00B9,
+		0x00B3,
+		0x00B2,
+		0x25A0,
+		0x00A0,
 	};
 	std::string out;
 	for (unsigned char c : s) {
@@ -243,10 +355,10 @@ static std::vector<StringRef> parseScriptStringRefs(uint32_t scriptDataOffset) {
 
 // Group string indices by their dialog references
 // Returns vector of (startIndex, count) pairs, sorted by startIndex
-static std::vector<std::pair<int, int>> groupStrings(
-		const std::vector<uint32_t> &stringOffsets,
-		const std::vector<StringRef> &scriptRefs,
-		int totalStrings) {
+static std::vector<std::pair<int, int> > groupStrings(
+	const std::vector<uint32_t> &stringOffsets,
+	const std::vector<StringRef> &scriptRefs,
+	int totalStrings) {
 
 	// Map byte offset -> string index
 	std::map<uint32_t, int> offsetToIndex;
@@ -270,7 +382,7 @@ static std::vector<std::pair<int, int>> groupStrings(
 
 	// Build groups. Strings not referenced by any opcode get grouped with
 	// the nearest preceding group start, or form their own single-line group.
-	std::vector<std::pair<int, int>> groups;
+	std::vector<std::pair<int, int> > groups;
 	if (groupStarts.empty()) {
 		// No script refs found - put all strings in one group
 		if (totalStrings > 0)
@@ -301,7 +413,7 @@ static std::vector<std::pair<int, int>> groupStrings(
 	std::sort(groups.begin(), groups.end());
 
 	// Remove duplicates/overlaps
-	std::vector<std::pair<int, int>> merged;
+	std::vector<std::pair<int, int> > merged;
 	for (const auto &g : groups) {
 		if (!merged.empty() && g.first < merged.back().first + merged.back().second)
 			continue; // skip overlap
@@ -313,10 +425,14 @@ static std::vector<std::pair<int, int>> groupStrings(
 static std::string poEscape(const std::string &s) {
 	std::string out;
 	for (char c : s) {
-		if (c == '\\') out += "\\\\";
-		else if (c == '"') out += "\\\"";
-		else if (c == '\n') out += "\\n";
-		else out += c;
+		if (c == '\\')
+			out += "\\\\";
+		else if (c == '"')
+			out += "\\\"";
+		else if (c == '\n')
+			out += "\\n";
+		else
+			out += c;
 	}
 	return out;
 }
@@ -325,10 +441,17 @@ static std::string poUnescape(const std::string &s) {
 	std::string out;
 	for (size_t i = 0; i < s.size(); i++) {
 		if (s[i] == '\\' && i + 1 < s.size()) {
-			if (s[i + 1] == 'n') { out += '\n'; i++; }
-			else if (s[i + 1] == '\\') { out += '\\'; i++; }
-			else if (s[i + 1] == '"') { out += '"'; i++; }
-			else out += s[i];
+			if (s[i + 1] == 'n') {
+				out += '\n';
+				i++;
+			} else if (s[i + 1] == '\\') {
+				out += '\\';
+				i++;
+			} else if (s[i + 1] == '"') {
+				out += '"';
+				i++;
+			} else
+				out += s[i];
 		} else {
 			out += s[i];
 		}
@@ -337,7 +460,7 @@ static std::string poUnescape(const std::string &s) {
 }
 
 static void writePoEntry(FILE *out, const char *ctx, int startIdx,
-                         const std::vector<std::string> &strings, int offset, int count) {
+						 const std::vector<std::string> &strings, int offset, int count) {
 	fprintf(out, "msgctxt \"%s:%d\"\n", ctx, startIdx);
 	fprintf(out, "msgid \"\"\n");
 	for (int i = 0; i < count; i++) {
@@ -462,8 +585,8 @@ static int doPack(const char *poPath, const char *outPath) {
 
 	// Parse PO: msgctxt "scene:N:startIdx" or "object:N:startIdx"
 	// msgstr contains \n-separated translated lines
-	std::map<uint16_t, std::map<int, std::vector<std::string>>> sceneStrings;
-	std::map<uint16_t, std::map<int, std::vector<std::string>>> objectStrings;
+	std::map<uint16_t, std::map<int, std::vector<std::string> > > sceneStrings;
+	std::map<uint16_t, std::map<int, std::vector<std::string> > > objectStrings;
 
 	char line[8192];
 	bool isScene = false;
@@ -510,26 +633,47 @@ static int doPack(const char *poPath, const char *outPath) {
 			flushEntry();
 			int id = 0, idx = 0;
 			if (sscanf(line + 9, "scene:%d:%d", &id, &idx) == 2) {
-				isScene = true; currentId = (uint16_t)id; currentStartIdx = idx; hasCtx = true;
+				isScene = true;
+				currentId = (uint16_t)id;
+				currentStartIdx = idx;
+				hasCtx = true;
 			} else if (sscanf(line + 9, "object:%d:%d", &id, &idx) == 2) {
-				isScene = false; currentId = (uint16_t)id; currentStartIdx = idx; hasCtx = true;
+				isScene = false;
+				currentId = (uint16_t)id;
+				currentStartIdx = idx;
+				hasCtx = true;
 			}
 			continue;
 		}
 		if (strncmp(line, "msgstr ", 7) == 0) {
-			inMsgstr = true; inMsgid = false;
+			inMsgstr = true;
+			inMsgid = false;
 			char *s = strchr(line + 7, '"');
-			if (s) { s++; char *e = strrchr(s, '"'); if (e) currentMsgstr = std::string(s, e - s); }
+			if (s) {
+				s++;
+				char *e = strrchr(s, '"');
+				if (e)
+					currentMsgstr = std::string(s, e - s);
+			}
+			continue;
+		}
+		if (strncmp(line, "msgid ", 6) == 0) {
+			inMsgid = true;
+			inMsgstr = false;
 			continue;
 		}
-		if (strncmp(line, "msgid ", 6) == 0) { inMsgid = true; inMsgstr = false; continue; }
 		if (line[0] == '"' && inMsgstr) {
-			char *s = line + 1; char *e = strrchr(s, '"');
-			if (e) currentMsgstr += std::string(s, e - s);
+			char *s = line + 1;
+			char *e = strrchr(s, '"');
+			if (e)
+				currentMsgstr += std::string(s, e - s);
 			continue;
 		}
-		if (line[0] == '"' && inMsgid) { continue; } // skip msgid continuation
-		if (line[0] == '\0') flushEntry();
+		if (line[0] == '"' && inMsgid) {
+			continue;
+		} // skip msgid continuation
+		if (line[0] == '\0')
+			flushEntry();
 	}
 	flushEntry();
 	fclose(in);
@@ -543,7 +687,8 @@ static int doPack(const char *poPath, const char *outPath) {
 		int maxIdx = 0;
 		for (auto &gv : kv.second)
 			for (int i = 0; i < (int)gv.second.size(); i++)
-				if (gv.first + i > maxIdx) maxIdx = gv.first + i;
+				if (gv.first + i > maxIdx)
+					maxIdx = gv.first + i;
 		block.strings.resize(maxIdx + 1);
 		for (auto &gv : kv.second)
 			for (int i = 0; i < (int)gv.second.size(); i++)
@@ -558,7 +703,8 @@ static int doPack(const char *poPath, const char *outPath) {
 		int maxIdx = 0;
 		for (auto &gv : kv.second)
 			for (int i = 0; i < (int)gv.second.size(); i++)
-				if (gv.first + i > maxIdx) maxIdx = gv.first + i;
+				if (gv.first + i > maxIdx)
+					maxIdx = gv.first + i;
 		block.strings.resize(maxIdx + 1);
 		for (auto &gv : kv.second)
 			for (int i = 0; i < (int)gv.second.size(); i++)
@@ -568,7 +714,10 @@ static int doPack(const char *poPath, const char *outPath) {
 
 	// Write binary DAT
 	FILE *out = fopen(outPath, "wb");
-	if (!out) { fprintf(stderr, "Error: Cannot create '%s'\n", outPath); return 1; }
+	if (!out) {
+		fprintf(stderr, "Error: Cannot create '%s'\n", outPath);
+		return 1;
+	}
 
 	fwrite("MCS2", 1, 4, out);
 	writeU16(out, 1);
@@ -577,14 +726,16 @@ static int doPack(const char *poPath, const char *outPath) {
 
 	long indexStart = ftell(out);
 	uint32_t indexSize = ((uint32_t)sceneBlocks.size() + (uint32_t)objectBlocks.size()) * 8;
-	for (uint32_t i = 0; i < indexSize; i++) fputc(0, out);
+	for (uint32_t i = 0; i < indexSize; i++)
+		fputc(0, out);
 
 	std::vector<uint32_t> sceneOffsets;
 	for (const auto &block : sceneBlocks) {
 		sceneOffsets.push_back((uint32_t)ftell(out));
 		for (const auto &s : block.strings) {
 			writeU16(out, (uint16_t)s.size());
-			if (!s.empty()) fwrite(s.data(), 1, s.size(), out);
+			if (!s.empty())
+				fwrite(s.data(), 1, s.size(), out);
 		}
 	}
 	std::vector<uint32_t> objectOffsets;
@@ -592,7 +743,8 @@ static int doPack(const char *poPath, const char *outPath) {
 		objectOffsets.push_back((uint32_t)ftell(out));
 		for (const auto &s : block.strings) {
 			writeU16(out, (uint16_t)s.size());
-			if (!s.empty()) fwrite(s.data(), 1, s.size(), out);
+			if (!s.empty())
+				fwrite(s.data(), 1, s.size(), out);
 		}
 	}
 
@@ -610,7 +762,7 @@ static int doPack(const char *poPath, const char *outPath) {
 
 	fclose(out);
 	printf("Packed %zu scene + %zu object blocks into %s\n",
-	       sceneBlocks.size(), objectBlocks.size(), outPath);
+		   sceneBlocks.size(), objectBlocks.size(), outPath);
 	return 0;
 }
 
@@ -625,8 +777,10 @@ int main(int argc, char **argv) {
 		printHelp(argv[0]);
 		return 1;
 	}
-	if (!strcmp(argv[1], "extract")) return doExtract(argv[2], argv[3]);
-	if (!strcmp(argv[1], "pack")) return doPack(argv[2], argv[3]);
+	if (!strcmp(argv[1], "extract"))
+		return doExtract(argv[2], argv[3]);
+	if (!strcmp(argv[1], "pack"))
+		return doPack(argv[2], argv[3]);
 	printHelp(argv[0]);
 	return 1;
 }
diff --git a/engines/macs2/demacs2.cpp b/engines/macs2/demacs2.cpp
index 4fd9a0db..8979cb35 100644
--- a/engines/macs2/demacs2.cpp
+++ b/engines/macs2/demacs2.cpp
@@ -338,7 +338,8 @@ static std::string decodeParams(uint8_t opcode, uint32_t endPos, int &indent) {
 		indent++;
 		break;
 	}
-	case 0x0A: {
+	case 0x0A:
+	case 0x30: {
 		std::string x = formatValue();
 		std::string y = formatValue();
 		uint16_t strOffset = readWord();
@@ -650,7 +651,6 @@ static std::string decodeParams(uint8_t opcode, uint32_t endPos, int &indent) {
 	case 0x1C:
 	case 0x1D:
 	case 0x28:
-	case 0x30:
 	case 0x36:
 	case 0x37:
 	case 0x39:


Commit: 347677f432929c594252fb0bcbfa1c557aa0852c
    https://github.com/scummvm/scummvm-tools/commit/347677f432929c594252fb0bcbfa1c557aa0852c
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: fixed opcode name

Changed paths:
    engines/macs2/demacs2.cpp


diff --git a/engines/macs2/demacs2.cpp b/engines/macs2/demacs2.cpp
index 8979cb35..2e0b4871 100644
--- a/engines/macs2/demacs2.cpp
+++ b/engines/macs2/demacs2.cpp
@@ -203,7 +203,7 @@ static const char *getOpcodeName(uint8_t opcode) {
 	case 0x30:
 		return "printStringRight";
 	case 0x31:
-		return "setVolume";
+		return "setPaletteDarkness";
 	case 0x32:
 		return "setObjectClickable";
 	case 0x33:


Commit: 724d33e57919db51c8abba32b5e4a36493210630
    https://github.com/scummvm/scummvm-tools/commit/724d33e57919db51c8abba32b5e4a36493210630
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: also extract the object code

Changed paths:
    engines/macs2/demacs2.cpp


diff --git a/engines/macs2/demacs2.cpp b/engines/macs2/demacs2.cpp
index 2e0b4871..9a9c0d2f 100644
--- a/engines/macs2/demacs2.cpp
+++ b/engines/macs2/demacs2.cpp
@@ -32,6 +32,63 @@ static uint8_t *scriptData = nullptr;
 static uint32_t scriptSize = 0;
 static uint32_t pos = 0;
 
+static uint8_t *stringData = nullptr;
+static uint32_t stringDataSize = 0;
+
+static void appendUtf8(std::string &out, uint8_t ch) {
+	if (ch < 0x80) {
+		out += (char)ch;
+		return;
+	}
+	// CP850 to Unicode mapping for 0x80-0xFF
+	static const uint16_t cp850[128] = {
+		0x00C7,0x00FC,0x00E9,0x00E2,0x00E4,0x00E0,0x00E5,0x00E7,
+		0x00EA,0x00EB,0x00E8,0x00EF,0x00EC,0x00ED,0x00C4,0x00C5,
+		0x00C9,0x00E6,0x00C6,0x00F4,0x00F6,0x00F2,0x00FB,0x00F9,
+		0x00FF,0x00D6,0x00DC,0x00F8,0x00A3,0x00D8,0x00D7,0x0192,
+		0x00E1,0x00ED,0x00F3,0x00FA,0x00F1,0x00D1,0x00AA,0x00BA,
+		0x00BF,0x00AE,0x00AC,0x00BD,0x00BC,0x00A1,0x00AB,0x00BB,
+		0x2591,0x2592,0x2593,0x2502,0x2524,0x00C1,0x00C2,0x00C0,
+		0x00A9,0x2563,0x2551,0x2557,0x255D,0x00A2,0x00A5,0x2510,
+		0x2514,0x2534,0x252C,0x251C,0x2500,0x253C,0x00E3,0x00C3,
+		0x255A,0x2554,0x2569,0x2566,0x2560,0x2550,0x256C,0x00A4,
+		0x00F0,0x00D0,0x00CA,0x00CB,0x00C8,0x0131,0x00CD,0x00CE,
+		0x00CF,0x2518,0x250C,0x2588,0x2584,0x00A6,0x00CC,0x2580,
+		0x00D3,0x00DF,0x00D4,0x00D2,0x00F5,0x00D5,0x00B5,0x00FE,
+		0x00DE,0x00DA,0x00DB,0x00D9,0x00FD,0x00DD,0x00AF,0x00B4,
+		0x00AD,0x00B1,0x2017,0x00BE,0x00B6,0x00A7,0x00F7,0x00B8,
+		0x00B0,0x00A8,0x00B7,0x00B9,0x00B3,0x00B2,0x25A0,0x00A0,
+	};
+	uint16_t u = cp850[ch - 0x80];
+	if (u < 0x800) {
+		out += (char)(0xC0 | (u >> 6));
+		out += (char)(0x80 | (u & 0x3F));
+	} else {
+		out += (char)(0xE0 | (u >> 12));
+		out += (char)(0x80 | ((u >> 6) & 0x3F));
+		out += (char)(0x80 | (u & 0x3F));
+	}
+}
+
+static std::string decodeString(uint32_t offset, uint16_t numLines) {
+	if (!stringData || offset >= stringDataSize)
+		return "";
+	uint32_t p = offset;
+	std::string result;
+	for (uint16_t line = 0; line < numLines && p + 2 <= stringDataSize; line++) {
+		uint16_t length = (uint16_t)stringData[p] | ((uint16_t)stringData[p + 1] << 8);
+		p += 2;
+		for (int i = 1; i <= length && p < stringDataSize; i++, p++) {
+			uint8_t x = (uint8_t)(i * i * 0x0C);
+			uint8_t y = (uint8_t)(stringData[p] ^ i);
+			appendUtf8(result, x ^ y);
+		}
+		if (line + 1 < numLines)
+			result += " / ";
+	}
+	return result;
+}
+
 static uint8_t readByte() {
 	if (pos >= scriptSize)
 		return 0;
@@ -296,7 +353,7 @@ struct DecompiledLine {
 static std::string decodeParams(uint8_t opcode, uint32_t endPos, int &indent) {
 	std::string result;
 
-	char buf[256];
+	char buf[1024];
 	switch (opcode) {
 	case 0x01:
 	case 0x02: {
@@ -344,7 +401,11 @@ static std::string decodeParams(uint8_t opcode, uint32_t endPos, int &indent) {
 		std::string y = formatValue();
 		uint16_t strOffset = readWord();
 		uint16_t numLines = readWord();
-		snprintf(buf, sizeof(buf), " pos=(%s,%s) str=%u lines=%u", x.c_str(), y.c_str(), strOffset, numLines);
+		std::string decoded = decodeString(strOffset, numLines);
+		if (!decoded.empty())
+			snprintf(buf, sizeof(buf), " pos=(%s,%s) str=%u lines=%u \"%s\"", x.c_str(), y.c_str(), strOffset, numLines, decoded.c_str());
+		else
+			snprintf(buf, sizeof(buf), " pos=(%s,%s) str=%u lines=%u", x.c_str(), y.c_str(), strOffset, numLines);
 		result = buf;
 		break;
 	}
@@ -372,8 +433,13 @@ static std::string decodeParams(uint8_t opcode, uint32_t endPos, int &indent) {
 		std::string side = formatValue();
 		uint16_t strOffset = readWord();
 		uint16_t numLines = readWord();
-		snprintf(buf, sizeof(buf), " obj=%s pos=(%s,%s) side=%s str=%u lines=%u",
-				 obj.c_str(), x.c_str(), y.c_str(), side.c_str(), strOffset, numLines);
+		std::string decoded = decodeString(strOffset, numLines);
+		if (!decoded.empty())
+			snprintf(buf, sizeof(buf), " obj=%s pos=(%s,%s) side=%s str=%u lines=%u \"%s\"",
+					 obj.c_str(), x.c_str(), y.c_str(), side.c_str(), strOffset, numLines, decoded.c_str());
+		else
+			snprintf(buf, sizeof(buf), " obj=%s pos=(%s,%s) side=%s str=%u lines=%u",
+					 obj.c_str(), x.c_str(), y.c_str(), side.c_str(), strOffset, numLines);
 		result = buf;
 		break;
 	}
@@ -417,7 +483,11 @@ static std::string decodeParams(uint8_t opcode, uint32_t endPos, int &indent) {
 		std::string idx = formatValue();
 		uint16_t strOffset = readWord();
 		uint16_t numLines = readWord();
-		snprintf(buf, sizeof(buf), " idx=%s str=%u lines=%u", idx.c_str(), strOffset, numLines);
+		std::string decoded = decodeString(strOffset, numLines);
+		if (!decoded.empty())
+			snprintf(buf, sizeof(buf), " idx=%s str=%u lines=%u \"%s\"", idx.c_str(), strOffset, numLines, decoded.c_str());
+		else
+			snprintf(buf, sizeof(buf), " idx=%s str=%u lines=%u", idx.c_str(), strOffset, numLines);
 		result = buf;
 		break;
 	}
@@ -745,9 +815,9 @@ static std::string jsonEscape(const std::string &s) {
 	return out;
 }
 
-static void disassembleJson(int sceneIndex) {
+static void disassembleJson(int index, const char *type = "scene") {
 	auto lines = decompileToLines();
-	printf("  {\"scene\": %d, \"size\": %u, \"instructions\": [\n", sceneIndex, scriptSize);
+	printf("  {\"type\": \"%s\", \"index\": %d, \"size\": %u, \"instructions\": [\n", type, index, scriptSize);
 	for (size_t i = 0; i < lines.size(); i++) {
 		const auto &l = lines[i];
 		std::string text = std::string(getOpcodeName(l.opcode)) + l.params;
@@ -761,8 +831,10 @@ static void disassembleJson(int sceneIndex) {
 
 static void printHelp(const char *bin) {
 	printf("MACS2 Script Decompiler\n\n");
-	printf("Usage: %s [--json] <game_data_file> [scene_index]\n\n", bin);
+	printf("Usage: %s [--json] [--objects] <game_data_file> [scene_index]\n\n", bin);
 	printf("  --json          - Output as JSON\n");
+	printf("  --objects       - Also decompile object scripts for the scene(s)\n");
+	printf("                    Objects with scene=0 (inventory/global) are included\n");
 	printf("  game_data_file  - The main game data file\n");
 	printf("  scene_index     - Scene number to decompile (1-based)\n");
 	printf("                    If omitted, decompiles all scenes\n\n");
@@ -801,6 +873,103 @@ static bool loadSceneScript(FILE *f, uint16_t sceneIndex) {
 
 	scriptSize = size;
 	pos = 0;
+
+	// Load scene strings
+	uint32_t strTableOffset = (uint32_t)sceneIndex * 0xC + 0xC + 0x4 - 0x4;
+	fseek(f, strTableOffset, SEEK_SET);
+	uint32_t strDataOffset;
+	if (fread(&strDataOffset, 4, 1, f) == 1 && strDataOffset != 0) {
+		fseek(f, strDataOffset, SEEK_SET);
+		uint16_t strSize;
+		if (fread(&strSize, 2, 1, f) == 1 && strSize > 0) {
+			stringData = (uint8_t *)malloc(strSize);
+			if (stringData) {
+				if (fread(stringData, 1, strSize, f) == strSize)
+					stringDataSize = strSize;
+				else {
+					free(stringData);
+					stringData = nullptr;
+				}
+			}
+		}
+	}
+
+	return true;
+}
+
+// Returns the scene index for an object, or 0 if the object doesn't exist
+static uint16_t getObjectSceneIndex(FILE *f, uint16_t objectIndex) {
+	// Object data table: offset at 0x17F4 + 0xC + 0x4 + objectIndex * 0xC
+	uint32_t addressOffset = 0x17F4 + (0xC + 0x04) + (uint32_t)objectIndex * 0xC;
+	fseek(f, addressOffset, SEEK_SET);
+	uint32_t objectOffset;
+	if (fread(&objectOffset, 4, 1, f) != 1 || objectOffset == 0)
+		return 0;
+	// sceneIndex is at +4 in the object data (after x:u16, y:u16)
+	fseek(f, objectOffset + 4, SEEK_SET);
+	uint16_t sceneIndex;
+	if (fread(&sceneIndex, 2, 1, f) != 1)
+		return 0;
+	return sceneIndex;
+}
+
+static bool loadObjectScript(FILE *f, uint16_t objectIndex) {
+	// Object script table: each entry is 12 bytes, script offset is at +4
+	uint32_t addressOffset = 0x17F8 + (0xC + 0x04) + (uint32_t)objectIndex * 0xC;
+	fseek(f, addressOffset, SEEK_SET);
+
+	uint32_t objectOffset;
+	if (fread(&objectOffset, 4, 1, f) != 1)
+		return false;
+
+	if (objectOffset == 0)
+		return false;
+
+	fseek(f, objectOffset, SEEK_SET);
+
+	// Skip 32 resource offsets (128 bytes)
+	fseek(f, 128, SEEK_CUR);
+
+	uint16_t size;
+	if (fread(&size, 2, 1, f) != 1)
+		return false;
+
+	if (size == 0)
+		return false;
+
+	scriptData = (uint8_t *)malloc(size);
+	if (!scriptData)
+		return false;
+
+	if (fread(scriptData, 1, size, f) != size) {
+		free(scriptData);
+		scriptData = nullptr;
+		return false;
+	}
+
+	scriptSize = size;
+	pos = 0;
+
+	// Load object strings
+	uint32_t strTableOffset = (uint32_t)objectIndex * 0xC + 0xC + 0x4 + 0x17FC;
+	fseek(f, strTableOffset, SEEK_SET);
+	uint32_t strDataOffset;
+	if (fread(&strDataOffset, 4, 1, f) == 1 && strDataOffset != 0) {
+		fseek(f, strDataOffset, SEEK_SET);
+		uint16_t strSize;
+		if (fread(&strSize, 2, 1, f) == 1 && strSize > 0) {
+			stringData = (uint8_t *)malloc(strSize);
+			if (stringData) {
+				if (fread(stringData, 1, strSize, f) == strSize)
+					stringDataSize = strSize;
+				else {
+					free(stringData);
+					stringData = nullptr;
+				}
+			}
+		}
+	}
+
 	return true;
 }
 
@@ -811,9 +980,17 @@ int main(int argc, char **argv) {
 	}
 
 	bool jsonMode = false;
+	bool objectsMode = false;
 	int argIdx = 1;
-	if (!strcmp(argv[argIdx], "--json")) {
-		jsonMode = true;
+	while (argIdx < argc && argv[argIdx][0] == '-') {
+		if (!strcmp(argv[argIdx], "--json")) {
+			jsonMode = true;
+		} else if (!strcmp(argv[argIdx], "--objects")) {
+			objectsMode = true;
+		} else {
+			printHelp(argv[0]);
+			return 1;
+		}
 		argIdx++;
 	}
 
@@ -829,15 +1006,12 @@ int main(int argc, char **argv) {
 	}
 	argIdx++;
 
-	int startScene = -1;
-	int endScene = -1;
+	int startScene = 1;
+	int endScene = 512;
 
 	if (argIdx < argc) {
 		startScene = atoi(argv[argIdx]);
 		endScene = startScene;
-	} else {
-		startScene = 1;
-		endScene = 512;
 	}
 
 	if (jsonMode) {
@@ -848,10 +1022,32 @@ int main(int argc, char **argv) {
 				if (!first)
 					printf(",\n");
 				first = false;
-				disassembleJson(scene);
+				disassembleJson(scene, "scene");
 				free(scriptData);
 				scriptData = nullptr;
 				scriptSize = 0;
+				free(stringData);
+				stringData = nullptr;
+				stringDataSize = 0;
+			}
+			if (objectsMode) {
+				for (int obj = 1; obj <= 0x200; obj++) {
+					uint16_t objScene = getObjectSceneIndex(f, (uint16_t)obj);
+					if (objScene != (uint16_t)scene && objScene != 0)
+						continue;
+					if (loadObjectScript(f, (uint16_t)obj)) {
+						if (!first)
+							printf(",\n");
+						first = false;
+						disassembleJson(obj, objScene == 0 ? "inventory" : "object");
+						free(scriptData);
+						scriptData = nullptr;
+						scriptSize = 0;
+						free(stringData);
+						stringData = nullptr;
+						stringDataSize = 0;
+					}
+				}
 			}
 		}
 		printf("\n]}\n");
@@ -874,6 +1070,54 @@ int main(int argc, char **argv) {
 				free(scriptData);
 				scriptData = nullptr;
 				scriptSize = 0;
+				free(stringData);
+				stringData = nullptr;
+				stringDataSize = 0;
+			}
+			if (objectsMode) {
+				bool hasSceneObjs = false;
+				bool hasInventoryObjs = false;
+				// Scene objects first
+				for (int obj = 1; obj <= 0x200; obj++) {
+					uint16_t objScene = getObjectSceneIndex(f, (uint16_t)obj);
+					if (objScene != (uint16_t)scene)
+						continue;
+					if (!hasSceneObjs) {
+						printf("  [Scene Objects]\n");
+						hasSceneObjs = true;
+					}
+					if (loadObjectScript(f, (uint16_t)obj)) {
+						printf("--- Object 0x%x (size: %u bytes) ---\n", obj, scriptSize);
+						disassemble();
+						printf("\n");
+						free(scriptData);
+						scriptData = nullptr;
+						scriptSize = 0;
+						free(stringData);
+						stringData = nullptr;
+						stringDataSize = 0;
+					}
+				}
+				// Inventory/global objects (scene == 0)
+				for (int obj = 1; obj <= 0x200; obj++) {
+					if (getObjectSceneIndex(f, (uint16_t)obj) != 0)
+						continue;
+					if (!hasInventoryObjs) {
+						printf("  [Inventory/Global Objects]\n");
+						hasInventoryObjs = true;
+					}
+					if (loadObjectScript(f, (uint16_t)obj)) {
+						printf("--- Object 0x%x [inventory] (size: %u bytes) ---\n", obj, scriptSize);
+						disassemble();
+						printf("\n");
+						free(scriptData);
+						scriptData = nullptr;
+						scriptSize = 0;
+						free(stringData);
+						stringData = nullptr;
+						stringDataSize = 0;
+					}
+				}
 			}
 		}
 	}


Commit: c4a54217e410da488b393b74b4cf7511a5e6d960
    https://github.com/scummvm/scummvm-tools/commit/c4a54217e410da488b393b74b4cf7511a5e6d960
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: extract item images

Changed paths:
    engines/macs2/extract_macs2.cpp


diff --git a/engines/macs2/extract_macs2.cpp b/engines/macs2/extract_macs2.cpp
index 9a9b4a1b..1967ba1c 100644
--- a/engines/macs2/extract_macs2.cpp
+++ b/engines/macs2/extract_macs2.cpp
@@ -505,6 +505,138 @@ static void extractSceneData(uint16_t sceneIndex, const char *outDir) {
 	printf("  Wrote %s + 4 map files\n", path);
 }
 
+// Extract inventory icon images for all objects that have one (blob slot 0x13)
+static void extractItems(const char *outDir) {
+	// Get scene 1 palette as fallback for icon rendering
+	uint8_t palette[768] = {};
+	uint32_t bgOffset = getSceneBgOffset(1);
+	if (bgOffset != 0) {
+		fseek(resFile, bgOffset, SEEK_SET);
+		// Skip background RLE image to reach palette
+		for (int y = 0; y < 200; y++) {
+			uint16_t length = readU16(resFile);
+			fseek(resFile, length, SEEK_CUR);
+		}
+		fread(palette, 1, 768, resFile);
+	}
+
+	printf("Extracting inventory items...\n");
+	int count = 0;
+	for (int i = 1; i <= 0x200; i++) {
+		uint32_t addressOffset = 0x17F4 + 0xC + 0x04 + i * 0xC;
+		fseek(resFile, addressOffset, SEEK_SET);
+		uint32_t objectOffset = readU32(resFile);
+		if (objectOffset == 0)
+			continue;
+
+		fseek(resFile, objectOffset, SEEK_SET);
+		// Skip: x(2), y(2), sceneIndex(2), orientation(2), verticalOffsetScale(2)
+		fseek(resFile, 10, SEEK_CUR);
+
+		// Read through 21 blob slots to reach slot 0x13 (index 19, 0-based)
+		std::vector<uint8_t> iconBlob;
+		for (int j = 0; j < 0x15; j++) {
+			fseek(resFile, 2, SEEK_CUR); // animID
+			fseek(resFile, 2, SEEK_CUR); // sourceKey
+			uint32_t dataSize = readU32(resFile);
+			if (j == 0x13) {
+				if (dataSize > 0) {
+					iconBlob.resize(dataSize);
+					fread(iconBlob.data(), 1, dataSize, resFile);
+				}
+			} else {
+				fseek(resFile, dataSize, SEEK_CUR);
+			}
+			fseek(resFile, 4, SEEK_CUR); // speed(2) + mirrorFlag(1) + padding(1)
+		}
+
+		if (iconBlob.empty())
+			continue;
+
+		// Parse the animation blob to get frame 1 data
+		// Blob header: 12 bytes (6 words), then sequence table
+		// Sequence length stored as (len-1) at offset 10
+		if (iconBlob.size() < 12)
+			continue;
+		uint16_t seqLen = (uint16_t)(iconBlob[10] | (iconBlob[11] << 8)) + 1;
+		uint32_t frameTableOffset = 0xB + seqLen; // matches engine: stream.seek(0xB + bp0E)
+		if (frameTableOffset + 2 > iconBlob.size())
+			continue;
+		uint16_t frameCount = iconBlob[frameTableOffset] | (iconBlob[frameTableOffset + 1] << 8);
+		if (frameCount == 0)
+			continue;
+		uint32_t frameDataOffset = frameTableOffset + 2;
+		// Frame data: offsetX(2), offsetY(2), unknown(2), width(2), height(2), pixels(w*h)
+		if (frameDataOffset + 10 > iconBlob.size())
+			continue;
+		// Skip frame header (6 bytes: offsetX, offsetY, unknown) to get width/height/pixels
+		uint32_t p = frameDataOffset + 6;
+		if (p + 4 > iconBlob.size())
+			continue;
+		uint16_t w = iconBlob[p] | (iconBlob[p + 1] << 8);
+		uint16_t h = iconBlob[p + 2] | (iconBlob[p + 3] << 8);
+		p += 4;
+		if (w == 0 || h == 0 || p + (uint32_t)w * h > iconBlob.size())
+			continue;
+
+		// Write BMP
+		char path[512];
+		snprintf(path, sizeof(path), "%s/item_%03d.bmp", outDir, i);
+		FILE *f = fopen(path, "wb");
+		if (!f)
+			continue;
+
+		uint32_t rowStride = (w + 3) & ~3;
+		uint32_t imageSize = rowStride * h;
+		uint32_t paletteSize = 256 * 4;
+		uint32_t dataOff = 14 + 40 + paletteSize;
+		uint32_t fileSize = dataOff + imageSize;
+
+		fputc('B', f);
+		fputc('M', f);
+		uint8_t hdr[12] = {};
+		hdr[0] = fileSize & 0xFF;
+		hdr[1] = (fileSize >> 8) & 0xFF;
+		hdr[2] = (fileSize >> 16) & 0xFF;
+		hdr[3] = (fileSize >> 24) & 0xFF;
+		hdr[8] = dataOff & 0xFF;
+		hdr[9] = (dataOff >> 8) & 0xFF;
+		hdr[10] = (dataOff >> 16) & 0xFF;
+		hdr[11] = (dataOff >> 24) & 0xFF;
+		fwrite(hdr, 1, 12, f);
+
+		uint8_t dib[40] = {};
+		dib[0] = 40;
+		dib[4] = w & 0xFF;
+		dib[5] = (w >> 8) & 0xFF;
+		dib[8] = h & 0xFF;
+		dib[9] = (h >> 8) & 0xFF;
+		dib[12] = 1;
+		dib[14] = 8;
+		fwrite(dib, 1, 40, f);
+
+		for (int c = 0; c < 256; c++) {
+			uint8_t r = (palette[c * 3 + 0] * 259 + 33) >> 6;
+			uint8_t g = (palette[c * 3 + 1] * 259 + 33) >> 6;
+			uint8_t b = (palette[c * 3 + 2] * 259 + 33) >> 6;
+			uint8_t bgra[4] = {b, g, r, 0};
+			fwrite(bgra, 1, 4, f);
+		}
+
+		// BMP rows must be 4-byte aligned
+		uint8_t pad[4] = {0};
+		for (int y = h - 1; y >= 0; y--) {
+			fwrite(iconBlob.data() + p + y * w, 1, w, f);
+			if (rowStride > w)
+				fwrite(pad, 1, rowStride - w, f);
+		}
+		fclose(f);
+		printf("  item %d: %ux%u -> %s\n", i, w, h, path);
+		count++;
+	}
+	printf("Extracted %d inventory item icons.\n", count);
+}
+
 static void printHelp(const char *bin) {
 	printf("MACS2 Resource Extractor\n\n");
 	printf("Usage: %s <mode> <game_data_file> <output_dir> [scene_index]\n\n", bin);
@@ -513,6 +645,7 @@ static void printHelp(const char *bin) {
 	printf("  sounds    - Extract sound/music resource blobs\n");
 	printf("  strings   - Extract and decrypt text strings\n");
 	printf("  scenedata - Extract scene metadata as JSON (pathfinding, hotspots, walk params)\n");
+	printf("  items     - Extract inventory item icons as BMP (with object ID)\n");
 	printf("  all       - Extract everything\n");
 	printf("\n");
 	printf("If scene_index is omitted, extracts from all scenes.\n");
@@ -545,6 +678,11 @@ int main(int argc, char **argv) {
 	bool doSounds = !strcmp(mode, "sounds") || !strcmp(mode, "all");
 	bool doStrings = !strcmp(mode, "strings") || !strcmp(mode, "all");
 	bool doSceneData = !strcmp(mode, "scenedata") || !strcmp(mode, "all");
+	bool doItems = !strcmp(mode, "items") || !strcmp(mode, "all");
+
+	if (doItems) {
+		extractItems(outDir);
+	}
 
 	for (int scene = startScene; scene <= endScene; scene++) {
 		bool hasData = false;


Commit: 87b839042cb462125e3ce7b21f839940e283454b
    https://github.com/scummvm/scummvm-tools/commit/87b839042cb462125e3ce7b21f839940e283454b
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: add makefile targets

Changed paths:
    Makefile
    Makefile.common


diff --git a/Makefile b/Makefile
index 047c4042..2b4428e2 100644
--- a/Makefile
+++ b/Makefile
@@ -109,6 +109,9 @@ endif
 	$(STRIP) descumm$(EXEEXT) -o $(WIN32PATH)/tools/descumm$(EXEEXT)
 	$(STRIP) desword2$(EXEEXT) -o $(WIN32PATH)/tools/desword2$(EXEEXT)
 	$(STRIP) detwine$(EXEEXT) -o $(WIN32PATH)/tools/detwine$(EXEEXT)
+	$(STRIP) demacs2$(EXEEXT) -o $(WIN32PATH)/tools/demacs2$(EXEEXT)
+	$(STRIP) extract_macs2$(EXEEXT) -o $(WIN32PATH)/tools/extract_macs2$(EXEEXT)
+	$(STRIP) create_macs2_translation$(EXEEXT) -o $(WIN32PATH)/tools/create_macs2_translation$(EXEEXT)
 	$(STRIP) extract_mohawk$(EXEEXT) -o $(WIN32PATH)/tools/extract_mohawk$(EXEEXT)
 	$(STRIP) gob_loadcalc$(EXEEXT) -o $(WIN32PATH)/tools/gob_loadcalc$(EXEEXT)
 	$(STRIP) grim_animb2txt$(EXEEXT) -o $(WIN32PATH)/tools/grim_animb2txt$(EXEEXT)
@@ -308,6 +311,9 @@ endif
 	$(STRIP) descumm$(EXEEXT) -o $(AMIGAOSPATH)/descumm$(EXEEXT)
 	$(STRIP) desword2$(EXEEXT) -o $(AMIGAOSPATH)/desword2$(EXEEXT)
 	$(STRIP) detwine$(EXEEXT) -o $(AMIGAOSPATH)/detwine$(EXEEXT)
+	$(STRIP) demacs2$(EXEEXT) -o $(AMIGAOSPATH)/demacs2$(EXEEXT)
+	$(STRIP) extract_macs2$(EXEEXT) -o $(AMIGAOSPATH)/extract_macs2$(EXEEXT)
+	$(STRIP) create_macs2_translation$(EXEEXT) -o $(AMIGAOSPATH)/create_macs2_translation$(EXEEXT)
 	$(STRIP) extract_mohawk$(EXEEXT) -o $(AMIGAOSPATH)/extract_mohawk$(EXEEXT)
 	$(STRIP) extract_ngi$(EXEEXT) -o $(AMIGAOSPATH)/extract_ngi$(EXEEXT)
 	$(STRIP) gob_loadcalc$(EXEEXT) -o $(AMIGAOSPATH)/gob_loadcalc$(EXEEXT)
diff --git a/Makefile.common b/Makefile.common
index b3bfc179..9fa390f1 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -35,6 +35,7 @@ PROGRAMS = \
 	extract_gob_cdi \
 	extract_mohawk \
 	extract_macs2 \
+	create_macs2_translation \
 	extract_ngi \
 	construct_mohawk \
 	msn_convert_mod \
@@ -145,6 +146,9 @@ demacs2_OBJS := \
 extract_macs2_OBJS := \
 	engines/macs2/extract_macs2.o
 
+create_macs2_translation_OBJS := \
+	engines/macs2/create_macs2_translation.o
+
 extract_hadesch_OBJS := \
 	engines/hadesch/extract_hadesch.o \
 	$(UTILS)


Commit: 5e654c9331da374fd3ed955558e412bcd37b136a
    https://github.com/scummvm/scummvm-tools/commit/5e654c9331da374fd3ed955558e412bcd37b136a
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: also extract object strings

Changed paths:
    engines/macs2/extract_macs2.cpp


diff --git a/engines/macs2/extract_macs2.cpp b/engines/macs2/extract_macs2.cpp
index 1967ba1c..bce8f023 100644
--- a/engines/macs2/extract_macs2.cpp
+++ b/engines/macs2/extract_macs2.cpp
@@ -274,6 +274,48 @@ static void extractStrings(uint16_t sceneIndex, const char *outDir) {
 	printf("  Wrote %s (%d strings)\n", path, stringIndex);
 }
 
+// Extract strings for an object
+static void extractObjectStrings(uint16_t objectIndex, const char *outDir) {
+	uint32_t strTableOffset = (uint32_t)objectIndex * 0xC + 0xC + 0x4 + 0x17FC;
+	fseek(resFile, strTableOffset, SEEK_SET);
+	uint32_t strDataOffset = readU32(resFile);
+	if (strDataOffset == 0)
+		return;
+
+	fseek(resFile, strDataOffset, SEEK_SET);
+	uint16_t totalSize = readU16(resFile);
+	if (totalSize == 0)
+		return;
+
+	std::vector<uint8_t> strData(totalSize);
+	fread(strData.data(), 1, totalSize, resFile);
+
+	char path[512];
+	snprintf(path, sizeof(path), "%s/strings_object%03d.txt", outDir, objectIndex);
+	FILE *out = fopen(path, "w");
+	if (!out)
+		return;
+
+	fprintf(out, "; Object %d strings\n", objectIndex);
+	fprintf(out, "; Total data size: %u bytes\n\n", totalSize);
+
+	uint32_t pos = 0;
+	int stringIndex = 0;
+	while (pos + 2 <= totalSize) {
+		uint16_t len = strData[pos] | (strData[pos + 1] << 8);
+		pos += 2;
+		if (len == 0 || pos + len > totalSize)
+			break;
+		std::string decoded = decryptString(strData.data() + pos, len);
+		fprintf(out, "[%d] (offset=%u) %s\n", stringIndex, (unsigned)(pos - 2), decoded.c_str());
+		pos += len;
+		stringIndex++;
+	}
+
+	fclose(out);
+	printf("  Wrote %s (%d strings)\n", path, stringIndex);
+}
+
 static void mkdirp(const char *path) {
 #ifdef _WIN32
 	mkdir(path);
@@ -732,6 +774,12 @@ int main(int argc, char **argv) {
 		}
 	}
 
+	if (doStrings) {
+		for (int obj = 1; obj <= 0x200; obj++) {
+			extractObjectStrings(obj, outDir);
+		}
+	}
+
 	fclose(resFile);
 	return 0;
 }


Commit: c88685afca13bc7cc30749c8e9b5a6bc26b6e789
    https://github.com/scummvm/scummvm-tools/commit/c88685afca13bc7cc30749c8e9b5a6bc26b6e789
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: extract more resources from game files

Changed paths:
    engines/macs2/extract_macs2.cpp


diff --git a/engines/macs2/extract_macs2.cpp b/engines/macs2/extract_macs2.cpp
index bce8f023..363f1c37 100644
--- a/engines/macs2/extract_macs2.cpp
+++ b/engines/macs2/extract_macs2.cpp
@@ -25,6 +25,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <set>
 #include <string>
 #include <vector>
 #ifdef WIN32
@@ -96,6 +97,61 @@ static bool decodeRLEImage(uint32_t offset, uint8_t *pixels) {
 	return true;
 }
 
+// Write a BMP file (8-bit indexed, arbitrary dimensions)
+static void writeBMPEx(const char *path, const uint8_t *pixels, uint16_t w, uint16_t h, const uint8_t *palette) {
+	FILE *f = fopen(path, "wb");
+	if (!f) {
+		fprintf(stderr, "Cannot write %s\n", path);
+		return;
+	}
+
+	uint32_t rowStride = (w + 3) & ~3;
+	uint32_t imageSize = rowStride * h;
+	uint32_t paletteSize = 256 * 4;
+	uint32_t dataOffset = 14 + 40 + paletteSize;
+	uint32_t fileSize = dataOffset + imageSize;
+
+	fputc('B', f);
+	fputc('M', f);
+	uint8_t hdr[12] = {};
+	hdr[0] = fileSize & 0xFF;
+	hdr[1] = (fileSize >> 8) & 0xFF;
+	hdr[2] = (fileSize >> 16) & 0xFF;
+	hdr[3] = (fileSize >> 24) & 0xFF;
+	hdr[8] = dataOffset & 0xFF;
+	hdr[9] = (dataOffset >> 8) & 0xFF;
+	hdr[10] = (dataOffset >> 16) & 0xFF;
+	hdr[11] = (dataOffset >> 24) & 0xFF;
+	fwrite(hdr, 1, 12, f);
+
+	uint8_t dib[40] = {};
+	dib[0] = 40;
+	dib[4] = w & 0xFF;
+	dib[5] = (w >> 8) & 0xFF;
+	dib[8] = h & 0xFF;
+	dib[9] = (h >> 8) & 0xFF;
+	dib[12] = 1;
+	dib[14] = 8;
+	fwrite(dib, 1, 40, f);
+
+	for (int i = 0; i < 256; i++) {
+		uint8_t r = (palette[i * 3 + 0] * 259 + 33) >> 6;
+		uint8_t g = (palette[i * 3 + 1] * 259 + 33) >> 6;
+		uint8_t b = (palette[i * 3 + 2] * 259 + 33) >> 6;
+		uint8_t bgra[4] = {b, g, r, 0};
+		fwrite(bgra, 1, 4, f);
+	}
+
+	uint8_t pad[4] = {0};
+	for (int y = h - 1; y >= 0; y--) {
+		fwrite(pixels + y * w, 1, w, f);
+		if (rowStride > w)
+			fwrite(pad, 1, rowStride - w, f);
+	}
+	fclose(f);
+	printf("  Wrote %s\n", path);
+}
+
 // Write a BMP file (8-bit indexed)
 static void writeBMP(const char *path, const uint8_t *pixels, const uint8_t *palette) {
 	FILE *f = fopen(path, "wb");
@@ -155,6 +211,158 @@ static void writeBMP(const char *path, const uint8_t *pixels, const uint8_t *pal
 // Forward declaration
 static bool decodeRLEMap(FILE *f, uint8_t *pixels);
 
+// Extract cursor/icon images (33 entries at file offset 0x3B10)
+static void extractCursors(const char *outDir) {
+	// Read global palette at 0x3010
+	uint8_t palette[768];
+	fseek(resFile, 0xC + 0x4 + 0x3000, SEEK_SET);
+	fread(palette, 1, 768, resFile);
+
+	fseek(resFile, 0xC + 0x4 + 0x3000 + 0x300 + 0x800, SEEK_SET);
+	printf("Extracting cursors...\n");
+	int count = 0;
+	for (int i = 0; i < 0x21; i++) {
+		uint32_t length = readU32(resFile);
+		if (length == 0)
+			continue;
+		long blobStart = ftell(resFile);
+		fseek(resFile, 2, SEEK_CUR); // skip 2 bytes
+		uint16_t w = readU16(resFile);
+		uint16_t h = readU16(resFile);
+		if (w == 0 || h == 0 || (uint32_t)w * h > length) {
+			fseek(resFile, blobStart + length, SEEK_SET);
+			continue;
+		}
+		std::vector<uint8_t> pixels(w * h);
+		fread(pixels.data(), 1, w * h, resFile);
+
+		char path[512];
+		snprintf(path, sizeof(path), "%s/cursor_%02d.bmp", outDir, i);
+		writeBMPEx(path, pixels.data(), w, h, palette);
+		count++;
+		fseek(resFile, blobStart + length, SEEK_SET);
+	}
+	printf("Extracted %d cursor images.\n", count);
+}
+
+// Render a font's glyphs into an atlas BMP
+static void renderFontAtlas(const char *path, FILE *f, uint16_t glyphCount, const uint8_t *palette) {
+	struct Glyph {
+		uint16_t w, h;
+		std::vector<uint8_t> data;
+	};
+	std::vector<Glyph> glyphs;
+	uint16_t maxH = 0;
+	uint32_t totalW = 0;
+	for (int i = 0; i < glyphCount; i++) {
+		Glyph g;
+		fseek(f, 1, SEEK_CUR); // ascii byte
+		g.w = readU16(f);
+		g.h = readU16(f);
+		if (g.w > 320 || g.h > 200)
+			return;
+		g.data.resize((uint32_t)g.w * g.h);
+		fread(g.data.data(), 1, g.data.size(), f);
+		if (g.h > maxH)
+			maxH = g.h;
+		totalW += g.w + 1;
+		glyphs.push_back(g);
+	}
+	if (glyphs.empty() || totalW == 0)
+		return;
+
+	uint16_t atlasW = (uint16_t)(totalW - 1);
+	std::vector<uint8_t> atlas((uint32_t)atlasW * maxH, 0);
+	uint32_t x = 0;
+	for (const auto &g : glyphs) {
+		for (uint16_t gy = 0; gy < g.h; gy++)
+			for (uint16_t gx = 0; gx < g.w; gx++)
+				atlas[gy * atlasW + x + gx] = g.data[gy * g.w + gx];
+		x += g.w + 1;
+	}
+	writeBMPEx(path, atlas.data(), atlasW, maxH, palette);
+}
+
+// Extract all fonts: global fonts (2) + overlay fonts from scene/object resources
+static void extractFonts(const char *outDir) {
+	// Read global palette
+	uint8_t palette[768];
+	fseek(resFile, 0xC + 0x4 + 0x3000, SEEK_SET);
+	fread(palette, 1, 768, resFile);
+
+	// Seek past cursor images
+	fseek(resFile, 0xC + 0x4 + 0x3000 + 0x300 + 0x800, SEEK_SET);
+	for (int i = 0; i < 0x21; i++) {
+		uint32_t length = readU32(resFile);
+		if (length > 0)
+			fseek(resFile, length, SEEK_CUR);
+	}
+
+	printf("Extracting fonts...\n");
+	int fontIdx = 0;
+
+	// Global Font 1 (dialog font)
+	readU32(resFile); // size field
+	uint16_t glyphCount = readU16(resFile);
+	if (glyphCount > 0 && glyphCount <= 256) {
+		char path[512];
+		snprintf(path, sizeof(path), "%s/font_%d_dialog.bmp", outDir, fontIdx);
+		renderFontAtlas(path, resFile, glyphCount, palette);
+		fontIdx++;
+	}
+
+	// Global Font 2 (save/load panel font)
+	readU32(resFile);
+	glyphCount = readU16(resFile);
+	if (glyphCount > 0 && glyphCount <= 256) {
+		char path[512];
+		snprintf(path, sizeof(path), "%s/font_%d_panel.bmp", outDir, fontIdx);
+		renderFontAtlas(path, resFile, glyphCount, palette);
+		fontIdx++;
+	}
+
+	// Overlay fonts from scene resources (loaded by opcode 0x38)
+	// These are at resourceOffset + 0x10: uint16 glyphCount + glyph data
+	std::set<uint32_t> extractedOffsets;
+	for (int scene = 1; scene <= 512; scene++) {
+		uint32_t dataOffset = getSceneDataOffset(scene);
+		if (dataOffset == 0)
+			continue;
+		fseek(resFile, dataOffset, SEEK_SET);
+		uint32_t resourceTable[32];
+		for (int i = 0; i < 32; i++)
+			resourceTable[i] = readU32(resFile);
+
+		for (int i = 0; i < 32; i++) {
+			if (resourceTable[i] == 0)
+				continue;
+			if (extractedOffsets.count(resourceTable[i]))
+				continue;
+
+			// Check if resource has AHFFFONT magic header (at offset+4, after size field)
+			fseek(resFile, resourceTable[i] + 4, SEEK_SET);
+			uint8_t magic[8];
+			fread(magic, 1, 8, resFile);
+			if (memcmp(magic, "AHFF", 4) != 0 || memcmp(magic + 4, "FONT", 4) != 0)
+				continue;
+
+			// Font data at offset+0x10: uint16 glyphCount + glyph data
+			fseek(resFile, resourceTable[i] + 0x10, SEEK_SET);
+			uint16_t gc = readU16(resFile);
+			if (gc == 0 || gc > 256)
+				continue;
+
+			// Extract font
+			extractedOffsets.insert(resourceTable[i]);
+			char path[512];
+			snprintf(path, sizeof(path), "%s/font_%d_scene%03d_res%02d.bmp", outDir, fontIdx, scene, i + 1);
+			renderFontAtlas(path, resFile, gc, palette);
+			fontIdx++;
+		}
+	}
+	printf("Extracted %d font atlas images.\n", fontIdx);
+}
+
 // Extract background image for a scene
 static void extractImage(uint16_t sceneIndex, const char *outDir) {
 	uint32_t bgOffset = getSceneBgOffset(sceneIndex);
@@ -547,6 +755,111 @@ static void extractSceneData(uint16_t sceneIndex, const char *outDir) {
 	printf("  Wrote %s + 4 map files\n", path);
 }
 
+// Read the map scene offsets table (256 uint32 entries) by walking the file layout
+static bool readMapSceneOffsets(uint32_t offsets[256]) {
+	// File layout: header(0xC) + actorIdx/sceneIdx(4) + sceneTable(0x3000) + palette(0x300) + shading(0x800)
+	fseek(resFile, 0xC + 0x4 + 0x3000 + 0x300 + 0x800, SEEK_SET);
+
+	// Skip 33 image resources (each: uint32 length, then if >0: 2+w(2)+h(2)+w*h bytes)
+	for (int i = 0; i < 0x21; i++) {
+		uint32_t length = readU32(resFile);
+		if (length > 0)
+			fseek(resFile, length, SEEK_CUR);
+	}
+
+	// Skip Font 1: uint32 sizeField + uint16 glyphCount + glyphs (each: 1+2+2+w*h)
+	readU32(resFile); // size field
+	uint16_t glyphCount = readU16(resFile);
+	for (int i = 0; i < glyphCount; i++) {
+		fseek(resFile, 1, SEEK_CUR); // ascii
+		uint16_t w = readU16(resFile);
+		uint16_t h = readU16(resFile);
+		fseek(resFile, (uint32_t)w * h, SEEK_CUR);
+	}
+
+	// Skip Font 2: same structure
+	readU32(resFile);
+	glyphCount = readU16(resFile);
+	for (int i = 0; i < glyphCount; i++) {
+		fseek(resFile, 1, SEEK_CUR);
+		uint16_t w = readU16(resFile);
+		uint16_t h = readU16(resFile);
+		fseek(resFile, (uint32_t)w * h, SEEK_CUR);
+	}
+
+	// Now at map scene offsets: 256 x uint32
+	for (int i = 0; i < 256; i++)
+		offsets[i] = readU32(resFile);
+	return true;
+}
+
+// Extract help/map panel images
+static void extractHelpImages(const char *outDir) {
+	uint32_t offsets[256];
+	if (!readMapSceneOffsets(offsets))
+		return;
+
+	printf("Extracting help/map panel images...\n");
+	int count = 0;
+	for (int i = 0; i < 256; i++) {
+		if (offsets[i] == 0)
+			continue;
+
+		uint8_t pixels[320 * 200];
+		if (!decodeRLEImage(offsets[i], pixels))
+			continue;
+
+		// Palette follows immediately after the image
+		uint8_t palette[768];
+		fread(palette, 1, 768, resFile);
+
+		char path[512];
+		snprintf(path, sizeof(path), "%s/help_%03d.bmp", outDir, i);
+		writeBMP(path, pixels, palette);
+		count++;
+	}
+	printf("Extracted %d help/map panel images.\n", count);
+}
+
+// Extract music data from scene resource tables
+static void extractMusic(const char *outDir) {
+	printf("Extracting music...\n");
+	int count = 0;
+	for (int scene = 1; scene <= 512; scene++) {
+		uint32_t dataOffset = getSceneDataOffset(scene);
+		if (dataOffset == 0)
+			continue;
+
+		fseek(resFile, dataOffset, SEEK_SET);
+		uint32_t resourceTable[32];
+		for (int i = 0; i < 32; i++)
+			resourceTable[i] = readU32(resFile);
+
+		for (int i = 0; i < 32; i++) {
+			if (resourceTable[i] == 0)
+				continue;
+			fseek(resFile, resourceTable[i], SEEK_SET);
+			uint32_t size = readU32(resFile);
+			if (size == 0 || size > 0x100000)
+				continue;
+
+			std::vector<uint8_t> data(size);
+			fread(data.data(), 1, size, resFile);
+
+			char path[512];
+			snprintf(path, sizeof(path), "%s/music_scene%03d_%02d.bin", outDir, scene, i + 1);
+			FILE *out = fopen(path, "wb");
+			if (out) {
+				fwrite(data.data(), 1, size, out);
+				fclose(out);
+				printf("  Wrote %s (%u bytes)\n", path, size);
+				count++;
+			}
+		}
+	}
+	printf("Extracted %d music/sound resources.\n", count);
+}
+
 // Extract inventory icon images for all objects that have one (blob slot 0x13)
 static void extractItems(const char *outDir) {
 	// Get scene 1 palette as fallback for icon rendering
@@ -683,12 +996,14 @@ static void printHelp(const char *bin) {
 	printf("MACS2 Resource Extractor\n\n");
 	printf("Usage: %s <mode> <game_data_file> <output_dir> [scene_index]\n\n", bin);
 	printf("Modes:\n");
-	printf("  images    - Extract background images as BMP files\n");
-	printf("  sounds    - Extract sound/music resource blobs\n");
-	printf("  strings   - Extract and decrypt text strings\n");
-	printf("  scenedata - Extract scene metadata as JSON (pathfinding, hotspots, walk params)\n");
-	printf("  items     - Extract inventory item icons as BMP (with object ID)\n");
-	printf("  all       - Extract everything\n");
+	printf("  images     - Extract background images as BMP files\n");
+	printf("  sounds     - Extract sound/music resource blobs\n");
+	printf("  music      - Extract music resources from scene data\n");
+	printf("  strings    - Extract and decrypt text strings\n");
+	printf("  scenedata  - Extract scene metadata as JSON (pathfinding, hotspots, walk params)\n");
+	printf("  items      - Extract inventory item icons as BMP (with object ID)\n");
+	printf("  helpimages - Extract help/map panel images as BMP\n");
+	printf("  all        - Extract everything\n");
 	printf("\n");
 	printf("If scene_index is omitted, extracts from all scenes.\n");
 }
@@ -718,14 +1033,29 @@ int main(int argc, char **argv) {
 
 	bool doImages = !strcmp(mode, "images") || !strcmp(mode, "all");
 	bool doSounds = !strcmp(mode, "sounds") || !strcmp(mode, "all");
+	bool doMusic = !strcmp(mode, "music") || !strcmp(mode, "all");
 	bool doStrings = !strcmp(mode, "strings") || !strcmp(mode, "all");
 	bool doSceneData = !strcmp(mode, "scenedata") || !strcmp(mode, "all");
 	bool doItems = !strcmp(mode, "items") || !strcmp(mode, "all");
+	bool doHelpImages = !strcmp(mode, "helpimages") || !strcmp(mode, "all");
 
 	if (doItems) {
 		extractItems(outDir);
 	}
 
+	if (doHelpImages) {
+		extractHelpImages(outDir);
+	}
+
+	if (doMusic) {
+		extractMusic(outDir);
+	}
+
+	if (doImages) {
+		extractCursors(outDir);
+		extractFonts(outDir);
+	}
+
 	for (int scene = startScene; scene <= endScene; scene++) {
 		bool hasData = false;
 


Commit: 1a9a9c5be9e399c9165e1389b86d8c79a2afb9a0
    https://github.com/scummvm/scummvm-tools/commit/1a9a9c5be9e399c9165e1389b86d8c79a2afb9a0
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: also extract the object code

Changed paths:
    engines/macs2/demacs2.cpp


diff --git a/engines/macs2/demacs2.cpp b/engines/macs2/demacs2.cpp
index 9a9c0d2f..1a7db6bd 100644
--- a/engines/macs2/demacs2.cpp
+++ b/engines/macs2/demacs2.cpp
@@ -140,6 +140,11 @@ static std::string formatValue() {
 	uint8_t type = readByte();
 	uint16_t val = readWord();
 	if (type == 0x00) {
+		if (val > 0x400 && val <= 0x600) {
+			char buf[32];
+			snprintf(buf, sizeof(buf), "obj_0x%x", val - 0x400);
+			return buf;
+		}
 		char buf[32];
 		snprintf(buf, sizeof(buf), "%u", val);
 		return buf;


Commit: 25ef57a60222fa346f4e91860412cfd5770c7430
    https://github.com/scummvm/scummvm-tools/commit/25ef57a60222fa346f4e91860412cfd5770c7430
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: also allow to hand over the game dir

Changed paths:
    engines/macs2/extract_macs2.cpp


diff --git a/engines/macs2/extract_macs2.cpp b/engines/macs2/extract_macs2.cpp
index 363f1c37..48a96fd0 100644
--- a/engines/macs2/extract_macs2.cpp
+++ b/engines/macs2/extract_macs2.cpp
@@ -1018,11 +1018,27 @@ int main(int argc, char **argv) {
 	const char *resPath = argv[2];
 	const char *outDir = argv[3];
 
-	resFile = fopen(resPath, "rb");
+	// If path is a directory, look for RESOURCE.MCS inside it
+	std::string resolvedPath = resPath;
+	struct stat st;
+	if (stat(resPath, &st) == 0 && S_ISDIR(st.st_mode)) {
+		resolvedPath = std::string(resPath) + "/RESOURCE.MCS";
+	}
+
+	resFile = fopen(resolvedPath.c_str(), "rb");
 	if (!resFile) {
-		fprintf(stderr, "Error: Cannot open '%s'\n", resPath);
+		fprintf(stderr, "Error: Cannot open '%s'\n", resolvedPath.c_str());
+		return 1;
+	}
+
+	// Validate the file header
+	uint8_t magic[12];
+	if (fread(magic, 1, 12, resFile) != 12 || memcmp(magic, "AHFFMACS0100", 12) != 0) {
+		fprintf(stderr, "Error: '%s' is not a valid MACS2 resource file\n", resolvedPath.c_str());
+		fclose(resFile);
 		return 1;
 	}
+	fseek(resFile, 0, SEEK_SET);
 
 	mkdirp(outDir);
 


Commit: 3d1c0aaed964ecc312165b22071f8b96ca9e933b
    https://github.com/scummvm/scummvm-tools/commit/3d1c0aaed964ecc312165b22071f8b96ca9e933b
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: extract amiga and allow to generate c code

Changed paths:
    engines/macs2/demacs2.cpp
    engines/macs2/extract_macs2.cpp


diff --git a/engines/macs2/demacs2.cpp b/engines/macs2/demacs2.cpp
index 1a7db6bd..66b7d21b 100644
--- a/engines/macs2/demacs2.cpp
+++ b/engines/macs2/demacs2.cpp
@@ -35,6 +35,8 @@ static uint32_t pos = 0;
 static uint8_t *stringData = nullptr;
 static uint32_t stringDataSize = 0;
 
+static bool amigaMode = false;
+
 static void appendUtf8(std::string &out, uint8_t ch) {
 	if (ch < 0x80) {
 		out += (char)ch;
@@ -42,22 +44,134 @@ static void appendUtf8(std::string &out, uint8_t ch) {
 	}
 	// CP850 to Unicode mapping for 0x80-0xFF
 	static const uint16_t cp850[128] = {
-		0x00C7,0x00FC,0x00E9,0x00E2,0x00E4,0x00E0,0x00E5,0x00E7,
-		0x00EA,0x00EB,0x00E8,0x00EF,0x00EC,0x00ED,0x00C4,0x00C5,
-		0x00C9,0x00E6,0x00C6,0x00F4,0x00F6,0x00F2,0x00FB,0x00F9,
-		0x00FF,0x00D6,0x00DC,0x00F8,0x00A3,0x00D8,0x00D7,0x0192,
-		0x00E1,0x00ED,0x00F3,0x00FA,0x00F1,0x00D1,0x00AA,0x00BA,
-		0x00BF,0x00AE,0x00AC,0x00BD,0x00BC,0x00A1,0x00AB,0x00BB,
-		0x2591,0x2592,0x2593,0x2502,0x2524,0x00C1,0x00C2,0x00C0,
-		0x00A9,0x2563,0x2551,0x2557,0x255D,0x00A2,0x00A5,0x2510,
-		0x2514,0x2534,0x252C,0x251C,0x2500,0x253C,0x00E3,0x00C3,
-		0x255A,0x2554,0x2569,0x2566,0x2560,0x2550,0x256C,0x00A4,
-		0x00F0,0x00D0,0x00CA,0x00CB,0x00C8,0x0131,0x00CD,0x00CE,
-		0x00CF,0x2518,0x250C,0x2588,0x2584,0x00A6,0x00CC,0x2580,
-		0x00D3,0x00DF,0x00D4,0x00D2,0x00F5,0x00D5,0x00B5,0x00FE,
-		0x00DE,0x00DA,0x00DB,0x00D9,0x00FD,0x00DD,0x00AF,0x00B4,
-		0x00AD,0x00B1,0x2017,0x00BE,0x00B6,0x00A7,0x00F7,0x00B8,
-		0x00B0,0x00A8,0x00B7,0x00B9,0x00B3,0x00B2,0x25A0,0x00A0,
+		0x00C7,
+		0x00FC,
+		0x00E9,
+		0x00E2,
+		0x00E4,
+		0x00E0,
+		0x00E5,
+		0x00E7,
+		0x00EA,
+		0x00EB,
+		0x00E8,
+		0x00EF,
+		0x00EC,
+		0x00ED,
+		0x00C4,
+		0x00C5,
+		0x00C9,
+		0x00E6,
+		0x00C6,
+		0x00F4,
+		0x00F6,
+		0x00F2,
+		0x00FB,
+		0x00F9,
+		0x00FF,
+		0x00D6,
+		0x00DC,
+		0x00F8,
+		0x00A3,
+		0x00D8,
+		0x00D7,
+		0x0192,
+		0x00E1,
+		0x00ED,
+		0x00F3,
+		0x00FA,
+		0x00F1,
+		0x00D1,
+		0x00AA,
+		0x00BA,
+		0x00BF,
+		0x00AE,
+		0x00AC,
+		0x00BD,
+		0x00BC,
+		0x00A1,
+		0x00AB,
+		0x00BB,
+		0x2591,
+		0x2592,
+		0x2593,
+		0x2502,
+		0x2524,
+		0x00C1,
+		0x00C2,
+		0x00C0,
+		0x00A9,
+		0x2563,
+		0x2551,
+		0x2557,
+		0x255D,
+		0x00A2,
+		0x00A5,
+		0x2510,
+		0x2514,
+		0x2534,
+		0x252C,
+		0x251C,
+		0x2500,
+		0x253C,
+		0x00E3,
+		0x00C3,
+		0x255A,
+		0x2554,
+		0x2569,
+		0x2566,
+		0x2560,
+		0x2550,
+		0x256C,
+		0x00A4,
+		0x00F0,
+		0x00D0,
+		0x00CA,
+		0x00CB,
+		0x00C8,
+		0x0131,
+		0x00CD,
+		0x00CE,
+		0x00CF,
+		0x2518,
+		0x250C,
+		0x2588,
+		0x2584,
+		0x00A6,
+		0x00CC,
+		0x2580,
+		0x00D3,
+		0x00DF,
+		0x00D4,
+		0x00D2,
+		0x00F5,
+		0x00D5,
+		0x00B5,
+		0x00FE,
+		0x00DE,
+		0x00DA,
+		0x00DB,
+		0x00D9,
+		0x00FD,
+		0x00DD,
+		0x00AF,
+		0x00B4,
+		0x00AD,
+		0x00B1,
+		0x2017,
+		0x00BE,
+		0x00B6,
+		0x00A7,
+		0x00F7,
+		0x00B8,
+		0x00B0,
+		0x00A8,
+		0x00B7,
+		0x00B9,
+		0x00B3,
+		0x00B2,
+		0x25A0,
+		0x00A0,
 	};
 	uint16_t u = cp850[ch - 0x80];
 	if (u < 0x800) {
@@ -70,7 +184,11 @@ static void appendUtf8(std::string &out, uint8_t ch) {
 	}
 }
 
+static std::string decodeStringAmiga(uint32_t offset, uint16_t numLines);
+
 static std::string decodeString(uint32_t offset, uint16_t numLines) {
+	if (amigaMode)
+		return decodeStringAmiga(offset, numLines);
 	if (!stringData || offset >= stringDataSize)
 		return "";
 	uint32_t p = offset;
@@ -353,6 +471,7 @@ struct DecompiledLine {
 	uint8_t opcode;
 	int indent;
 	std::string params;
+	std::vector<uint8_t> args;
 };
 
 static std::string decodeParams(uint8_t opcode, uint32_t endPos, int &indent) {
@@ -778,7 +897,10 @@ static std::vector<DecompiledLine> decompileToLines() {
 		line.offset = instrAddr;
 		line.opcode = opcode;
 		line.indent = indent;
+		uint32_t argsStart = pos;
 		line.params = decodeParams(opcode, endPos, indent);
+		uint32_t argsEnd = pos;
+		line.args.assign(scriptData + argsStart, scriptData + argsEnd);
 		lines.push_back(line);
 
 		if (pos != endPos)
@@ -826,21 +948,27 @@ static void disassembleJson(int index, const char *type = "scene") {
 	for (size_t i = 0; i < lines.size(); i++) {
 		const auto &l = lines[i];
 		std::string text = std::string(getOpcodeName(l.opcode)) + l.params;
-		printf("    {\"offset\": %u, \"opcode\": %u, \"name\": \"%s\", \"indent\": %d, \"text\": \"%s\"}%s\n",
+		printf("    {\"offset\": %u, \"opcode\": %u, \"name\": \"%s\", \"indent\": %d, \"text\": \"%s\", \"args\": [",
 			   l.offset, l.opcode, jsonEscape(getOpcodeName(l.opcode)).c_str(),
-			   l.indent, jsonEscape(text).c_str(),
-			   (i + 1 < lines.size()) ? "," : "");
+			   l.indent, jsonEscape(text).c_str());
+		for (size_t j = 0; j < l.args.size(); j++) {
+			if (j)
+				printf(",");
+			printf("%u", l.args[j]);
+		}
+		printf("]}%s\n", (i + 1 < lines.size()) ? "," : "");
 	}
 	printf("  ]}");
 }
 
 static void printHelp(const char *bin) {
 	printf("MACS2 Script Decompiler\n\n");
-	printf("Usage: %s [--json] [--objects] <game_data_file> [scene_index]\n\n", bin);
+	printf("Usage: %s [--json] [--cpp] [--objects] <game_data> [scene_index]\n\n", bin);
 	printf("  --json          - Output as JSON\n");
+	printf("  --cpp           - Output as C++ source (navigable via IDE)\n");
 	printf("  --objects       - Also decompile object scripts for the scene(s)\n");
 	printf("                    Objects with scene=0 (inventory/global) are included\n");
-	printf("  game_data_file  - The main game data file\n");
+	printf("  game_data       - RESOURCE.MCS file (DOS) or directory with DataA+Mdir (Amiga)\n");
 	printf("  scene_index     - Scene number to decompile (1-based)\n");
 	printf("                    If omitted, decompiles all scenes\n\n");
 	printf("The decompiled script will be written to stdout.\n");
@@ -978,6 +1106,1071 @@ static bool loadObjectScript(FILE *f, uint16_t objectIndex) {
 	return true;
 }
 
+// ============================================================================
+// Amiga PP20 decompression + MXOO resource loading
+// ============================================================================
+
+#ifndef _WIN32
+#include <dirent.h>
+#include <sys/stat.h>
+#endif
+
+static bool pp20Decompress(const uint8_t *src, uint32_t srcLen, std::vector<uint8_t> &dst) {
+	if (srcLen < 12 || memcmp(src, "PP20", 4) != 0)
+		return false;
+	const uint8_t *eff = src + 4;
+	const uint8_t *packed = src + 8;
+	uint32_t packedLen = srcLen - 12;
+	const uint8_t *trailer = src + srcLen - 4;
+	uint32_t origSize = ((uint32_t)trailer[0] << 16) | ((uint32_t)trailer[1] << 8) | trailer[2];
+	uint8_t bitrot = trailer[3];
+	if (origSize == 0)
+		return false;
+	dst.resize(origSize);
+
+	uint8_t rev[256];
+	for (int a = 0; a < 256; a++) {
+		uint8_t b = (uint8_t)a;
+		b = (uint8_t)(((b & 0x0f) << 4) | ((b >> 4) & 0x0f));
+		b = (uint8_t)(((b & 0x33) << 2) | ((b >> 2) & 0x33));
+		b = (uint8_t)(((b & 0x55) << 1) | ((b >> 1) & 0x55));
+		rev[a] = b;
+	}
+
+	uint32_t outPos = origSize;
+	int32_t inPos = (int32_t)packedLen;
+	uint32_t code = 0, shift = 32;
+
+#define PP_PEEK(x)                                     \
+	while (shift > 32u - (x)) {                        \
+		if (inPos <= 0)                                \
+			goto done;                                 \
+		shift -= 8;                                    \
+		inPos--;                                       \
+		code += (uint32_t)rev[packed[inPos]] << shift; \
+	}
+#define PP_SHIFT(x)                        \
+	do {                                   \
+		shift += (x);                      \
+		code = (code << (x)) & 0xFFFFFFFF; \
+	} while (0)
+
+	PP_PEEK(bitrot);
+	PP_SHIFT(bitrot);
+	while (outPos > 0) {
+		PP_PEEK(1);
+		uint32_t bit = code >> 31;
+		PP_SHIFT(1);
+		if (bit == 0) {
+			PP_PEEK(2);
+			uint32_t length = (code >> 30) + 1;
+			PP_SHIFT(2);
+			if (length == 4) {
+				for (;;) {
+					PP_PEEK(2);
+					uint32_t b2 = code >> 30;
+					PP_SHIFT(2);
+					length += b2;
+					if (b2 != 3)
+						break;
+				}
+			}
+			for (uint32_t i = 0; i < length && outPos > 0; i++) {
+				PP_PEEK(8);
+				outPos--;
+				dst[outPos] = (uint8_t)(code >> 24);
+				PP_SHIFT(8);
+			}
+			if (outPos == 0)
+				break;
+		}
+		PP_PEEK(2);
+		uint32_t cv = code >> 30;
+		PP_SHIFT(2);
+		uint32_t length, nbits;
+		if (cv == 0) {
+			length = 2;
+			nbits = eff[0];
+		} else if (cv == 1) {
+			length = 3;
+			nbits = eff[1];
+		} else if (cv == 2) {
+			length = 4;
+			nbits = eff[2];
+		} else {
+			PP_PEEK(1);
+			uint32_t extra = code >> 31;
+			PP_SHIFT(1);
+			length = 5;
+			nbits = (extra == 0) ? 7 : eff[3];
+		}
+		PP_PEEK(nbits);
+		uint32_t ptr = (code >> (32 - nbits)) + 1;
+		PP_SHIFT(nbits);
+		if (length == 5) {
+			for (;;) {
+				PP_PEEK(3);
+				uint32_t b3 = code >> 29;
+				PP_SHIFT(3);
+				length += b3;
+				if (b3 != 7)
+					break;
+			}
+		}
+		for (uint32_t i = 0; i < length && outPos > 0; i++) {
+			outPos--;
+			dst[outPos] = dst[outPos + ptr];
+		}
+	}
+done:
+#undef PP_PEEK
+#undef PP_SHIFT
+	return outPos == 0;
+}
+
+static uint32_t readU32BE(const uint8_t *p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3]; }
+static uint16_t readU16BE(const uint8_t *p) { return ((uint16_t)p[0] << 8) | p[1]; }
+
+static bool isAmigaDir(const char *path) {
+#ifndef _WIN32
+	struct stat st;
+	if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode))
+		return false;
+	std::string da = std::string(path) + "/DataA";
+	std::string md = std::string(path) + "/Mdir";
+	return stat(da.c_str(), &st) == 0 && stat(md.c_str(), &st) == 0;
+#else
+	return false;
+#endif
+}
+
+// Load script+strings from an MXOO resource blob (decompressed PP20 data)
+static bool loadAmigaMXOO(const uint8_t *data, uint32_t dataLen) {
+	if (dataLen < 12 || memcmp(data, "MXOO", 4) != 0)
+		return false;
+
+	uint32_t scriptOff = readU32BE(data + 4);
+	uint32_t stringOff = readU32BE(data + 8);
+
+	// Load script: at scriptOff there's padding(2) + size_BE(2) + bytecode
+	if (scriptOff + 4 > dataLen)
+		return false;
+	uint16_t sz = readU16BE(data + scriptOff + 2);
+	if (sz == 0 || scriptOff + 4 + sz > dataLen)
+		return false;
+
+	scriptData = (uint8_t *)malloc(sz);
+	memcpy(scriptData, data + scriptOff + 4, sz);
+	scriptSize = sz;
+	pos = 0;
+
+	// Load strings: at stringOff there's padding(2) + totalSize_BE(2) + string entries
+	if (stringOff + 4 <= dataLen) {
+		uint16_t strSz = readU16BE(data + stringOff + 2);
+		if (strSz > 0 && stringOff + 4 + strSz <= dataLen) {
+			// Convert Amiga string format (u16BE length + plaintext) to DOS format (u16LE length + encrypted)
+			// Since the decompiler's decodeString expects DOS encrypted format,
+			// we store raw and override string decoding for Amiga.
+			// Actually: store the raw Amiga string block and handle in decode.
+			stringData = (uint8_t *)malloc(strSz);
+			memcpy(stringData, data + stringOff + 4, strSz);
+			stringDataSize = strSz;
+		}
+	}
+	return true;
+}
+
+// Override string decode for Amiga (plaintext with u16BE lengths)
+
+static std::string decodeStringAmiga(uint32_t offset, uint16_t numLines) {
+	if (!stringData || offset >= stringDataSize)
+		return "";
+	uint32_t p = offset;
+	std::string result;
+	for (uint16_t line = 0; line < numLines && p + 2 <= stringDataSize; line++) {
+		uint16_t length = readU16BE(stringData + p);
+		p += 2;
+		if (length == 0 || p + length > stringDataSize)
+			break;
+		for (uint16_t i = 0; i < length && p < stringDataSize; i++, p++) {
+			appendUtf8(result, stringData[p]);
+		}
+		if (line + 1 < numLines)
+			result += " / ";
+	}
+	return result;
+}
+
+static int runAmiga(const char *dir, bool jsonMode, bool objectsMode) {
+	std::string dataAPath = std::string(dir) + "/DataA";
+
+	FILE *df = fopen(dataAPath.c_str(), "rb");
+	if (!df) {
+		fprintf(stderr, "Cannot open %s\n", dataAPath.c_str());
+		return 1;
+	}
+	fseek(df, 0, SEEK_END);
+	uint32_t fileSize = (uint32_t)ftell(df);
+	fseek(df, 0, SEEK_SET);
+
+	// Read MXMF header
+	uint8_t hdr[14];
+	fread(hdr, 1, 14, df);
+	if (memcmp(hdr, "MXMF", 4) != 0) {
+		fprintf(stderr, "Not MXMF\n");
+		fclose(df);
+		return 1;
+	}
+	uint16_t totalRes = readU16BE(hdr + 8);
+	uint32_t firstBlockSize = readU32BE(hdr + 10);
+
+	amigaMode = true;
+
+	if (!jsonMode) {
+		printf("; MACS2 Amiga Script Decompiler\n");
+		printf("; Scripts are byte-identical to DOS version\n");
+		printf("; Strings are plaintext (not encrypted)\n;\n\n");
+	} else {
+		printf("{\"resources\": [\n");
+	}
+
+	// Iterate all resources sequentially
+	fseek(df, 14 + firstBlockSize, SEEK_SET);
+	bool first = true;
+	int resIdx = 0;
+	for (uint16_t i = 0; i < totalRes - 1; i++) {
+		uint8_t ehdr[8];
+		if (fread(ehdr, 1, 8, df) != 8)
+			break;
+		char eType[3] = {(char)ehdr[0], (char)ehdr[1], '\0'};
+		uint16_t eId = readU16BE(ehdr + 2);
+		uint32_t eCompSize = readU32BE(ehdr + 4);
+		if (eCompSize == 0 || eCompSize > fileSize)
+			break;
+
+		std::vector<uint8_t> payload(eCompSize);
+		if (fread(payload.data(), 1, eCompSize, df) != eCompSize)
+			break;
+
+		// Only process OO (object) resources that contain PP20
+		if (memcmp(payload.data(), "PP20", 4) != 0)
+			continue;
+		// Skip non-OO types (MM=music, OS=sound, FF=font)
+		if (strcmp(eType, "OO") != 0 && strcmp(eType, "FF") != 0)
+			continue;
+
+		std::vector<uint8_t> dec;
+		if (!pp20Decompress(payload.data(), eCompSize, dec))
+			continue;
+		if (dec.size() < 12 || memcmp(dec.data(), "MXOO", 4) != 0)
+			continue;
+
+		// Check if this MXOO has a script (scriptOff != 0 and has valid script size)
+		uint32_t scriptOff = readU32BE(dec.data() + 4);
+		if (scriptOff + 4 > dec.size())
+			continue;
+		uint16_t sz = readU16BE(dec.data() + scriptOff + 2);
+		if (sz == 0)
+			continue;
+
+		if (loadAmigaMXOO(dec.data(), (uint32_t)dec.size())) {
+			if (jsonMode) {
+				if (!first)
+					printf(",\n");
+				first = false;
+				disassembleJson(eId, eType);
+			} else {
+				printf("=== %s_%04d (size: %u bytes) ===\n", eType, eId, scriptSize);
+				disassemble();
+				printf("\n");
+			}
+			free(scriptData);
+			scriptData = nullptr;
+			scriptSize = 0;
+			free(stringData);
+			stringData = nullptr;
+			stringDataSize = 0;
+		}
+		resIdx++;
+	}
+
+	if (jsonMode)
+		printf("\n]}\n");
+	fclose(df);
+	return 0;
+}
+
+// ============================================================================
+// C source output mode
+// ============================================================================
+
+#include <set>
+
+static std::set<uint16_t> usedObjects;
+static std::set<uint16_t> usedVars;
+static uint16_t maxVarIdx = 0;
+
+static const char *getVarName(uint16_t idx) {
+	switch (idx) {
+	case 3:
+		return "VAR_MUSIC_SLOT";
+	case 10:
+		return "VAR_KNIFE_FIGHT_DONE";
+	case 11:
+		return "VAR_CHAPTER";
+	case 12:
+		return "VAR_SCENE_PROGRESS";
+	case 13:
+		return "VAR_TEMP_X";
+	case 14:
+		return "VAR_BGANIM_TOGGLED";
+	case 15:
+		return "VAR_TALKED_TO_NPC";
+	case 16:
+		return "VAR_HAS_ITEM";
+	case 18:
+		return "VAR_HAS_PLOT_ITEM";
+	case 19:
+		return "VAR_MET_CHARACTER";
+	case 22:
+		return "VAR_CUTSCENE_TRIGGERED";
+	case 51:
+		return "VAR_KNIFE_SEPARATED";
+	case 263:
+		return "VAR_DIALOGUE_CHOICE";
+	default:
+		return nullptr;
+	}
+}
+
+static std::string formatVarAccess(uint16_t idx) {
+	const char *name = getVarName(idx);
+	if (name)
+		return std::string("_vars[") + name + "]";
+	char buf[32];
+	snprintf(buf, sizeof(buf), "_vars[%u]", idx);
+	return buf;
+}
+
+static const char *getObjectName(uint16_t idx) {
+	// Full game object names from Macs2::GameObjects::init()
+	// Characters/NPCs
+	switch (idx) {
+	case 1:
+		return "OBJ_PLAYER";
+	case 2:
+		return "OBJ_CAPTAIN";
+	case 3:
+		return "OBJ_AUNT";
+	case 6:
+		return "OBJ_CORNEL";
+	case 7:
+		return "OBJ_BEAR";
+	case 9:
+		return "OBJ_WOLF";
+	case 12:
+		return "OBJ_INDIAN";
+	case 13:
+		return "OBJ_PATTERSON";
+	case 15:
+		return "OBJ_ROLLINS";
+	case 19:
+		return "OBJ_HILTON";
+	// Items (full game, indices from _objectNames)
+	case 0x08:
+		return "OBJ_BOARD";
+	case 0x0E:
+		return "OBJ_BUCKET_WATER";
+	case 0x10:
+		return "OBJ_BARREL";
+	case 0x11:
+		return "OBJ_BOWIE_KNIFE";
+	case 0x14:
+		return "OBJ_BUCKET_WATER2";
+	case 0x17:
+		return "OBJ_HATBOX_OPEN";
+	case 0x18:
+		return "OBJ_HAT";
+	case 0x19:
+		return "OBJ_HATBOX_CLOSED";
+	case 0x1A:
+		return "OBJ_METAL_BUCKET";
+	case 0x1B:
+		return "OBJ_FIRE_POKER";
+	case 0x1C:
+		return "OBJ_POT_HOLDER";
+	case 0x1D:
+		return "OBJ_BOARD_WRAPPED";
+	case 0x1E:
+		return "OBJ_COAL_SHOVEL";
+	case 0x20:
+		return "OBJ_COCKROACH";
+	case 0x22:
+		return "OBJ_CUP_FULL";
+	case 0x23:
+		return "OBJ_CUP_EMPTY";
+	case 0x24:
+		return "OBJ_AXE";
+	case 0x25:
+		return "OBJ_AXE_BLADE";
+	case 0x26:
+		return "OBJ_AXE_HANDLE";
+	case 0x28:
+		return "OBJ_LETTER";
+	case 0x29:
+		return "OBJ_BREAD_MOLDY";
+	case 0x2A:
+		return "OBJ_BREAD_STALE";
+	case 0x2B:
+		return "OBJ_ENVELOPE_OPEN";
+	case 0x2C:
+		return "OBJ_ENVELOPE_SEALED";
+	case 0x2D:
+		return "OBJ_RACCOON_FUR";
+	case 0x2E:
+		return "OBJ_WHISKY_GLASS";
+	case 0x2F:
+		return "OBJ_LEATHER_BELT";
+	case 0x30:
+		return "OBJ_POKER";
+	case 0x34:
+		return "OBJ_BOARD_SOLID";
+	case 0x36:
+		return "OBJ_BIRDCAGE_BIRD";
+	case 0x37:
+		return "OBJ_BIRDCAGE_BROKEN";
+	case 0x38:
+		return "OBJ_BIRDCAGE_EMPTY";
+	case 0x3A:
+		return "OBJ_MAP";
+	case 0x3B:
+		return "OBJ_CANDLE";
+	case 0x3C:
+		return "OBJ_PEBBLES";
+	case 0x3D:
+		return "OBJ_SUITCASE_DYNAMITE";
+	case 0x3E:
+		return "OBJ_BANKNOTES";
+	case 0x3F:
+		return "OBJ_LEATHER_POUCH";
+	case 0x40:
+		return "OBJ_SLINGSHOT";
+	case 0x41:
+		return "OBJ_FIRECRACKERS";
+	case 0x42:
+		return "OBJ_SUITCASE_OPEN";
+	case 0x43:
+		return "OBJ_SUITCASE_CLOSED";
+	case 0x44:
+		return "OBJ_PAPER";
+	case 0x47:
+		return "OBJ_KNIFE_RUSTY";
+	case 0x4B:
+		return "OBJ_REEDS_DRY";
+	case 0x4C:
+		return "OBJ_REEDS";
+	case 0x4F:
+		return "OBJ_LIQUOR_BOTTLE";
+	case 0x51:
+		return "OBJ_CLOTH_BAG";
+	case 0x52:
+		return "OBJ_CLOTH_BAG_MARBLES";
+	case 0x53:
+		return "OBJ_BELLOWS";
+	case 0x54:
+		return "OBJ_FIREWOOD";
+	case 0x55:
+		return "OBJ_BREAD_SLICED";
+	case 0x56:
+		return "OBJ_BREAD_KNIFE";
+	case 0x62:
+		return "OBJ_PICKAXE";
+	case 0x6B:
+		return "OBJ_CORKSCREW";
+	case 0x6F:
+		return "OBJ_MARBLES";
+	case 0x70:
+		return "OBJ_MUSKETS";
+	case 0x71:
+		return "OBJ_WOODEN_STAKE";
+	case 0x74:
+		return "OBJ_SHOVEL";
+	case 0x78:
+		return "OBJ_BRASS_KEY";
+	case 0x7E:
+		return "OBJ_GUNPOWDER";
+	case 0x7F:
+		return "OBJ_SULPHUR";
+	case 0x80:
+		return "OBJ_HEMP_ROPE";
+	case 0x82:
+		return "OBJ_ROPES_TIED";
+	case 0x84:
+		return "OBJ_SPATULA";
+	case 0x86:
+		return "OBJ_MATCH";
+	case 0x94:
+		return "OBJ_MINE_CART";
+	case 0x99:
+		return "OBJ_CROWBAR";
+	case 0x9B:
+		return "OBJ_DYNAMITE";
+	case 0x9C:
+		return "OBJ_TORCH_LIT";
+	case 0x9D:
+		return "OBJ_TORCH";
+	case 0xA1:
+		return "OBJ_PULLEY";
+	case 0xA2:
+		return "OBJ_HOOK";
+	case 0xA3:
+		return "OBJ_HOOK_ROPE";
+	case 0xAE:
+		return "OBJ_HEMP_ROPE2";
+	case 0xB2:
+		return "OBJ_SCREWDRIVER";
+	case 0xB3:
+		return "OBJ_IRON_BAR";
+	case 0xB4:
+		return "OBJ_TOMAHAWK";
+	default:
+		return nullptr;
+	}
+}
+
+static std::string formatValueC() {
+	uint8_t type = readByte();
+	uint16_t val = readWord();
+	char buf[64];
+	if (type == 0x00) {
+		if (val > 0x400 && val <= 0x600) {
+			uint16_t idx = val - 0x400;
+			usedObjects.insert(idx);
+			const char *name = getObjectName(idx);
+			if (name)
+				return name;
+			snprintf(buf, sizeof(buf), "_objects[%u]", idx);
+			return buf;
+		}
+		snprintf(buf, sizeof(buf), "%d", (int)(int16_t)val);
+		return buf;
+	} else if (type == 0xFF) {
+		const char *name = getSpecialName(val);
+		if (name)
+			return std::string("_rt.") + name;
+		if (val >= 0x0E && val <= 0x22) {
+			snprintf(buf, sizeof(buf), "%u", val - 0x0D);
+			return buf;
+		}
+		switch (val) {
+		case 0x06:
+			return "TRUE";
+		case 0x07:
+			return "FALSE";
+		case 0x08:
+			return "FADE_CUT";
+		case 0x09:
+			return "SIDE_LEFT";
+		case 0x0A:
+			return "SIDE_RIGHT";
+		case 0x0C:
+			return "LOOP";
+		}
+		snprintf(buf, sizeof(buf), "_rt.special[0x%02x]", val);
+		return buf;
+	}
+	usedVars.insert(val);
+	if (val > maxVarIdx)
+		maxVarIdx = val;
+	return formatVarAccess(val);
+}
+
+static std::string decodeCLine(uint8_t opcode, uint32_t endPos, int &indent) {
+	char buf[1024];
+	switch (opcode) {
+	case 0x01: {
+		readByte();
+		uint16_t v = readWord();
+		usedVars.insert(v);
+		if (v > maxVarIdx)
+			maxVarIdx = v;
+		std::string val = formatValueC();
+		std::string dest = formatVarAccess(v);
+		snprintf(buf, sizeof(buf), "%s = %s;", dest.c_str(), val.c_str());
+		return buf;
+	}
+	case 0x02: {
+		readByte();
+		uint16_t v = readWord();
+		usedVars.insert(v);
+		if (v > maxVarIdx)
+			maxVarIdx = v;
+		std::string val = formatValueC();
+		std::string dest = formatVarAccess(v);
+		snprintf(buf, sizeof(buf), "%s |= %s;", dest.c_str(), val.c_str());
+		return buf;
+	}
+	case 0x03: {
+		std::string val = formatValueC();
+		snprintf(buf, sizeof(buf), "if (!%s) {", val.c_str());
+		indent++;
+		return buf;
+	}
+	case 0x04: {
+		std::string val = formatValueC();
+		snprintf(buf, sizeof(buf), "if (%s) {", val.c_str());
+		indent++;
+		return buf;
+	}
+	case 0x05: {
+		uint8_t op = readByte();
+		std::string a = formatValueC();
+		std::string b = formatValueC();
+		snprintf(buf, sizeof(buf), "if (%s %s %s) {", a.c_str(), cmpOpName(op), b.c_str());
+		indent++;
+		return buf;
+	}
+	case 0x06: {
+		uint8_t sub = readByte();
+		std::string i = formatValueC();
+		std::string a = formatValueC();
+		std::string b = formatValueC();
+		snprintf(buf, sizeof(buf), "if (%sifInteraction(%s, %s, %s)) {", sub == 2 ? "!" : "", i.c_str(), a.c_str(), b.c_str());
+		indent++;
+		return buf;
+	}
+	case 0x07:
+		return "}";
+	case 0x08:
+		indent++;
+		return "} else {";
+	case 0x0A:
+	case 0x30: {
+		std::string x = formatValueC();
+		std::string y = formatValueC();
+		uint16_t s = readWord();
+		uint16_t n = readWord();
+		std::string decoded = decodeString(s, n);
+		std::string result2 = std::string(opcode == 0x0A ? "printStringLeft(" : "printStringRight(") + x + ", " + y + ", " + std::to_string(s) + ", " + std::to_string(n) + ");";
+		if (!decoded.empty())
+			result2 += " // \"" + decoded + "\"";
+		return result2;
+	}
+	case 0x0B: {
+		std::string o = formatValueC();
+		std::string s = formatValueC();
+		std::string x = formatValueC();
+		std::string y = formatValueC();
+		if (s == "0" && x == "0" && y == "0")
+			snprintf(buf, sizeof(buf), "removeFromScene(%s);", o.c_str());
+		else
+			snprintf(buf, sizeof(buf), "moveObject(%s, %s, %s, %s);", o.c_str(), s.c_str(), x.c_str(), y.c_str());
+		return buf;
+	}
+	case 0x0C: {
+		std::string s = formatValueC();
+		std::string m = formatValueC();
+		std::string sp = formatValueC();
+		snprintf(buf, sizeof(buf), "changeScene(%s, %s, %s);", s.c_str(), m.c_str(), sp.c_str());
+		return buf;
+	}
+	case 0x0D: {
+		std::string o = formatValueC();
+		std::string x = formatValueC();
+		std::string y = formatValueC();
+		std::string s = formatValueC();
+		uint16_t so = readWord();
+		uint16_t n = readWord();
+		std::string decoded = decodeString(so, n);
+		std::string result2 = std::string("showDialogue(") + o + ", " + x + ", " + y + ", " + s + ", " + std::to_string(so) + ", " + std::to_string(n) + ");";
+		if (!decoded.empty())
+			result2 += " // \"" + decoded + "\"";
+		return result2;
+	}
+	case 0x0E:
+		return "changeAnimation();";
+	case 0x0F: {
+		std::string v = formatValueC();
+		snprintf(buf, sizeof(buf), "frameWait(%s);", v.c_str());
+		return buf;
+	}
+	case 0x10: {
+		std::string o = formatValueC();
+		std::string x = formatValueC();
+		std::string y = formatValueC();
+		snprintf(buf, sizeof(buf), "walkToPosition(%s, %s, %s);", o.c_str(), x.c_str(), y.c_str());
+		return buf;
+	}
+	case 0x11: {
+		std::string o = formatValueC();
+		snprintf(buf, sizeof(buf), "waitForWalk(%s);", o.c_str());
+		return buf;
+	}
+	case 0x12: {
+		std::string a = formatValueC();
+		std::string b = formatValueC();
+		std::string c = formatValueC();
+		snprintf(buf, sizeof(buf), "setPathfinding(%s, %s, %s);", a.c_str(), b.c_str(), c.c_str());
+		return buf;
+	}
+	case 0x13:
+		return "skipUntil14();";
+	case 0x14: {
+		uint16_t w = readWord();
+		snprintf(buf, sizeof(buf), "skipWord(0x%04x);", w);
+		return buf;
+	}
+	case 0x15:
+		return "clearDialogueChoices();";
+	case 0x16: {
+		std::string i = formatValueC();
+		uint16_t s = readWord();
+		uint16_t n = readWord();
+		std::string decoded = decodeString(s, n);
+		std::string result2 = std::string("addDialogueChoice(") + i + ", " + std::to_string(s) + ", " + std::to_string(n) + ");";
+		if (!decoded.empty())
+			result2 += " // \"" + decoded + "\"";
+		return result2;
+	}
+	case 0x17: {
+		std::string o = formatValueC();
+		std::string x = formatValueC();
+		std::string y = formatValueC();
+		std::string s = formatValueC();
+		snprintf(buf, sizeof(buf), "showDialogueChoice(%s, %s, %s, %s);", o.c_str(), x.c_str(), y.c_str(), s.c_str());
+		return buf;
+	}
+	case 0x18:
+		return "dismissPanel();";
+	case 0x19: {
+		std::string a = formatValueC();
+		std::string o = formatValueC();
+		snprintf(buf, sizeof(buf), "walkToAndPickup(%s, %s);", a.c_str(), o.c_str());
+		return buf;
+	}
+	case 0x1A: {
+		std::string o = formatValueC();
+		std::string s = formatValueC();
+		std::string e = formatValueC();
+		snprintf(buf, sizeof(buf), "setPickupFrames(%s, %s, %s);", o.c_str(), s.c_str(), e.c_str());
+		return buf;
+	}
+	case 0x1B: {
+		std::string o = formatValueC();
+		std::string s = formatValueC();
+		std::string sp = formatValueC();
+		snprintf(buf, sizeof(buf), "setupObject(%s, %s, %s);", o.c_str(), s.c_str(), sp.c_str());
+		return buf;
+	}
+	case 0x1C:
+		return "setSkippable();";
+	case 0x1D:
+		return "clearSkippable();";
+	case 0x1E: {
+		std::string o = formatValueC();
+		std::string s = formatValueC();
+		std::string f = formatValueC();
+		snprintf(buf, sizeof(buf), "playAnimation(%s, %s, %s);", o.c_str(), s.c_str(), f.c_str());
+		return buf;
+	}
+	case 0x1F: {
+		std::string o = formatValueC();
+		std::string x = formatValueC();
+		std::string y = formatValueC();
+		snprintf(buf, sizeof(buf), "_rt.pathWalkable = testPathfinding(%s, %s, %s);", o.c_str(), x.c_str(), y.c_str());
+		return buf;
+	}
+	case 0x20: {
+		std::string o = formatValueC();
+		std::string v = formatValueC();
+		snprintf(buf, sizeof(buf), "setYOffset(%s, %s);", o.c_str(), v.c_str());
+		return buf;
+	}
+	case 0x21: {
+		std::string o = formatValueC();
+		std::string t = formatValueC();
+		std::string d = formatValueC();
+		std::string di = formatValueC();
+		snprintf(buf, sizeof(buf), "setMotion(%s, %s, %s, %s);", o.c_str(), t.c_str(), d.c_str(), di.c_str());
+		return buf;
+	}
+	case 0x22: {
+		std::string o = formatValueC();
+		std::string a = formatValueC();
+		// Replace numeric orientation with named constant
+		static const char *orientNames[] = {nullptr, "ORIENT_WALK_N", "ORIENT_WALK_NE", "ORIENT_WALK_E", "ORIENT_WALK_SE", "ORIENT_WALK_S", "ORIENT_WALK_SW", "ORIENT_WALK_W", "ORIENT_WALK_NW", "ORIENT_STAND_N", "ORIENT_STAND_NE", "ORIENT_STAND_E", "ORIENT_STAND_SE", "ORIENT_STAND_S", "ORIENT_STAND_SW", "ORIENT_STAND_W", "ORIENT_STAND_NW", "ORIENT_PICKUP"};
+		int ov = atoi(a.c_str());
+		if (ov >= 1 && ov <= 17)
+			a = orientNames[ov];
+		snprintf(buf, sizeof(buf), "setOrientation(%s, %s);", o.c_str(), a.c_str());
+		return buf;
+	}
+	case 0x23: {
+		std::string o = formatValueC();
+		std::string x = formatValueC();
+		std::string y = formatValueC();
+		std::string v = formatValueC();
+		snprintf(buf, sizeof(buf), "moveToPosition(%s, %s, %s, %s);", o.c_str(), x.c_str(), y.c_str(), v.c_str());
+		return buf;
+	}
+	case 0x24:
+	case 0x25: {
+		std::string a = formatValueC();
+		std::string b = formatValueC();
+		snprintf(buf, sizeof(buf), "%s %s= %s;", a.c_str(), opcode == 0x24 ? "+" : "-", b.c_str());
+		return buf;
+	}
+	case 0x26: {
+		std::string o = formatValueC();
+		std::string d = formatValueC();
+		uint8_t i = readByte();
+		snprintf(buf, sizeof(buf), "loadSpecialAnim(%s, %s, %u);", o.c_str(), d.c_str(), i);
+		return buf;
+	}
+	case 0x27: {
+		std::string o = formatValueC();
+		std::string m = formatValueC();
+		snprintf(buf, sizeof(buf), "setDirection(%s, %s);", o.c_str(), m.c_str());
+		return buf;
+	}
+	case 0x28:
+		return "stopAnimation();";
+	case 0x29: {
+		std::string o = formatValueC();
+		snprintf(buf, sizeof(buf), "openInventory(%s);", o.c_str());
+		return buf;
+	}
+	case 0x2A: {
+		std::string o = formatValueC();
+		std::string s = formatValueC();
+		std::string d = formatValueC();
+		uint8_t i = readByte();
+		snprintf(buf, sizeof(buf), "loadObjectAnim(%s, %s, %s, %u);", o.c_str(), s.c_str(), d.c_str(), i);
+		return buf;
+	}
+	case 0x2B: {
+		std::string o = formatValueC();
+		snprintf(buf, sizeof(buf), "checkObjectData(%s);", o.c_str());
+		return buf;
+	}
+	case 0x2C: {
+		std::string o = formatValueC();
+		std::string v = formatValueC();
+		snprintf(buf, sizeof(buf), "_rt.invCheck = checkInventory(%s, %s);", o.c_str(), v.c_str());
+		return buf;
+	}
+	case 0x2D: {
+		std::string o = formatValueC();
+		std::string v = formatValueC();
+		snprintf(buf, sizeof(buf), "setSnapToTarget(%s, %s);", o.c_str(), v.c_str());
+		return buf;
+	}
+	case 0x2E: {
+		std::string a = formatValueC();
+		std::string lo = formatValueC();
+		std::string hi = formatValueC();
+		snprintf(buf, sizeof(buf), "_rt.animBlobRange = testSceneAnimFrame(%s, %s, %s);", a.c_str(), lo.c_str(), hi.c_str());
+		return buf;
+	}
+	case 0x2F: {
+		std::string o = formatValueC();
+		std::string s = formatValueC();
+		std::string lo = formatValueC();
+		std::string hi = formatValueC();
+		snprintf(buf, sizeof(buf), "_rt.animBlobRange = testObjectAnimFrame(%s, %s, %s, %s);", o.c_str(), s.c_str(), lo.c_str(), hi.c_str());
+		return buf;
+	}
+	case 0x31: {
+		std::string v = formatValueC();
+		snprintf(buf, sizeof(buf), "setPaletteDarkness(%s);", v.c_str());
+		return buf;
+	}
+	case 0x32: {
+		std::string o = formatValueC();
+		std::string v = formatValueC();
+		snprintf(buf, sizeof(buf), "setObjectClickable(%s, %s);", o.c_str(), v.c_str());
+		return buf;
+	}
+	case 0x33: {
+		std::string o = formatValueC();
+		std::string v = formatValueC();
+		snprintf(buf, sizeof(buf), "setObjectVisible(%s, %s);", o.c_str(), v.c_str());
+		return buf;
+	}
+	case 0x34: {
+		std::string a = formatValueC();
+		std::string b = formatValueC();
+		snprintf(buf, sizeof(buf), "setHotspotOverride(%s, %s);", a.c_str(), b.c_str());
+		return buf;
+	}
+	case 0x35: {
+		std::string o = formatValueC();
+		std::string p = formatValueC();
+		std::string a = formatValueC();
+		std::string b = formatValueC();
+		std::string c = formatValueC();
+		snprintf(buf, sizeof(buf), "setObjectBounds(%s, %s, %s, %s, %s);", o.c_str(), p.c_str(), a.c_str(), b.c_str(), c.c_str());
+		return buf;
+	}
+	case 0x36:
+		return "dismissAllPanels();";
+	case 0x37:
+		return "resetToSceneScript();";
+	case 0x38: {
+		uint8_t r = readByte();
+		snprintf(buf, sizeof(buf), "loadOverlayFont(%u);", r);
+		return buf;
+	}
+	case 0x39:
+		return "endOverlayText();";
+	case 0x3A: {
+		std::string x = formatValueC();
+		std::string y = formatValueC();
+		std::string a = formatValueC();
+		uint16_t s = readWord();
+		uint16_t t = readWord();
+		snprintf(buf, sizeof(buf), "addOverlayTextEntry(%s, %s, %s, %u, %u);", x.c_str(), y.c_str(), a.c_str(), s, t);
+		return buf;
+	}
+	case 0x3B:
+		return "clearOverlayText();";
+	case 0x3C: {
+		std::string v = formatValueC();
+		snprintf(buf, sizeof(buf), "fadeToBlack(%s);", v.c_str());
+		return buf;
+	}
+	case 0x3D: {
+		std::string v = formatValueC();
+		snprintf(buf, sizeof(buf), "fadeFromBlack(%s);", v.c_str());
+		return buf;
+	}
+	case 0x3E: {
+		uint8_t r = readByte();
+		snprintf(buf, sizeof(buf), "loadPcmSound(%u);", r);
+		return buf;
+	}
+	case 0x3F:
+		return "freePcmSound();";
+	case 0x40:
+		return "playPcmSound();";
+	case 0x41:
+		return "waitForSound();";
+	case 0x42:
+		return "stopPcmSound();";
+	case 0x43: {
+		std::string s = formatValueC();
+		uint8_t r = readByte();
+		snprintf(buf, sizeof(buf), "loadMusicSlot(%s, %u);", s.c_str(), r);
+		return buf;
+	}
+	case 0x44:
+	case 0x45: {
+		std::string s = formatValueC();
+		std::string a = formatValueC();
+		std::string b = formatValueC();
+		snprintf(buf, sizeof(buf), "%s(%s, %s, %s);", opcode == 0x44 ? "playMusicSlot" : "stopMusicSlot", s.c_str(), a.c_str(), b.c_str());
+		return buf;
+	}
+	case 0x46: {
+		std::string s = formatValueC();
+		snprintf(buf, sizeof(buf), "freeMusicSlot(%s);", s.c_str());
+		return buf;
+	}
+	case 0x47: {
+		std::string o = formatValueC();
+		snprintf(buf, sizeof(buf), "waitForMusic(%s);", o.c_str());
+		return buf;
+	}
+	case 0x48:
+	case 0x49:
+	case 0x4A:
+	case 0x4B: {
+		std::string o = formatValueC();
+		if (pos + 3 <= endPos) {
+			readByte();
+			uint16_t v = readWord();
+			usedVars.insert(v);
+			if (v > maxVarIdx)
+				maxVarIdx = v;
+			snprintf(buf, sizeof(buf), "%s = %s(%s);", formatVarAccess(v).c_str(), getOpcodeName(opcode), o.c_str());
+		} else {
+			snprintf(buf, sizeof(buf), "%s(%s);", getOpcodeName(opcode), o.c_str());
+		}
+		return buf;
+	}
+	case 0x4C:
+		return "clearActorInventory();";
+	case 0x4D: {
+		std::string a = formatValueC();
+		std::string b = formatValueC();
+		snprintf(buf, sizeof(buf), "setPathfindingRemap(%s, %s);", a.c_str(), b.c_str());
+		return buf;
+	}
+	case 0x4E:
+		return "waitForAdlib();";
+	default:
+		snprintf(buf, sizeof(buf), "/* unknown opcode 0x%02x */", opcode);
+		return buf;
+	}
+}
+
+static void disassembleC(int sceneIndex, const char *type = "scene") {
+	// First pass: collect used objects and vars
+	usedObjects.clear();
+	usedVars.clear();
+	maxVarIdx = 0;
+
+	uint32_t savedPos = pos;
+	uint32_t savedSize = scriptSize;
+	// Dry run to collect references
+	int dummyIndent = 0;
+	while (pos < scriptSize - 1) {
+		uint8_t opcode = readByte();
+		if (opcode == 0x00)
+			continue;
+		uint8_t length = readByte();
+		uint32_t endPos = pos + length;
+		if (opcode == 0x07 && dummyIndent > 0)
+			dummyIndent--;
+		if (opcode == 0x08 && dummyIndent > 0)
+			dummyIndent--;
+		decodeCLine(opcode, endPos, dummyIndent);
+		if (pos != endPos)
+			pos = endPos;
+	}
+
+	// Second pass: emit C
+	pos = savedPos;
+	scriptSize = savedSize;
+
+	printf("// --- %s %d ---\n", type, sceneIndex);
+
+	// Function body
+	printf("void %s_%d_script(void) {\n", type, sceneIndex);
+
+	int indent = 0;
+	while (pos < scriptSize - 1) {
+		uint8_t opcode = readByte();
+		if (opcode == 0x00)
+			continue;
+		uint8_t length = readByte();
+		uint32_t endPos = pos + length;
+		if (opcode == 0x07 && indent > 0)
+			indent--;
+		if (opcode == 0x08 && indent > 0)
+			indent--;
+		std::string line = decodeCLine(opcode, endPos, indent);
+		printf("\t");
+		for (int i = 0; i < indent - (opcode == 0x03 || opcode == 0x04 || opcode == 0x05 || opcode == 0x06 || opcode == 0x08 ? 1 : 0); i++)
+			printf("\t");
+		printf("%s\n", line.c_str());
+		if (pos != endPos)
+			pos = endPos;
+	}
+
+	printf("}\n");
+}
+
 int main(int argc, char **argv) {
 	if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
 		printHelp(argv[0]);
@@ -985,11 +2178,14 @@ int main(int argc, char **argv) {
 	}
 
 	bool jsonMode = false;
+	bool cMode = false;
 	bool objectsMode = false;
 	int argIdx = 1;
 	while (argIdx < argc && argv[argIdx][0] == '-') {
 		if (!strcmp(argv[argIdx], "--json")) {
 			jsonMode = true;
+		} else if (!strcmp(argv[argIdx], "--c") || !strcmp(argv[argIdx], "--cpp")) {
+			cMode = true;
 		} else if (!strcmp(argv[argIdx], "--objects")) {
 			objectsMode = true;
 		} else {
@@ -1004,11 +2200,26 @@ int main(int argc, char **argv) {
 		return 1;
 	}
 
+	// Check if it's an Amiga data directory first
+	if (isAmigaDir(argv[argIdx])) {
+		return runAmiga(argv[argIdx], jsonMode, objectsMode);
+	}
+
 	FILE *f = fopen(argv[argIdx], "rb");
 	if (!f) {
 		fprintf(stderr, "Error: Cannot open file '%s'\n", argv[argIdx]);
 		return 1;
 	}
+
+	// Also check: if it opens but starts with MXMF, redirect to Amiga mode
+	uint8_t magic[4];
+	fread(magic, 1, 4, f);
+	fseek(f, 0, SEEK_SET);
+	if (memcmp(magic, "MXMF", 4) == 0) {
+		fclose(f);
+		fprintf(stderr, "Error: '%s' is an Amiga DataA file. Pass the directory containing it.\n", argv[argIdx]);
+		return 1;
+	}
 	argIdx++;
 
 	int startScene = 1;
@@ -1019,7 +2230,162 @@ int main(int argc, char **argv) {
 		endScene = startScene;
 	}
 
-	if (jsonMode) {
+	if (cMode) {
+		printf("// MACS2 auto-decompiled scripts\n");
+		printf("#define DEMACS2\n");
+		printf("#include \"engines/macs2/scriptexecutor.h\"\n\n");
+		printf("using namespace Macs2::Script;\n\n");
+		printf("static ScriptExecutor _executor;\n\n");
+		printf("typedef uint16_t Object;\ntypedef uint16_t Value;\n\n");
+		printf("static const Value TRUE = 1;\n");
+		printf("static const Value FALSE = 0;\n");
+		printf("static const Value FADE_CUT = 0;\n");
+		printf("static const Value SIDE_LEFT = 0;\n");
+		printf("static const Value SIDE_RIGHT = 1;\n");
+		printf("static const Value LOOP = 1;\n\n");
+		printf("/* Orientation constants (1-8=walking, 9-16=standing, 17=pickup) */\n");
+		printf("#define ORIENT_WALK_N  1\n#define ORIENT_WALK_NE 2\n#define ORIENT_WALK_E  3\n#define ORIENT_WALK_SE 4\n");
+		printf("#define ORIENT_WALK_S  5\n#define ORIENT_WALK_SW 6\n#define ORIENT_WALK_W  7\n#define ORIENT_WALK_NW 8\n");
+		printf("#define ORIENT_STAND_N  9\n#define ORIENT_STAND_NE 10\n#define ORIENT_STAND_E  11\n#define ORIENT_STAND_SE 12\n");
+		printf("#define ORIENT_STAND_S  13\n#define ORIENT_STAND_SW 14\n#define ORIENT_STAND_W  15\n#define ORIENT_STAND_NW 16\n");
+		printf("#define ORIENT_PICKUP 17\n\n");
+		printf("/* Known script variable indices */\n");
+		for (uint16_t i = 0; i <= 512; i++) {
+			const char *n = getVarName(i);
+			if (n)
+				printf("#define %s %u\n", n, i);
+		}
+		printf("\n");
+		printf("static struct {\n");
+		for (const auto &s : kSpecialNames)
+			printf("\tValue %s;\n", s.name);
+		printf("} _rt;\n\n");
+		printf("static Object _objects[512];\nstatic Value _vars[512];\n\n");
+		printf("/* Named objects */\n");
+		for (uint16_t i = 1; i <= 0xBF; i++) {
+			const char *n = getObjectName(i);
+			if (n)
+				printf("#define %s 0x%02x\n", n, i);
+		}
+		printf("\n");
+		printf("/* Wrappers to ScriptExecutor::scriptXXX */\n");
+		printf("static void moveObject(Object obj, Value scene, Value x, Value y) { _executor.scriptMoveObject(); }\n");
+		printf("static void removeFromScene(Object obj) { _executor.scriptMoveObject(); }\n");
+		printf("static void changeScene(Value scene, Value mode, Value speed) { _executor.scriptChangeScene(); }\n");
+		printf("static void showDialogue(Object obj, Value x, Value y, Value side, uint16_t s, uint16_t n) { _executor.scriptShowDialogue(); }\n");
+		printf("static void printStringLeft(Value x, Value y, uint16_t s, uint16_t n) { _executor.scriptPrintStringLeft(); }\n");
+		printf("static void printStringRight(Value x, Value y, uint16_t s, uint16_t n) { _executor.scriptPrintStringRight(); }\n");
+		printf("static void frameWait(Value t) { _executor.scriptFrameWait(); }\n");
+		printf("static void walkToPosition(Object o, Value x, Value y) { _executor.scriptWalkToPosition(); }\n");
+		printf("static void waitForWalk(Object o) { _executor.scriptWaitForWalk(); }\n");
+		printf("static void setPathfinding(Value a, Value b, Value c) { _executor.scriptSetPathfinding(); }\n");
+		printf("static void skipWord(uint16_t w) { _executor.scriptSkipWord(); }\n");
+		printf("static void clearDialogueChoices() { _executor.scriptClearDialogueChoices(); }\n");
+		printf("static void addDialogueChoice(Value i, uint16_t s, uint16_t n) { _executor.scriptAddDialogueChoice(); }\n");
+		printf("static void showDialogueChoice(Object o, Value x, Value y, Value s) { _executor.scriptShowDialogueChoice(); }\n");
+		printf("static void dismissPanel() { _executor.scriptDismissPanel(); }\n");
+		printf("static void walkToAndPickup(Object a, Object o) { _executor.scriptWalkToAndPickup(); }\n");
+		printf("static void setPickupFrames(Object o, Value s, Value e) { _executor.scriptSetPickupFrames(); }\n");
+		printf("static void setupObject(Object o, Value s, Value sp) { _executor.scriptSetupObject(); }\n");
+		printf("static void setSkippable() { _executor.scriptSetSkippable(); }\n");
+		printf("static void clearSkippable() { _executor.scriptClearSkippable(); }\n");
+		printf("static void playAnimation(Object o, Value s, Value f) { _executor.scriptPlayAnimation(); }\n");
+		printf("static Value testPathfinding(Object o, Value x, Value y) { _executor.scriptTestPathfinding(); return 0; }\n");
+		printf("static void setYOffset(Object o, Value v) { _executor.scriptSetYOffset(); }\n");
+		printf("static void setMotion(Object o, Value t, Value d, Value di) { _executor.scriptSetMotion(); }\n");
+		printf("static void setOrientation(Object o, Value a) { _executor.scriptSetOrientation(); }\n");
+		printf("static void moveToPosition(Object o, Value x, Value y, Value v) { _executor.scriptMoveToPosition(); }\n");
+		printf("static void loadSpecialAnim(Object o, Value d, uint8_t i) { _executor.scriptLoadSpecialAnim(); }\n");
+		printf("static void setDirection(Object o, Value m) { _executor.scriptSetDirection(); }\n");
+		printf("static void stopAnimation() { _executor.scriptStopAnimation(); }\n");
+		printf("static void changeAnimation() { _executor.scriptChangeAnimation(); }\n");
+		printf("static void openInventory(Object o) { _executor.scriptOpenInventory(); }\n");
+		printf("static void loadObjectAnim(Object o, Value s, Value d, uint8_t i) { _executor.scriptLoadObjectAnim(); }\n");
+		printf("static void checkObjectData(Object o) { _executor.scriptCheckObjectData(); }\n");
+		printf("static Value checkInventory(Object o, Value v) { _executor.scriptCheckInventory(); return 0; }\n");
+		printf("static void setSnapToTarget(Object o, Value v) { _executor.scriptSetSnapToTarget(); }\n");
+		printf("static Value testSceneAnimFrame(Value a, Value lo, Value hi) { _executor.scriptTestSceneAnimFrame(); return 0; }\n");
+		printf("static Value testObjectAnimFrame(Object o, Value s, Value lo, Value hi) { _executor.scriptTestObjectAnimFrame(); return 0; }\n");
+		printf("static void setPaletteDarkness(Value v) { _executor.scriptSetPaletteDarkness(); }\n");
+		printf("static void setObjectClickable(Object o, Value v) { _executor.scriptSetObjectClickable(); }\n");
+		printf("static void setObjectVisible(Object o, Value v) { _executor.scriptSetObjectVisible(); }\n");
+		printf("static void setHotspotOverride(Value a, Value b) { _executor.scriptSetHotspotOverride(); }\n");
+		printf("static void setObjectBounds(Object o, Object p, Value a, Value b, Value c) { _executor.scriptSetObjectBounds(); }\n");
+		printf("static void dismissAllPanels() { _executor.scriptDismissAllPanels(); }\n");
+		printf("static void resetToSceneScript() { _executor.scriptResetToSceneScript(); }\n");
+		printf("static void loadOverlayFont(uint8_t r) { _executor.scriptLoadOverlayFont(); }\n");
+		printf("static void endOverlayText() { _executor.scriptEndOverlayText(); }\n");
+		printf("static void addOverlayTextEntry(Value x, Value y, Value a, uint16_t s, uint16_t t) { _executor.scriptAddOverlayTextEntry(); }\n");
+		printf("static void clearOverlayText() { _executor.scriptClearOverlayText(); }\n");
+		printf("static void fadeToBlack(Value s) { _executor.scriptFadeToBlack(); }\n");
+		printf("static void fadeFromBlack(Value s) { _executor.scriptFadeFromBlack(); }\n");
+		printf("static void loadPcmSound(uint8_t r) { _executor.scriptLoadPcmSound(); }\n");
+		printf("static void freePcmSound() { _executor.scriptFreePcmSound(); }\n");
+		printf("static void playPcmSound() { _executor.scriptPlayPcmSound(); }\n");
+		printf("static void waitForSound() { _executor.scriptWaitForSound(); }\n");
+		printf("static void stopPcmSound() { _executor.scriptStopPcmSound(); }\n");
+		printf("static void loadMusicSlot(Value s, uint8_t r) { _executor.scriptLoadMusicSlot(); }\n");
+		printf("static void playMusicSlot(Value s, Value a, Value b) { _executor.scriptPlayMusicSlot(); }\n");
+		printf("static void stopMusicSlot(Value s, Value a, Value b) { _executor.scriptStopMusicSlot(); }\n");
+		printf("static void freeMusicSlot(Value s) { _executor.scriptFreeMusicSlot(); }\n");
+		printf("static void waitForMusic(Object o) { _executor.scriptWaitForMusic(); }\n");
+		printf("static Value getObjectX(Object o) { _executor.scriptGetObjectX(); return 0; }\n");
+		printf("static Value getObjectY(Object o) { _executor.scriptGetObjectY(); return 0; }\n");
+		printf("static Value getObjectField8(Object o) { _executor.scriptGetObjectField8(); return 0; }\n");
+		printf("static Value getObjectOrientation(Object o) { _executor.scriptGetObjectOrientation(); return 0; }\n");
+		printf("static void clearActorInventory() { _executor.scriptClearActorInventory(); }\n");
+		printf("static void setPathfindingRemap(Value a, Value b) { _executor.scriptSetPathfindingRemap(); }\n");
+		printf("static void waitForAdlib() { _executor.scriptWaitForAdlib(); }\n");
+		printf("static void skipUntil14() { _executor.scriptSkipUntil14(); }\n");
+		printf("static int ifInteraction(Value i, Value a, Value b) { _executor.scriptIfInteraction(); return 0; }\n");
+		printf("static void nop() { _executor.scriptNop09(); }\n\n");
+
+		for (int scene = startScene; scene <= endScene; scene++) {
+			if (loadSceneScript(f, (uint16_t)scene)) {
+				disassembleC(scene, "scene");
+				free(scriptData);
+				scriptData = nullptr;
+				scriptSize = 0;
+				free(stringData);
+				stringData = nullptr;
+				stringDataSize = 0;
+			}
+			if (objectsMode) {
+				for (int obj = 1; obj <= 0x200; obj++) {
+					uint16_t objScene = getObjectSceneIndex(f, (uint16_t)obj);
+					if (objScene != (uint16_t)scene)
+						continue;
+					if (loadObjectScript(f, (uint16_t)obj)) {
+						printf("\n");
+						disassembleC(obj, "object");
+						free(scriptData);
+						scriptData = nullptr;
+						scriptSize = 0;
+						free(stringData);
+						stringData = nullptr;
+						stringDataSize = 0;
+					}
+				}
+			}
+		}
+		if (objectsMode) {
+			for (int obj = 1; obj <= 0x200; obj++) {
+				uint16_t objScene = getObjectSceneIndex(f, (uint16_t)obj);
+				if (objScene != 0)
+					continue;
+				if (loadObjectScript(f, (uint16_t)obj)) {
+					printf("\n");
+					disassembleC(obj, "inventory");
+					free(scriptData);
+					scriptData = nullptr;
+					scriptSize = 0;
+					free(stringData);
+					stringData = nullptr;
+					stringDataSize = 0;
+				}
+			}
+		}
+	} else if (jsonMode) {
 		printf("{\"scenes\": [\n");
 		bool first = true;
 		for (int scene = startScene; scene <= endScene; scene++) {
diff --git a/engines/macs2/extract_macs2.cpp b/engines/macs2/extract_macs2.cpp
index 48a96fd0..60b7a5a7 100644
--- a/engines/macs2/extract_macs2.cpp
+++ b/engines/macs2/extract_macs2.cpp
@@ -992,9 +992,386 @@ static void extractItems(const char *outDir) {
 	printf("Extracted %d inventory item icons.\n", count);
 }
 
+// ============================================================================
+// Amiga PP20 (PowerPacker) decompression
+// ============================================================================
+
+static bool pp20Decompress(const uint8_t *src, uint32_t srcLen, uint8_t *dst, uint32_t dstLen) {
+	if (srcLen < 12 || memcmp(src, "PP20", 4) != 0)
+		return false;
+
+	const uint8_t *eff = src + 4; // efficiency table (4 bytes)
+	const uint8_t *packed = src + 8;
+	uint32_t packedLen = srcLen - 12; // exclude magic(4) + eff(4) + trailer(4)
+
+	// Trailer: last 4 bytes = decrunch info
+	const uint8_t *trailer = src + srcLen - 4;
+	uint32_t origSize = ((uint32_t)trailer[0] << 16) | ((uint32_t)trailer[1] << 8) | trailer[2];
+	uint8_t bitrot = trailer[3];
+
+	if (origSize != dstLen || origSize == 0)
+		return false;
+
+	// Build bit-reverse table
+	uint8_t rev[256];
+	for (int a = 0; a < 256; a++) {
+		uint8_t b = (uint8_t)a;
+		b = (uint8_t)(((b & 0x0f) << 4) | ((b >> 4) & 0x0f));
+		b = (uint8_t)(((b & 0x33) << 2) | ((b >> 2) & 0x33));
+		b = (uint8_t)(((b & 0x55) << 1) | ((b >> 1) & 0x55));
+		rev[a] = b;
+	}
+
+	uint32_t outPos = dstLen;
+	int32_t inPos = (int32_t)packedLen;
+	uint32_t code = 0;
+	uint32_t shift = 32;
+
+	// Lambda-like macros for bit reading
+#define PP_PEEK(x) \
+	while (shift > 32u - (x)) { \
+		if (inPos <= 0) goto pp_done; \
+		shift -= 8; \
+		inPos--; \
+		code += (uint32_t)rev[packed[inPos]] << shift; \
+	}
+
+#define PP_SHIFT(x) do { shift += (x); code = (code << (x)) & 0xFFFFFFFF; } while(0)
+
+	PP_PEEK(bitrot);
+	PP_SHIFT(bitrot);
+
+	while (outPos > 0) {
+		PP_PEEK(1);
+		uint32_t bit = code >> 31;
+		PP_SHIFT(1);
+
+		if (bit == 0) {
+			// Literal run
+			PP_PEEK(2);
+			uint32_t length = (code >> 30) + 1;
+			PP_SHIFT(2);
+			if (length == 4) {
+				for (;;) {
+					PP_PEEK(2);
+					uint32_t b2 = code >> 30;
+					PP_SHIFT(2);
+					length += b2;
+					if (b2 != 3) break;
+				}
+			}
+			for (uint32_t i = 0; i < length && outPos > 0; i++) {
+				PP_PEEK(8);
+				outPos--;
+				dst[outPos] = (uint8_t)(code >> 24);
+				PP_SHIFT(8);
+			}
+			if (outPos == 0) break;
+		}
+
+		// LZ match
+		PP_PEEK(2);
+		uint32_t cv = code >> 30;
+		PP_SHIFT(2);
+
+		uint32_t length, nbits;
+		if (cv == 0) { length = 2; nbits = eff[0]; }
+		else if (cv == 1) { length = 3; nbits = eff[1]; }
+		else if (cv == 2) { length = 4; nbits = eff[2]; }
+		else {
+			PP_PEEK(1);
+			uint32_t extra = code >> 31;
+			PP_SHIFT(1);
+			length = 5;
+			nbits = (extra == 0) ? 7 : eff[3];
+		}
+
+		PP_PEEK(nbits);
+		uint32_t ptr = (code >> (32 - nbits)) + 1;
+		PP_SHIFT(nbits);
+
+		if (length == 5) {
+			for (;;) {
+				PP_PEEK(3);
+				uint32_t b3 = code >> 29;
+				PP_SHIFT(3);
+				length += b3;
+				if (b3 != 7) break;
+			}
+		}
+
+		for (uint32_t i = 0; i < length && outPos > 0; i++) {
+			outPos--;
+			dst[outPos] = dst[outPos + ptr];
+		}
+	}
+
+pp_done:
+#undef PP_PEEK
+#undef PP_SHIFT
+	return outPos == 0;
+}
+
+// Get decompressed size from PP20 trailer
+static uint32_t pp20GetOrigSize(const uint8_t *data, uint32_t len) {
+	if (len < 12) return 0;
+	const uint8_t *t = data + len - 4;
+	return ((uint32_t)t[0] << 16) | ((uint32_t)t[1] << 8) | t[2];
+}
+
+// ============================================================================
+// Amiga DataA (MXMF) + Mdir (MXDR) extraction
+// ============================================================================
+
+static uint32_t readU32BE(const uint8_t *p) {
+	return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
+}
+
+static uint16_t readU16BE(const uint8_t *p) {
+	return ((uint16_t)p[0] << 8) | p[1];
+}
+
+static bool isAmigaDataDir(const char *path) {
+	// Check if path is a directory containing DataA and Mdir
+	std::string dataA = std::string(path) + "/DataA";
+	std::string mdir = std::string(path) + "/Mdir";
+	struct stat st;
+	return (stat(dataA.c_str(), &st) == 0 && S_ISREG(st.st_mode) &&
+	        stat(mdir.c_str(), &st) == 0 && S_ISREG(st.st_mode));
+}
+
+static int extractAmiga(const char *gameDir, const char *outDir) {
+	std::string dataAPath = std::string(gameDir) + "/DataA";
+	std::string mdirPath = std::string(gameDir) + "/Mdir";
+
+	// Read Mdir
+	FILE *mf = fopen(mdirPath.c_str(), "rb");
+	if (!mf) {
+		fprintf(stderr, "Error: Cannot open '%s'\n", mdirPath.c_str());
+		return 1;
+	}
+	fseek(mf, 0, SEEK_END);
+	uint32_t mdirSize = (uint32_t)ftell(mf);
+	fseek(mf, 0, SEEK_SET);
+	std::vector<uint8_t> mdirData(mdirSize);
+	fread(mdirData.data(), 1, mdirSize, mf);
+	fclose(mf);
+
+	if (mdirSize < 18 || memcmp(mdirData.data(), "MXDR", 4) != 0) {
+		fprintf(stderr, "Error: '%s' is not a valid MXDR file\n", mdirPath.c_str());
+		return 1;
+	}
+
+	uint16_t dirEntryCount = (uint16_t)((mdirSize - 18) / 10);
+	printf("Mdir: %u directory entries\n", dirEntryCount);
+
+	// Parse directory entries (offset 18, each 10 bytes: type(2) + id(2) + disk(2) + offset(4))
+	struct DirEntry {
+		char type[3];
+		uint16_t id;
+		uint16_t disk;
+		uint32_t offset;
+	};
+	std::vector<DirEntry> entries(dirEntryCount);
+	for (uint16_t i = 0; i < dirEntryCount; i++) {
+		const uint8_t *p = mdirData.data() + 18 + i * 10;
+		entries[i].type[0] = (char)p[0];
+		entries[i].type[1] = (char)p[1];
+		entries[i].type[2] = '\0';
+		entries[i].id = readU16BE(p + 2);
+		entries[i].disk = readU16BE(p + 4);
+		entries[i].offset = readU32BE(p + 6);
+	}
+
+	// Open DataA
+	FILE *df = fopen(dataAPath.c_str(), "rb");
+	if (!df) {
+		fprintf(stderr, "Error: Cannot open '%s'\n", dataAPath.c_str());
+		return 1;
+	}
+	fseek(df, 0, SEEK_END);
+	uint32_t dataASize = (uint32_t)ftell(df);
+	fseek(df, 0, SEEK_SET);
+
+	// Read and validate MXMF header (14 bytes)
+	uint8_t mxmfHdr[14];
+	fread(mxmfHdr, 1, 14, df);
+	if (memcmp(mxmfHdr, "MXMF", 4) != 0) {
+		fprintf(stderr, "Error: '%s' is not a valid MXMF file\n", dataAPath.c_str());
+		fclose(df);
+		return 1;
+	}
+	uint16_t totalResources = readU16BE(mxmfHdr + 8);
+	uint32_t firstBlockSize = readU32BE(mxmfHdr + 10);
+	printf("DataA: %u resources, file size=%u\n", totalResources, dataASize);
+
+	mkdirp(outDir);
+
+	// Extract first PP20 block (scene table) at offset 14
+	{
+		std::vector<uint8_t> pp(firstBlockSize);
+		fseek(df, 14, SEEK_SET);
+		fread(pp.data(), 1, firstBlockSize, df);
+
+		uint32_t origSize = pp20GetOrigSize(pp.data(), firstBlockSize);
+		if (origSize > 0 && memcmp(pp.data(), "PP20", 4) == 0) {
+			std::vector<uint8_t> dec(origSize);
+			if (pp20Decompress(pp.data(), firstBlockSize, dec.data(), origSize)) {
+				char path[512];
+				snprintf(path, sizeof(path), "%s/scene_table.bin", outDir);
+				writeRawFile(path, dec.data(), origSize);
+				printf("  scene_table.bin: %u -> %u bytes\n", firstBlockSize, origSize);
+			} else {
+				fprintf(stderr, "  WARN: scene_table PP20 decompression failed\n");
+			}
+		}
+	}
+
+	// Default 32-color Amiga palette (grayscale fallback for sprites without scene palette)
+	uint8_t defaultPalette[768];
+	for (int i = 0; i < 256; i++) {
+		uint8_t v = (uint8_t)((i < 32) ? (i * 255 / 31) : 0);
+		defaultPalette[i * 3 + 0] = v;
+		defaultPalette[i * 3 + 1] = v;
+		defaultPalette[i * 3 + 2] = v;
+	}
+
+	// Extract all resources sequentially from offset 14 + firstBlockSize
+	fseek(df, 14 + firstBlockSize, SEEK_SET);
+	int extracted = 1; // counting scene_table
+	int images = 0;
+	for (uint16_t i = 0; i < totalResources - 1; i++) {
+		long entryStart = ftell(df);
+		uint8_t ehdr[8];
+		if (fread(ehdr, 1, 8, df) != 8)
+			break;
+
+		char eType[3] = {(char)ehdr[0], (char)ehdr[1], '\0'};
+		uint16_t eId = readU16BE(ehdr + 2);
+		uint32_t eCompSize = readU32BE(ehdr + 4);
+
+		if (eCompSize == 0 || eCompSize > dataASize) {
+			fprintf(stderr, "  WARN: bad entry at offset 0x%lx, stopping\n", entryStart);
+			break;
+		}
+
+		std::vector<uint8_t> payload(eCompSize);
+		if (fread(payload.data(), 1, eCompSize, df) != eCompSize)
+			break;
+
+		char fname[64];
+		char path[512];
+
+		if (memcmp(payload.data(), "PP20", 4) == 0) {
+			// Direct PP20 block
+			uint32_t origSize = pp20GetOrigSize(payload.data(), eCompSize);
+			if (origSize > 0) {
+				std::vector<uint8_t> dec(origSize);
+				if (pp20Decompress(payload.data(), eCompSize, dec.data(), origSize)) {
+					snprintf(fname, sizeof(fname), "%s_%04d.bin", eType, eId);
+					snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+					writeRawFile(path, dec.data(), origSize);
+					printf("  %s: %u -> %u bytes\n", fname, eCompSize, origSize);
+
+					// Try to extract sprite image from MXOO
+					if (origSize >= 32 && memcmp(dec.data(), "MXOO", 4) == 0) {
+						uint32_t scriptOff = readU32BE(dec.data() + 4);
+						// Sprite body: from offset 12 to scriptOff
+						if (scriptOff > 32 && scriptOff <= origSize) {
+							const uint8_t *body = dec.data() + 12;
+							uint32_t bodyLen = scriptOff - 12;
+							// Check sprite signature: body[10:12]=0x0101, body[14:16]>0
+							if (bodyLen >= 20 && body[10] == 1 && body[11] == 1) {
+								uint16_t frames = readU16BE(body + 14);
+								uint16_t w = readU16BE(body + 16);
+								uint16_t h = readU16BE(body + 18);
+								uint32_t rowBytes = (w + 7) / 8;
+								uint32_t planeSize = rowBytes * h;
+								uint32_t pixelBytes = bodyLen - 20;
+								uint32_t numPlanes = (planeSize > 0) ? pixelBytes / planeSize : 0;
+								if (w > 0 && w <= 320 && h > 0 && h <= 200 &&
+								    frames > 0 && numPlanes >= 5 && numPlanes <= 6 &&
+								    pixelBytes == planeSize * numPlanes) {
+									// Convert planar to chunky (5 color planes)
+									std::vector<uint8_t> pixels(w * h, 0);
+									const uint8_t *pixData = body + 20;
+									for (uint32_t plane = 0; plane < 5 && plane < numPlanes; plane++) {
+										uint32_t pOff = plane * planeSize;
+										for (uint16_t y = 0; y < h; y++) {
+											for (uint32_t bx = 0; bx < rowBytes; bx++) {
+												uint8_t b = pixData[pOff + y * rowBytes + bx];
+												for (int bit = 0; bit < 8; bit++) {
+													uint16_t x = (uint16_t)(bx * 8 + bit);
+													if (x < w && (b & (0x80 >> bit)))
+														pixels[y * w + x] |= (1 << plane);
+												}
+											}
+										}
+									}
+									snprintf(fname, sizeof(fname), "%s_%04d.bmp", eType, eId);
+									snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+									writeBMPEx(path, pixels.data(), w, h, defaultPalette);
+									images++;
+								}
+							}
+						}
+					}
+				} else {
+					snprintf(fname, sizeof(fname), "%s_%04d.pp20", eType, eId);
+					snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+					writeRawFile(path, payload.data(), eCompSize);
+					printf("  %s: %u bytes (decompress failed)\n", fname, eCompSize);
+				}
+			}
+		} else if (memcmp(payload.data(), "MXMM", 4) == 0 && eCompSize > 14) {
+			// Music sub-container: PP20 size at offset 10
+			uint32_t ppSize = readU32BE(payload.data() + 10);
+			if (ppSize > 0 && ppSize <= eCompSize - 14 && memcmp(payload.data() + 14, "PP20", 4) == 0) {
+				uint32_t origSize = pp20GetOrigSize(payload.data() + 14, ppSize);
+				if (origSize > 0) {
+					std::vector<uint8_t> dec(origSize);
+					if (pp20Decompress(payload.data() + 14, ppSize, dec.data(), origSize)) {
+						snprintf(fname, sizeof(fname), "%s_%04d.bin", eType, eId);
+						snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+						writeRawFile(path, dec.data(), origSize);
+						printf("  %s (music): %u -> %u bytes\n", fname, ppSize, origSize);
+					} else {
+						snprintf(fname, sizeof(fname), "%s_%04d.mxmm", eType, eId);
+						snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+						writeRawFile(path, payload.data(), eCompSize);
+						printf("  %s: %u bytes (music decompress failed)\n", fname, eCompSize);
+					}
+				}
+			} else {
+				snprintf(fname, sizeof(fname), "%s_%04d.mxmm", eType, eId);
+				snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+				writeRawFile(path, payload.data(), eCompSize);
+				printf("  %s: %u bytes (raw music container)\n", fname, eCompSize);
+			}
+		} else if (memcmp(payload.data(), "MXOO", 4) == 0) {
+			// Uncompressed object sub-container
+			snprintf(fname, sizeof(fname), "%s_%04d.mxoo", eType, eId);
+			snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+			writeRawFile(path, payload.data(), eCompSize);
+			printf("  %s: %u bytes (uncompressed object)\n", fname, eCompSize);
+		} else {
+			// Unknown format, save raw
+			snprintf(fname, sizeof(fname), "%s_%04d.raw", eType, eId);
+			snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+			writeRawFile(path, payload.data(), eCompSize);
+			printf("  %s: %u bytes (unknown)\n", fname, eCompSize);
+		}
+		extracted++;
+	}
+
+	fclose(df);
+	printf("\nExtracted %d resources (%d sprite images) to %s/\n", extracted, images, outDir);
+	return 0;
+}
+
 static void printHelp(const char *bin) {
 	printf("MACS2 Resource Extractor\n\n");
-	printf("Usage: %s <mode> <game_data_file> <output_dir> [scene_index]\n\n", bin);
+	printf("Usage: %s <mode> <game_data_path> <output_dir> [scene_index]\n\n", bin);
 	printf("Modes:\n");
 	printf("  images     - Extract background images as BMP files\n");
 	printf("  sounds     - Extract sound/music resource blobs\n");
@@ -1005,6 +1382,12 @@ static void printHelp(const char *bin) {
 	printf("  helpimages - Extract help/map panel images as BMP\n");
 	printf("  all        - Extract everything\n");
 	printf("\n");
+	printf("The game_data_path can be:\n");
+	printf("  - RESOURCE.MCS file (DOS version)\n");
+	printf("  - Directory containing RESOURCE.MCS (DOS version)\n");
+	printf("  - Directory containing DataA + Mdir (Amiga version)\n");
+	printf("\n");
+	printf("Amiga mode auto-detects and extracts all PP20-compressed resources.\n");
 	printf("If scene_index is omitted, extracts from all scenes.\n");
 }
 
@@ -1018,9 +1401,15 @@ int main(int argc, char **argv) {
 	const char *resPath = argv[2];
 	const char *outDir = argv[3];
 
+	// Check if this is an Amiga data directory (contains DataA + Mdir)
+	struct stat st;
+	if (stat(resPath, &st) == 0 && S_ISDIR(st.st_mode) && isAmigaDataDir(resPath)) {
+		printf("Detected Amiga data directory.\n");
+		return extractAmiga(resPath, outDir);
+	}
+
 	// If path is a directory, look for RESOURCE.MCS inside it
 	std::string resolvedPath = resPath;
-	struct stat st;
 	if (stat(resPath, &st) == 0 && S_ISDIR(st.st_mode)) {
 		resolvedPath = std::string(resPath) + "/RESOURCE.MCS";
 	}


Commit: aa253c1d3d773ddbc1d95b66a723e23c7f139b4c
    https://github.com/scummvm/scummvm-tools/commit/aa253c1d3d773ddbc1d95b66a723e23c7f139b4c
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MASC2: added missing texts for addOverlayTextEntry

Changed paths:
    engines/macs2/demacs2.cpp


diff --git a/engines/macs2/demacs2.cpp b/engines/macs2/demacs2.cpp
index 66b7d21b..7efab18a 100644
--- a/engines/macs2/demacs2.cpp
+++ b/engines/macs2/demacs2.cpp
@@ -240,7 +240,6 @@ static const struct {
 	{0x2B, "invAction"},
 	{0x2C, "interactedMap"},
 	{0x2D, "curScene"},
-	{0x2E, "const2"},
 	{0x2F, "prevScene"},
 	{0x30, "musicActive"},
 	{0x31, "soundActive"},
@@ -1675,6 +1674,8 @@ static std::string formatValueC() {
 			return "SIDE_RIGHT";
 		case 0x0C:
 			return "LOOP";
+		case 0x2E:
+			return "ALIGN_CENTER";
 		}
 		snprintf(buf, sizeof(buf), "_rt.special[0x%02x]", val);
 		return buf;
@@ -2029,8 +2030,11 @@ static std::string decodeCLine(uint8_t opcode, uint32_t endPos, int &indent) {
 		std::string a = formatValueC();
 		uint16_t s = readWord();
 		uint16_t t = readWord();
-		snprintf(buf, sizeof(buf), "addOverlayTextEntry(%s, %s, %s, %u, %u);", x.c_str(), y.c_str(), a.c_str(), s, t);
-		return buf;
+		std::string decoded = decodeString(s, 1);
+		std::string result2 = std::string("addOverlayTextEntry(") + x + ", " + y + ", " + a + ", " + std::to_string(s) + ", " + std::to_string(t) + ");";
+		if (!decoded.empty())
+			result2 += " // \"" + decoded + "\"";
+		return result2;
 	}
 	case 0x3B:
 		return "clearOverlayText();";
@@ -2242,6 +2246,9 @@ int main(int argc, char **argv) {
 		printf("static const Value FADE_CUT = 0;\n");
 		printf("static const Value SIDE_LEFT = 0;\n");
 		printf("static const Value SIDE_RIGHT = 1;\n");
+		printf("static const Value ALIGN_LEFT = 0;\n");
+		printf("static const Value ALIGN_RIGHT = 1;\n");
+		printf("static const Value ALIGN_CENTER = 2;\n");
 		printf("static const Value LOOP = 1;\n\n");
 		printf("/* Orientation constants (1-8=walking, 9-16=standing, 17=pickup) */\n");
 		printf("#define ORIENT_WALK_N  1\n#define ORIENT_WALK_NE 2\n#define ORIENT_WALK_E  3\n#define ORIENT_WALK_SE 4\n");


Commit: 1482504727d96e503c96f0c0eb8a6c0b488177fd
    https://github.com/scummvm/scummvm-tools/commit/1482504727d96e503c96f0c0eb8a6c0b488177fd
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: renamed more objects and fixed changeAnimation

Changed paths:
    engines/macs2/demacs2.cpp


diff --git a/engines/macs2/demacs2.cpp b/engines/macs2/demacs2.cpp
index 7efab18a..78677543 100644
--- a/engines/macs2/demacs2.cpp
+++ b/engines/macs2/demacs2.cpp
@@ -384,9 +384,9 @@ static const char *getOpcodeName(uint8_t opcode) {
 	case 0x31:
 		return "setPaletteDarkness";
 	case 0x32:
-		return "setObjectClickable";
+		return "setObjectShading";
 	case 0x33:
-		return "setObjectVisible";
+		return "setObjectScaling";
 	case 0x34:
 		return "setHotspotOverride";
 	case 0x35:
@@ -566,6 +566,13 @@ static std::string decodeParams(uint8_t opcode, uint32_t endPos, int &indent) {
 		result = buf;
 		break;
 	}
+	case 0x0E: {
+		std::string bgAnim = formatValue();
+		std::string frame = formatValue();
+		snprintf(buf, sizeof(buf), " bgAnim=%s frame=%s", bgAnim.c_str(), frame.c_str());
+		result = buf;
+		break;
+	}
 	case 0x0F:
 	case 0x31:
 	case 0x3C:
@@ -837,7 +844,6 @@ static std::string decodeParams(uint8_t opcode, uint32_t endPos, int &indent) {
 	}
 	case 0x07:
 	case 0x09:
-	case 0x0E:
 	case 0x13:
 	case 0x15:
 	case 0x18:
@@ -1460,7 +1466,11 @@ static const char *getObjectName(uint16_t idx) {
 	case 2:
 		return "OBJ_CAPTAIN";
 	case 3:
-		return "OBJ_AUNT";
+		return "OBJ_SHIP";
+	case 4:
+		return "OBJ_CABIN_BOY";
+	case 5:
+		return "OBJ_NARRATOR";
 	case 6:
 		return "OBJ_CORNEL";
 	case 7:
@@ -1468,13 +1478,47 @@ static const char *getObjectName(uint16_t idx) {
 	case 9:
 		return "OBJ_WOLF";
 	case 12:
-		return "OBJ_INDIAN";
+		return "OBJ_DROLL";
 	case 13:
 		return "OBJ_PATTERSON";
 	case 15:
 		return "OBJ_ROLLINS";
+	case 18:
+		return "OBJ_SHIP_GUARD";
 	case 19:
 		return "OBJ_HILTON";
+	case 22:
+		return "OBJ_WOMAN_SHIP";
+	case 33:
+		return "OBJ_SAILOR";
+	case 39:
+		return "OBJ_WAGON_OWNER";
+	case 53:
+		return "OBJ_THIEF";
+	case 69:
+		return "OBJ_SHERIFF";
+	case 77:
+		return "OBJ_INDIAN_COMPANION";
+	case 105:
+		return "OBJ_BARKEEPER";
+	case 110:
+		return "OBJ_COLONEL";
+	case 144:
+		return "OBJ_BANDIT_1";
+	case 145:
+		return "OBJ_BANDIT_2";
+	case 146:
+		return "OBJ_BANDIT_3";
+	case 147:
+		return "OBJ_WINNETOU";
+	case 149:
+		return "OBJ_CHIEF";
+	case 167:
+		return "OBJ_WINNETOU_INDIANS";
+	case 168:
+		return "OBJ_INDIAN_ELDER";
+	case 181:
+		return "OBJ_WINNETOU_ENDING";
 	// Items (full game, indices from _objectNames)
 	case 0x08:
 		return "OBJ_BOARD";
@@ -1788,8 +1832,12 @@ static std::string decodeCLine(uint8_t opcode, uint32_t endPos, int &indent) {
 			result2 += " // \"" + decoded + "\"";
 		return result2;
 	}
-	case 0x0E:
-		return "changeAnimation();";
+	case 0x0E: {
+		std::string bgAnim = formatValueC();
+		std::string frame = formatValueC();
+		snprintf(buf, sizeof(buf), "changeAnimation(%s, %s);", bgAnim.c_str(), frame.c_str());
+		return buf;
+	}
 	case 0x0F: {
 		std::string v = formatValueC();
 		snprintf(buf, sizeof(buf), "frameWait(%s);", v.c_str());
@@ -1989,13 +2037,13 @@ static std::string decodeCLine(uint8_t opcode, uint32_t endPos, int &indent) {
 	case 0x32: {
 		std::string o = formatValueC();
 		std::string v = formatValueC();
-		snprintf(buf, sizeof(buf), "setObjectClickable(%s, %s);", o.c_str(), v.c_str());
+		snprintf(buf, sizeof(buf), "setObjectShading(%s, %s);", o.c_str(), v.c_str());
 		return buf;
 	}
 	case 0x33: {
 		std::string o = formatValueC();
 		std::string v = formatValueC();
-		snprintf(buf, sizeof(buf), "setObjectVisible(%s, %s);", o.c_str(), v.c_str());
+		snprintf(buf, sizeof(buf), "setObjectScaling(%s, %s);", o.c_str(), v.c_str());
 		return buf;
 	}
 	case 0x34: {
@@ -2240,7 +2288,7 @@ int main(int argc, char **argv) {
 		printf("#include \"engines/macs2/scriptexecutor.h\"\n\n");
 		printf("using namespace Macs2::Script;\n\n");
 		printf("static ScriptExecutor _executor;\n\n");
-		printf("typedef uint16_t Object;\ntypedef uint16_t Value;\n\n");
+		printf("typedef uint16 Object;\ntypedef uint16 Value;\n\n");
 		printf("static const Value TRUE = 1;\n");
 		printf("static const Value FALSE = 0;\n");
 		printf("static const Value FADE_CUT = 0;\n");
@@ -2279,16 +2327,16 @@ int main(int argc, char **argv) {
 		printf("static void moveObject(Object obj, Value scene, Value x, Value y) { _executor.scriptMoveObject(); }\n");
 		printf("static void removeFromScene(Object obj) { _executor.scriptMoveObject(); }\n");
 		printf("static void changeScene(Value scene, Value mode, Value speed) { _executor.scriptChangeScene(); }\n");
-		printf("static void showDialogue(Object obj, Value x, Value y, Value side, uint16_t s, uint16_t n) { _executor.scriptShowDialogue(); }\n");
-		printf("static void printStringLeft(Value x, Value y, uint16_t s, uint16_t n) { _executor.scriptPrintStringLeft(); }\n");
-		printf("static void printStringRight(Value x, Value y, uint16_t s, uint16_t n) { _executor.scriptPrintStringRight(); }\n");
+		printf("static void showDialogue(Object obj, Value x, Value y, Value side, uint16 s, uint16 n) { _executor.scriptShowDialogue(); }\n");
+		printf("static void printStringLeft(Value x, Value y, uint16 s, uint16 n) { _executor.scriptPrintStringLeft(); }\n");
+		printf("static void printStringRight(Value x, Value y, uint16 s, uint16 n) { _executor.scriptPrintStringRight(); }\n");
 		printf("static void frameWait(Value t) { _executor.scriptFrameWait(); }\n");
 		printf("static void walkToPosition(Object o, Value x, Value y) { _executor.scriptWalkToPosition(); }\n");
 		printf("static void waitForWalk(Object o) { _executor.scriptWaitForWalk(); }\n");
 		printf("static void setPathfinding(Value a, Value b, Value c) { _executor.scriptSetPathfinding(); }\n");
-		printf("static void skipWord(uint16_t w) { _executor.scriptSkipWord(); }\n");
+		printf("static void skipWord(uint16 w) { _executor.scriptSkipWord(); }\n");
 		printf("static void clearDialogueChoices() { _executor.scriptClearDialogueChoices(); }\n");
-		printf("static void addDialogueChoice(Value i, uint16_t s, uint16_t n) { _executor.scriptAddDialogueChoice(); }\n");
+		printf("static void addDialogueChoice(Value i, uint16 s, uint16 n) { _executor.scriptAddDialogueChoice(); }\n");
 		printf("static void showDialogueChoice(Object o, Value x, Value y, Value s) { _executor.scriptShowDialogueChoice(); }\n");
 		printf("static void dismissPanel() { _executor.scriptDismissPanel(); }\n");
 		printf("static void walkToAndPickup(Object a, Object o) { _executor.scriptWalkToAndPickup(); }\n");
@@ -2302,36 +2350,36 @@ int main(int argc, char **argv) {
 		printf("static void setMotion(Object o, Value t, Value d, Value di) { _executor.scriptSetMotion(); }\n");
 		printf("static void setOrientation(Object o, Value a) { _executor.scriptSetOrientation(); }\n");
 		printf("static void moveToPosition(Object o, Value x, Value y, Value v) { _executor.scriptMoveToPosition(); }\n");
-		printf("static void loadSpecialAnim(Object o, Value d, uint8_t i) { _executor.scriptLoadSpecialAnim(); }\n");
+		printf("static void loadSpecialAnim(Object o, Value d, uint8 i) { _executor.scriptLoadSpecialAnim(); }\n");
 		printf("static void setDirection(Object o, Value m) { _executor.scriptSetDirection(); }\n");
 		printf("static void stopAnimation() { _executor.scriptStopAnimation(); }\n");
-		printf("static void changeAnimation() { _executor.scriptChangeAnimation(); }\n");
+		printf("static void changeAnimation(Value bgAnimIndex, Value targetFrame) { _executor.scriptChangeAnimation(); }\n");
 		printf("static void openInventory(Object o) { _executor.scriptOpenInventory(); }\n");
-		printf("static void loadObjectAnim(Object o, Value s, Value d, uint8_t i) { _executor.scriptLoadObjectAnim(); }\n");
+		printf("static void loadObjectAnim(Object o, Value s, Value d, uint8 i) { _executor.scriptLoadObjectAnim(); }\n");
 		printf("static void checkObjectData(Object o) { _executor.scriptCheckObjectData(); }\n");
 		printf("static Value checkInventory(Object o, Value v) { _executor.scriptCheckInventory(); return 0; }\n");
 		printf("static void setSnapToTarget(Object o, Value v) { _executor.scriptSetSnapToTarget(); }\n");
 		printf("static Value testSceneAnimFrame(Value a, Value lo, Value hi) { _executor.scriptTestSceneAnimFrame(); return 0; }\n");
 		printf("static Value testObjectAnimFrame(Object o, Value s, Value lo, Value hi) { _executor.scriptTestObjectAnimFrame(); return 0; }\n");
 		printf("static void setPaletteDarkness(Value v) { _executor.scriptSetPaletteDarkness(); }\n");
-		printf("static void setObjectClickable(Object o, Value v) { _executor.scriptSetObjectClickable(); }\n");
-		printf("static void setObjectVisible(Object o, Value v) { _executor.scriptSetObjectVisible(); }\n");
+		printf("static void setObjectShading(Object o, Value v) { _executor.scriptSetObjectShading(); }\n");
+		printf("static void setObjectScaling(Object o, Value v) { _executor.scriptSetObjectScaling(); }\n");
 		printf("static void setHotspotOverride(Value a, Value b) { _executor.scriptSetHotspotOverride(); }\n");
 		printf("static void setObjectBounds(Object o, Object p, Value a, Value b, Value c) { _executor.scriptSetObjectBounds(); }\n");
 		printf("static void dismissAllPanels() { _executor.scriptDismissAllPanels(); }\n");
 		printf("static void resetToSceneScript() { _executor.scriptResetToSceneScript(); }\n");
-		printf("static void loadOverlayFont(uint8_t r) { _executor.scriptLoadOverlayFont(); }\n");
+		printf("static void loadOverlayFont(uint8 r) { _executor.scriptLoadOverlayFont(); }\n");
 		printf("static void endOverlayText() { _executor.scriptEndOverlayText(); }\n");
-		printf("static void addOverlayTextEntry(Value x, Value y, Value a, uint16_t s, uint16_t t) { _executor.scriptAddOverlayTextEntry(); }\n");
+		printf("static void addOverlayTextEntry(Value x, Value y, Value a, uint16 s, uint16 t) { _executor.scriptAddOverlayTextEntry(); }\n");
 		printf("static void clearOverlayText() { _executor.scriptClearOverlayText(); }\n");
 		printf("static void fadeToBlack(Value s) { _executor.scriptFadeToBlack(); }\n");
 		printf("static void fadeFromBlack(Value s) { _executor.scriptFadeFromBlack(); }\n");
-		printf("static void loadPcmSound(uint8_t r) { _executor.scriptLoadPcmSound(); }\n");
+		printf("static void loadPcmSound(uint8 r) { _executor.scriptLoadPcmSound(); }\n");
 		printf("static void freePcmSound() { _executor.scriptFreePcmSound(); }\n");
 		printf("static void playPcmSound() { _executor.scriptPlayPcmSound(); }\n");
 		printf("static void waitForSound() { _executor.scriptWaitForSound(); }\n");
 		printf("static void stopPcmSound() { _executor.scriptStopPcmSound(); }\n");
-		printf("static void loadMusicSlot(Value s, uint8_t r) { _executor.scriptLoadMusicSlot(); }\n");
+		printf("static void loadMusicSlot(Value s, uint8 r) { _executor.scriptLoadMusicSlot(); }\n");
 		printf("static void playMusicSlot(Value s, Value a, Value b) { _executor.scriptPlayMusicSlot(); }\n");
 		printf("static void stopMusicSlot(Value s, Value a, Value b) { _executor.scriptStopMusicSlot(); }\n");
 		printf("static void freeMusicSlot(Value s) { _executor.scriptFreeMusicSlot(); }\n");


Commit: 0ea168367c700e7a7932b866252e7a34f12b1f82
    https://github.com/scummvm/scummvm-tools/commit/0ea168367c700e7a7932b866252e7a34f12b1f82
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-06-30T11:53:53+02:00

Commit Message:
MACS2: fixed unused var warning

Changed paths:
    engines/macs2/demacs2.cpp


diff --git a/engines/macs2/demacs2.cpp b/engines/macs2/demacs2.cpp
index 78677543..8228c3dc 100644
--- a/engines/macs2/demacs2.cpp
+++ b/engines/macs2/demacs2.cpp
@@ -1342,7 +1342,6 @@ static int runAmiga(const char *dir, bool jsonMode, bool objectsMode) {
 	// Iterate all resources sequentially
 	fseek(df, 14 + firstBlockSize, SEEK_SET);
 	bool first = true;
-	int resIdx = 0;
 	for (uint16_t i = 0; i < totalRes - 1; i++) {
 		uint8_t ehdr[8];
 		if (fread(ehdr, 1, 8, df) != 8)
@@ -1396,7 +1395,6 @@ static int runAmiga(const char *dir, bool jsonMode, bool objectsMode) {
 			stringData = nullptr;
 			stringDataSize = 0;
 		}
-		resIdx++;
 	}
 
 	if (jsonMode)




More information about the Scummvm-git-logs mailing list