[Scummvm-git-logs] scummvm-tools master -> 8fa80b2422540cc9741f1e27b6b9b03159e435ac

mgerhardy noreply at scummvm.org
Sun May 31 09:47:34 UTC 2026


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

Summary:
04eab9d1d0 MACS2: add disassembler
8fa80b2422 MACS2: data extractor


Commit: 04eab9d1d04bf392b50f3d2cc65087fb62527504
    https://github.com/scummvm/scummvm-tools/commit/04eab9d1d04bf392b50f3d2cc65087fb62527504
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-05-31T11:47:31+02:00

Commit Message:
MACS2: add disassembler

Changed paths:
  A engines/macs2/demacs2.cpp
    Makefile.common


diff --git a/Makefile.common b/Makefile.common
index f5546abb..c0aa7599 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -26,6 +26,7 @@ PROGRAMS = \
 	decine \
 	degob \
 	dekyra \
+	demacs2 \
 	deprince \
 	descumm \
 	desword2 \
@@ -137,6 +138,9 @@ dekyra_OBJS := \
 	engines/kyra/dekyra_v1.o \
 	$(UTILS)
 
+demacs2_OBJS := \
+	engines/macs2/demacs2.o
+
 extract_hadesch_OBJS := \
 	engines/hadesch/extract_hadesch.o \
 	$(UTILS)
diff --git a/engines/macs2/demacs2.cpp b/engines/macs2/demacs2.cpp
new file mode 100644
index 00000000..ea9bc5e2
--- /dev/null
+++ b/engines/macs2/demacs2.cpp
@@ -0,0 +1,738 @@
+/* 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 Script disassembler */
+
+#include <cstdio>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <vector>
+#include <string>
+
+static uint8_t *scriptData = nullptr;
+static uint32_t scriptSize = 0;
+static uint32_t pos = 0;
+
+static uint8_t readByte() {
+	if (pos >= scriptSize) return 0;
+	return scriptData[pos++];
+}
+
+static uint16_t readWord() {
+	uint8_t lo = readByte();
+	uint8_t hi = readByte();
+	return (uint16_t)hi << 8 | lo;
+}
+
+static std::string formatValue() {
+	uint8_t type = readByte();
+	uint16_t index = readWord();
+	if (type == 0x00) {
+		char buf[32];
+		snprintf(buf, sizeof(buf), "%u", index);
+		return buf;
+	} else if (type == 0xFF) {
+		// Special runtime values
+		const char *name = nullptr;
+		switch (index) {
+		case 0x01: name = "interacted_use"; break;
+		case 0x02: name = "interacted_look"; break;
+		case 0x03: name = "interacted_talk"; break;
+		case 0x04: name = "char_area"; break;
+		case 0x05: name = "noop"; break;
+		case 0x06: return "1";
+		case 0x07: return "0";
+		case 0x08: return "0";
+		case 0x09: return "0";
+		case 0x0A: return "1";
+		case 0x0B: name = "repeat_run_flag"; break;
+		case 0x0C: return "1";
+		case 0x0D: name = "chosen_dialogue_option"; break;
+		case 0x23: name = "path_walkable_result"; break;
+		case 0x24: name = "actor_x"; break;
+		case 0x25: name = "actor_y"; break;
+		case 0x26: name = "is_scene_init"; break;
+		case 0x27: name = "repeat_char_area"; break;
+		case 0x28: name = "inventory_check_result"; break;
+		case 0x29: name = "anim_range_test_result"; break;
+		case 0x2A: name = "inventory_combine_flag"; break;
+		case 0x2B: name = "inventory_action_flag"; break;
+		case 0x2C: name = "interacted_panel_use"; break;
+		case 0x2D: name = "current_scene"; break;
+		case 0x2E: return "2";
+		case 0x2F: name = "last_scene"; break;
+		case 0x30: name = "music_enabled"; break;
+		case 0x31: name = "sound_enabled"; break;
+		default:
+			if (index >= 0x0E && index <= 0x22) {
+				char buf[32];
+				snprintf(buf, sizeof(buf), "%u", index - 0x0D);
+				return buf;
+			}
+			char buf[32];
+			snprintf(buf, sizeof(buf), "$special_0x%02x", index);
+			return buf;
+		}
+		return std::string("$") + name;
+	} else {
+		char buf[32];
+		snprintf(buf, sizeof(buf), "var[%u]", index);
+		return buf;
+	}
+}
+
+// Read a value but return both words as "val" (for 32-bit context)
+static std::string formatValue32() {
+	return formatValue();
+}
+
+static std::string formatObjectId() {
+	std::string v = formatValue();
+	return v + " - 0x400";
+}
+
+static std::string formatSaveTarget() {
+	readByte(); // type (ignored for save target)
+	uint16_t index = readWord();
+	char buf[32];
+	snprintf(buf, sizeof(buf), "var[%u]", index);
+	return buf;
+}
+
+static const char *cmpOpName(uint8_t op) {
+	switch (op) {
+	case 0x01: return "==";
+	case 0x02: return "!=";
+	case 0x03: return "<";
+	case 0x04: return ">";
+	case 0x05: return "<=";
+	case 0x06: return ">=";
+	default: return "??";
+	}
+}
+
+static void disassemble() {
+	int indent = 0;
+
+	while (pos < scriptSize - 1) {
+		uint32_t instrAddr = pos;
+		uint8_t opcode = readByte();
+		if (opcode == 0x00) continue;
+
+		uint8_t length = readByte();
+		uint32_t endPos = pos + length;
+
+		// Adjust indent before printing for block-end opcodes
+		if (opcode == 0x07 && indent > 0) indent--;
+
+		// Print indent
+		for (int i = 0; i < indent; i++) printf("  ");
+		printf("[%04X] ", instrAddr);
+
+		switch (opcode) {
+		case 0x01: {
+			readByte(); // padding
+			uint16_t varIdx = readWord();
+			std::string val = formatValue();
+			printf("SET var[%u] = %s\n", varIdx, val.c_str());
+			break;
+		}
+		case 0x02: {
+			readByte(); // padding
+			uint16_t varIdx = readWord();
+			std::string val1 = formatValue();
+			std::string val2 = formatValue();
+			printf("SET_OR var[%u] = %s | %s\n", varIdx, val1.c_str(), val2.c_str());
+			break;
+		}
+		case 0x03: {
+			std::string val = formatValue();
+			printf("IF_TRUE %s THEN SKIP {\n", val.c_str());
+			indent++;
+			break;
+		}
+		case 0x04: {
+			std::string val = formatValue();
+			printf("IF_FALSE %s THEN SKIP {\n", val.c_str());
+			indent++;
+			break;
+		}
+		case 0x05: {
+			uint8_t cmpOp = readByte();
+			std::string v1 = formatValue();
+			std::string v2 = formatValue();
+			printf("IF %s %s %s {\n", v1.c_str(), cmpOpName(cmpOp), v2.c_str());
+			indent++;
+			break;
+		}
+		case 0x06: {
+			uint8_t subOp = readByte();
+			std::string obj1 = formatValue();
+			std::string obj2 = formatValue();
+			std::string cmp1 = formatValue();
+			std::string cmp2 = formatValue();
+			if (subOp == 0x01)
+				printf("IF_USE_ON (%s, %s) MATCHES (%s, %s) {\n", obj1.c_str(), obj2.c_str(), cmp1.c_str(), cmp2.c_str());
+			else
+				printf("IF_USE_ON (%s, %s) NOT_MATCHES (%s, %s) {\n", obj1.c_str(), obj2.c_str(), cmp1.c_str(), cmp2.c_str());
+			indent++;
+			break;
+		}
+		case 0x07: {
+			printf("}\n");
+			break;
+		}
+		case 0x08: {
+			printf("} ELSE {\n");
+			break;
+		}
+		case 0x0A: {
+			std::string x = formatValue();
+			std::string y = formatValue();
+			uint16_t strOffset = readWord();
+			uint16_t numLines = readWord();
+			printf("PRINT_STRING pos=(%s, %s) strOffset=%u numLines=%u\n", x.c_str(), y.c_str(), strOffset, numLines);
+			break;
+		}
+		case 0x0B: {
+			std::string objId = formatObjectId();
+			std::string scene = formatValue();
+			std::string x = formatValue();
+			std::string y = formatValue();
+			printf("MOVE_OBJECT obj=%s toScene=%s pos=(%s, %s)\n", objId.c_str(), scene.c_str(), x.c_str(), y.c_str());
+			break;
+		}
+		case 0x0C: {
+			std::string scene = formatValue32();
+			std::string mode = formatValue();
+			std::string speed = formatValue();
+			printf("CHANGE_SCENE scene=%s mode=%s speed=%s\n", scene.c_str(), mode.c_str(), speed.c_str());
+			break;
+		}
+		case 0x0D: {
+			std::string objId = formatObjectId();
+			std::string x = formatValue();
+			std::string y = formatValue();
+			std::string side = formatValue();
+			uint16_t strOffset = readWord();
+			uint16_t numLines = readWord();
+			printf("DIALOGUE speaker=%s pos=(%s, %s) side=%s strOffset=%u numLines=%u\n",
+				   objId.c_str(), x.c_str(), y.c_str(), side.c_str(), strOffset, numLines);
+			break;
+		}
+		case 0x0E: {
+			std::string id = formatValue32();
+			std::string frame = formatValue();
+			printf("CHANGE_ANIM id=%s frame=%s\n", id.c_str(), frame.c_str());
+			break;
+		}
+		case 0x0F: {
+			std::string duration = formatValue();
+			printf("WAIT_FRAMES %s\n", duration.c_str());
+			break;
+		}
+		case 0x10: {
+			std::string objId = formatObjectId();
+			std::string x = formatValue();
+			std::string y = formatValue();
+			printf("WALK_TO obj=%s pos=(%s, %s)\n", objId.c_str(), x.c_str(), y.c_str());
+			break;
+		}
+		case 0x11: {
+			std::string objId = formatObjectId();
+			printf("WAIT_WALK obj=%s\n", objId.c_str());
+			break;
+		}
+		case 0x12: {
+			std::string area = formatValue();
+			std::string active = formatValue();
+			std::string override_val = formatValue();
+			printf("SET_PATHFINDING area=%s active=%s override=%s\n", area.c_str(), active.c_str(), override_val.c_str());
+			break;
+		}
+		case 0x13: {
+			uint16_t tag = readWord();
+			printf("GOTO_TAG 0x%04X\n", tag);
+			break;
+		}
+		case 0x14: {
+			uint16_t tag = readWord();
+			printf("TAG 0x%04X\n", tag);
+			break;
+		}
+		case 0x15: {
+			printf("DIALOGUE_CHOICES_BEGIN\n");
+			break;
+		}
+		case 0x16: {
+			std::string idx = formatValue();
+			uint16_t strOffset = readWord();
+			uint16_t numLines = readWord();
+			printf("DIALOGUE_CHOICE index=%s strOffset=%u numLines=%u\n", idx.c_str(), strOffset, numLines);
+			break;
+		}
+		case 0x17: {
+			std::string x = formatValue32();
+			std::string y = formatValue32();
+			std::string side = formatValue();
+			printf("DIALOGUE_CHOICES_SHOW pos=(%s, %s) side=%s\n", x.c_str(), y.c_str(), side.c_str());
+			break;
+		}
+		case 0x18: {
+			printf("END_SCRIPT\n");
+			break;
+		}
+		case 0x19: {
+			std::string actor = formatObjectId();
+			std::string target = formatObjectId();
+			printf("PICKUP actor=%s target=%s\n", actor.c_str(), target.c_str());
+			break;
+		}
+		case 0x1A: {
+			std::string objId = formatObjectId();
+			std::string val217 = formatValue();
+			std::string val219 = formatValue();
+			printf("SET_OBJECT_RUNTIME obj=%s val217=%s val219=%s\n", objId.c_str(), val217.c_str(), val219.c_str());
+			break;
+		}
+		case 0x1B: {
+			std::string objId = formatObjectId();
+			std::string slot = formatValue();
+			std::string val = formatValue();
+			printf("SET_OBJECT_SLOT obj=%s slot=%s value=%s\n", objId.c_str(), slot.c_str(), val.c_str());
+			break;
+		}
+		case 0x1C: {
+			printf("SKIPPABLE_BEGIN\n");
+			break;
+		}
+		case 0x1D: {
+			printf("SKIPPABLE_END\n");
+			break;
+		}
+		case 0x1E: {
+			std::string objId = formatObjectId();
+			std::string slot = formatValue();
+			std::string frame = formatValue();
+			printf("PLAY_ANIM obj=%s slot=%s frame=%s\n", objId.c_str(), slot.c_str(), frame.c_str());
+			break;
+		}
+		case 0x1F: {
+			std::string objId = formatObjectId();
+			std::string x = formatValue32();
+			std::string y = formatValue32();
+			printf("TEST_PATH_WALKABLE obj=%s to=(%s, %s)\n", objId.c_str(), x.c_str(), y.c_str());
+			break;
+		}
+		case 0x20: {
+			std::string objId = formatObjectId();
+			std::string offset = formatValue();
+			printf("SET_Y_OFFSET obj=%s offset=%s\n", objId.c_str(), offset.c_str());
+			break;
+		}
+		case 0x21: {
+			std::string objId = formatObjectId();
+			std::string target = formatValue();
+			std::string delta = formatValue();
+			std::string dist = formatValue();
+			printf("SET_MOTION obj=%s targetOffset=%s delta=%s distance=%s\n", objId.c_str(), target.c_str(), delta.c_str(), dist.c_str());
+			break;
+		}
+		case 0x22: {
+			std::string objId = formatObjectId();
+			std::string orient = formatValue();
+			printf("SET_ORIENTATION obj=%s orientation=%s\n", objId.c_str(), orient.c_str());
+			break;
+		}
+		case 0x23: {
+			std::string objId = formatObjectId();
+			std::string x = formatValue32();
+			std::string y = formatValue32();
+			std::string targetOffset = formatValue();
+			printf("MOVE_TO_POSITION obj=%s pos=(%s, %s) targetOffset=%s\n", objId.c_str(), x.c_str(), y.c_str(), targetOffset.c_str());
+			break;
+		}
+		case 0x24: {
+			std::string a = formatValue32();
+			std::string b = formatValue32();
+			// The save target is embedded in the first value's position
+			// We can't perfectly reconstruct it without re-reading, so show the operands
+			printf("ADD %s + %s -> (save to first operand var)\n", a.c_str(), b.c_str());
+			break;
+		}
+		case 0x25: {
+			std::string a = formatValue32();
+			std::string b = formatValue32();
+			printf("SUB %s - %s -> (save to first operand var)\n", a.c_str(), b.c_str());
+			break;
+		}
+		case 0x26: {
+			std::string objId = formatObjectId();
+			std::string val = formatValue();
+			uint8_t animId = readByte();
+			printf("LOAD_SPECIAL_ANIM obj=%s val=%s animId=%u\n", objId.c_str(), val.c_str(), animId);
+			break;
+		}
+		case 0x27: {
+			std::string charId = formatValue();
+			std::string val = formatValue();
+			printf("SET_DIRECTION charId=%s value=%s\n", charId.c_str(), val.c_str());
+			break;
+		}
+		case 0x28: {
+			std::string objId = formatObjectId();
+			printf("STOP_ANIM obj=%s\n", objId.c_str());
+			break;
+		}
+		case 0x29: {
+			std::string objId = formatObjectId();
+			printf("OPEN_INVENTORY source=%s\n", objId.c_str());
+			break;
+		}
+		case 0x2A: {
+			std::string objId = formatObjectId();
+			std::string slot = formatValue();
+			std::string decode = formatValue();
+			uint8_t arrayIdx = readByte();
+			printf("LOAD_ANIM obj=%s slot=%s decode=%s arrayIdx=%u\n", objId.c_str(), slot.c_str(), decode.c_str(), arrayIdx);
+			break;
+		}
+		case 0x2B: {
+			std::string objId = formatValue();
+			printf("REFRESH_OBJECT obj=%s\n", objId.c_str());
+			break;
+		}
+		case 0x2C: {
+			std::string objId = formatValue();
+			std::string parentId = formatValue();
+			printf("CHECK_INVENTORY obj=%s parent=%s\n", objId.c_str(), parentId.c_str());
+			break;
+		}
+		case 0x2D: {
+			std::string objId = formatValue();
+			std::string enabled = formatValue();
+			printf("SET_RUNTIME_FLAG obj=%s enabled=%s\n", objId.c_str(), enabled.c_str());
+			break;
+		}
+		case 0x2E: {
+			std::string animIdx = formatValue32();
+			std::string minFrame = formatValue32();
+			std::string maxFrame = formatValue32();
+			printf("TEST_SCENE_ANIM_FRAME animIdx=%s min=%s max=%s\n", animIdx.c_str(), minFrame.c_str(), maxFrame.c_str());
+			break;
+		}
+		case 0x2F: {
+			std::string objId = formatObjectId();
+			std::string slot = formatValue();
+			std::string minFrame = formatValue();
+			std::string maxFrame = formatValue();
+			printf("TEST_OBJECT_ANIM_FRAME obj=%s slot=%s min=%s max=%s\n", objId.c_str(), slot.c_str(), minFrame.c_str(), maxFrame.c_str());
+			break;
+		}
+		case 0x30: {
+			std::string x = formatValue();
+			std::string y = formatValue();
+			uint16_t strOffset = readWord();
+			uint16_t numLines = readWord();
+			printf("PRINT_STRING_RIGHT pos=(%s, %s) strOffset=%u numLines=%u\n", x.c_str(), y.c_str(), strOffset, numLines);
+			break;
+		}
+		case 0x31: {
+			std::string vol = formatValue();
+			printf("SET_VOLUME %s\n", vol.c_str());
+			break;
+		}
+		case 0x32: {
+			std::string objId = formatValue();
+			std::string clickable = formatValue();
+			printf("SET_CLICKABLE obj=%s clickable=%s\n", objId.c_str(), clickable.c_str());
+			break;
+		}
+		case 0x33: {
+			std::string objId = formatValue();
+			std::string visible = formatValue();
+			printf("SET_VISIBLE obj=%s visible=%s\n", objId.c_str(), visible.c_str());
+			break;
+		}
+		case 0x34: {
+			std::string v1 = formatValue();
+			std::string v2 = formatValue();
+			printf("SET_HOTSPOT_OVERRIDE %s -> %s\n", v1.c_str(), v2.c_str());
+			break;
+		}
+		case 0x35: {
+			std::string objId = formatValue();
+			std::string otherId = formatValue();
+			std::string val1 = formatValue();
+			std::string val2 = formatValue();
+			std::string val3 = formatValue();
+			printf("SET_BOUNDS_ATTACHMENT obj=%s other=%s v1=%s v2=%s v3=%s\n",
+				   objId.c_str(), otherId.c_str(), val1.c_str(), val2.c_str(), val3.c_str());
+			break;
+		}
+		case 0x36: {
+			printf("DISMISS_PANEL\n");
+			break;
+		}
+		case 0x37: {
+			printf("RESET_TO_SCENE_SCRIPT\n");
+			break;
+		}
+		case 0x38: {
+			uint8_t resIdx = readByte();
+			printf("LOAD_OVERLAY_FONT resource=%u\n", resIdx);
+			break;
+		}
+		case 0x39: {
+			printf("END_OVERLAY_TEXT\n");
+			break;
+		}
+		case 0x3A: {
+			std::string x = formatValue();
+			std::string y = formatValue();
+			std::string align = formatValue();
+			uint16_t strOffset = readWord();
+			uint16_t entryType = readWord();
+			printf("OVERLAY_TEXT_ENTRY pos=(%s, %s) align=%s strOffset=%u type=%u\n",
+				   x.c_str(), y.c_str(), align.c_str(), strOffset, entryType);
+			break;
+		}
+		case 0x3B: {
+			printf("CLEAR_OVERLAY_TEXT\n");
+			break;
+		}
+		case 0x3C: {
+			std::string speed = formatValue();
+			printf("FADE_TO_BLACK speed=%s\n", speed.c_str());
+			break;
+		}
+		case 0x3D: {
+			std::string speed = formatValue();
+			printf("FADE_IN speed=%s\n", speed.c_str());
+			break;
+		}
+		case 0x3E: {
+			uint8_t resIdx = readByte();
+			printf("LOAD_SOUND resource=%u\n", resIdx);
+			break;
+		}
+		case 0x3F: {
+			printf("STOP_SOUND\n");
+			break;
+		}
+		case 0x40: {
+			printf("PLAY_SOUND\n");
+			break;
+		}
+		case 0x41: {
+			printf("WAIT_SOUND\n");
+			break;
+		}
+		case 0x42: {
+			printf("STOP_CURRENT_SOUND\n");
+			break;
+		}
+		case 0x43: {
+			std::string slot = formatValue();
+			uint8_t resIdx = readByte();
+			printf("LOAD_MUSIC slot=%s resource=%u\n", slot.c_str(), resIdx);
+			break;
+		}
+		case 0x44: {
+			std::string slot = formatValue();
+			std::string startMuted = formatValue();
+			std::string fadeParam = formatValue();
+			printf("PLAY_MUSIC slot=%s startMuted=%s fade=%s\n", slot.c_str(), startMuted.c_str(), fadeParam.c_str());
+			break;
+		}
+		case 0x45: {
+			std::string slot = formatValue();
+			std::string immediate = formatValue();
+			std::string fadeParam = formatValue();
+			printf("STOP_MUSIC slot=%s immediate=%s fade=%s\n", slot.c_str(), immediate.c_str(), fadeParam.c_str());
+			break;
+		}
+		case 0x46: {
+			std::string slot = formatValue();
+			printf("FREE_MUSIC slot=%s\n", slot.c_str());
+			break;
+		}
+		case 0x47: {
+			printf("WAIT_MUSIC\n");
+			break;
+		}
+		case 0x48: {
+			std::string objId = formatObjectId();
+			std::string target = formatSaveTarget();
+			printf("GET_OBJECT_X obj=%s -> %s\n", objId.c_str(), target.c_str());
+			break;
+		}
+		case 0x49: {
+			std::string objId = formatObjectId();
+			std::string target = formatSaveTarget();
+			printf("GET_OBJECT_Y obj=%s -> %s\n", objId.c_str(), target.c_str());
+			break;
+		}
+		case 0x4A: {
+			std::string objId = formatObjectId();
+			std::string target = formatSaveTarget();
+			printf("GET_OBJECT_FIELD obj=%s -> %s\n", objId.c_str(), target.c_str());
+			break;
+		}
+		case 0x4B: {
+			std::string objId = formatObjectId();
+			std::string target = formatSaveTarget();
+			printf("GET_OBJECT_ORIENTATION obj=%s -> %s\n", objId.c_str(), target.c_str());
+			break;
+		}
+		case 0x4C: {
+			printf("CLEAR_INVENTORY\n");
+			break;
+		}
+		case 0x4D: {
+			std::string src = formatValue();
+			std::string dst = formatValue();
+			printf("SET_AREA_REMAP %s -> %s\n", src.c_str(), dst.c_str());
+			break;
+		}
+		case 0x4E: {
+			printf("WAIT_ADLIB_READY\n");
+			break;
+		}
+		default: {
+			printf("UNKNOWN_OPCODE 0x%02X (len=%u)\n", opcode, length);
+			break;
+		}
+		}
+
+		// Ensure we advance to the correct position regardless of parsing
+		if (pos != endPos) {
+			pos = endPos;
+		}
+	}
+}
+
+static void printHelp(const char *bin) {
+	printf("MACS2 Script Disassembler\n\n");
+	printf("Usage: %s <game_data_file> [scene_index]\n\n", bin);
+	printf("  game_data_file  - The main game data file\n");
+	printf("  scene_index     - Scene number to disassemble (1-based)\n");
+	printf("                    If omitted, disassembles all scenes\n\n");
+	printf("The disassembled script will be written to stdout.\n");
+}
+
+static bool loadSceneScript(FILE *f, uint16_t sceneIndex) {
+	// Calculate offset: sceneIndex * 0xC + 0xC + 0x4 - 0x8
+	uint32_t offset = (uint32_t)sceneIndex * 0xC + 0xC + 0x4 - 0x8;
+	fseek(f, offset, SEEK_SET);
+
+	uint32_t sceneDataOffset;
+	if (fread(&sceneDataOffset, 4, 1, f) != 1) return false;
+	// Little-endian conversion (assuming host is LE for simplicity)
+	// For portability we should do proper byte swapping but this matches the game's target
+
+	fseek(f, sceneDataOffset + 0x80, SEEK_SET);
+
+	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;
+	return true;
+}
+
+int main(int argc, char **argv) {
+	if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+		printHelp(argv[0]);
+		return 1;
+	}
+
+	FILE *f = fopen(argv[1], "rb");
+	if (!f) {
+		fprintf(stderr, "Error: Cannot open file '%s'\n", argv[1]);
+		return 1;
+	}
+
+	int startScene = -1;
+	int endScene = -1;
+
+	if (argc >= 3) {
+		startScene = atoi(argv[2]);
+		endScene = startScene;
+	} else {
+		// Try all scenes from 1 to 512
+		startScene = 1;
+		endScene = 512;
+	}
+
+	printf("; MACS2 Script Runtime Variables (type 0xFF, read-only)\n");
+	printf("; These are computed by the engine at read-time, not stored in script data.\n");
+	printf("; $interacted_use         (FF:01) Object ID interacted with via Use/UseInventory cursor\n");
+	printf("; $interacted_look        (FF:02) Object ID interacted with via Look cursor\n");
+	printf("; $interacted_talk        (FF:03) Object ID interacted with via Talk cursor\n");
+	printf("; $char_area              (FF:04) Area ID at protagonist's current position\n");
+	printf("; $repeat_run_flag        (FF:0B) 1 during repeat/object script pass, 0 otherwise\n");
+	printf("; $chosen_dialogue_option (FF:0D) Index of last chosen dialogue option\n");
+	printf("; $path_walkable_result   (FF:23) 1 if last TEST_PATH_WALKABLE succeeded\n");
+	printf("; $actor_x                (FF:24) Protagonist X position\n");
+	printf("; $actor_y                (FF:25) Protagonist Y position\n");
+	printf("; $is_scene_init          (FF:26) 1 during scene initialization pass\n");
+	printf("; $repeat_char_area       (FF:27) Area at char position (only during repeat run)\n");
+	printf("; $inventory_check_result (FF:28) 1 if last CHECK_INVENTORY matched\n");
+	printf("; $anim_range_test_result (FF:29) 1 if last TEST_*_ANIM_FRAME matched\n");
+	printf("; $inventory_combine_flag (FF:2A) 1 if inventory combine pending (no UI open)\n");
+	printf("; $inventory_action_flag  (FF:2B) 1 if inventory action pending (no UI open)\n");
+	printf("; $interacted_panel_use   (FF:2C) Object ID interacted with via PanelUse cursor\n");
+	printf("; $current_scene          (FF:2D) Current scene index\n");
+	printf("; $last_scene             (FF:2F) Previous scene index\n");
+	printf("; $music_enabled          (FF:30) 1 if music enabled and sound system active\n");
+	printf("; $sound_enabled          (FF:31) 1 if sound enabled and sound system active\n");
+	printf(";\n");
+	printf("; Script variables: var[1]..var[2048] (read/write, all zeroed on scene load)\n");
+	printf("; Object IDs: raw value - 0x400 = object index (1..512)\n");
+	printf(";\n\n");
+
+	for (int scene = startScene; scene <= endScene; scene++) {
+		if (loadSceneScript(f, (uint16_t)scene)) {
+			printf("=== Scene %d (size: %u bytes) ===\n", scene, scriptSize);
+			disassemble();
+			printf("\n");
+			free(scriptData);
+			scriptData = nullptr;
+			scriptSize = 0;
+		}
+	}
+
+	fclose(f);
+	return 0;
+}


Commit: 8fa80b2422540cc9741f1e27b6b9b03159e435ac
    https://github.com/scummvm/scummvm-tools/commit/8fa80b2422540cc9741f1e27b6b9b03159e435ac
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-05-31T11:47:31+02:00

Commit Message:
MACS2: data extractor

Changed paths:
  A engines/macs2/extract_macs2.cpp
    Makefile.common


diff --git a/Makefile.common b/Makefile.common
index c0aa7599..b3bfc179 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -34,6 +34,7 @@ PROGRAMS = \
 	gob_loadcalc \
 	extract_gob_cdi \
 	extract_mohawk \
+	extract_macs2 \
 	extract_ngi \
 	construct_mohawk \
 	msn_convert_mod \
@@ -141,6 +142,9 @@ dekyra_OBJS := \
 demacs2_OBJS := \
 	engines/macs2/demacs2.o
 
+extract_macs2_OBJS := \
+	engines/macs2/extract_macs2.o
+
 extract_hadesch_OBJS := \
 	engines/hadesch/extract_hadesch.o \
 	$(UTILS)
diff --git a/engines/macs2/extract_macs2.cpp b/engines/macs2/extract_macs2.cpp
new file mode 100644
index 00000000..191e86b7
--- /dev/null
+++ b/engines/macs2/extract_macs2.cpp
@@ -0,0 +1,314 @@
+/* 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 Resource Extractor - extracts images, sounds, music, and strings */
+
+#include <cstdio>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <sys/stat.h>
+
+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);
+}
+
+// Scene table entry offsets
+static uint32_t getSceneBgOffset(uint16_t sceneIndex) {
+	fseek(resFile, 0xC + 0x4 + sceneIndex * 0xC - 0xC, SEEK_SET);
+	return readU32(resFile);
+}
+
+static uint32_t getSceneDataOffset(uint16_t sceneIndex) {
+	fseek(resFile, 0xC + 0x4 + sceneIndex * 0xC - 0x8, SEEK_SET);
+	return readU32(resFile);
+}
+
+static uint32_t getSceneStringsOffset(uint16_t sceneIndex) {
+	fseek(resFile, 0xC + 0x4 + sceneIndex * 0xC - 0x4, SEEK_SET);
+	return readU32(resFile);
+}
+
+// Decode an RLE-compressed 320x200 image
+static bool decodeRLEImage(uint32_t offset, uint8_t *pixels) {
+	fseek(resFile, offset, SEEK_SET);
+	for (int y = 0; y < 200; y++) {
+		uint16_t length = readU16(resFile);
+		if (length == 0 || length > 960) return false;
+		std::vector<uint8_t> rowData(length);
+		fread(rowData.data(), 1, length, resFile);
+		int remaining = 320;
+		int x = 0;
+		uint8_t *ptr = rowData.data();
+		uint8_t *end = ptr + length;
+		while (remaining > 0 && ptr < end) {
+			uint8_t val = *ptr++;
+			if (val != 0xF0) {
+				pixels[y * 320 + x++] = val;
+				remaining--;
+			} else {
+				if (ptr + 2 > end) break;
+				uint8_t runLen = *ptr++;
+				uint8_t runVal = *ptr++;
+				for (int i = 0; i < runLen && remaining > 0; i++) {
+					pixels[y * 320 + x++] = runVal;
+					remaining--;
+				}
+			}
+		}
+	}
+	return true;
+}
+
+// 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");
+	if (!f) { fprintf(stderr, "Cannot write %s\n", path); return; }
+
+	// BMP header
+	uint32_t imageSize = 320 * 200;
+	uint32_t paletteSize = 256 * 4;
+	uint32_t dataOffset = 14 + 40 + paletteSize;
+	uint32_t fileSize = dataOffset + imageSize;
+
+	// File header
+	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);
+
+	// DIB header (BITMAPINFOHEADER)
+	uint8_t dib[40] = {};
+	dib[0] = 40; // header size
+	dib[4] = 0x40; dib[5] = 0x01; // width = 320
+	dib[8] = 0xC8; // height = 200 (positive = bottom-up)
+	dib[12] = 1; // planes
+	dib[14] = 8; // bpp
+	fwrite(dib, 1, 40, f);
+
+	// Palette (BGRA)
+	for (int i = 0; i < 256; i++) {
+		// MACS2 palette is 6-bit VGA, scale to 8-bit
+		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);
+	}
+
+	// Pixel data (bottom-up: write rows in reverse)
+	for (int y = 199; y >= 0; y--) {
+		fwrite(pixels + y * 320, 1, 320, f);
+	}
+	fclose(f);
+	printf("  Wrote %s\n", path);
+}
+
+// Extract background image for a scene
+static void extractImage(uint16_t sceneIndex, const char *outDir) {
+	uint32_t bgOffset = getSceneBgOffset(sceneIndex);
+	if (bgOffset == 0) return;
+
+	uint8_t pixels[320 * 200];
+	if (!decodeRLEImage(bgOffset, pixels)) return;
+
+	// Palette follows immediately after the image
+	uint8_t palette[768];
+	fread(palette, 1, 768, resFile);
+
+	char path[512];
+	snprintf(path, sizeof(path), "%s/scene_%03d.bmp", outDir, sceneIndex);
+	writeBMP(path, pixels, palette);
+}
+
+// Extract indexed resources (sounds/music) for a scene
+static void extractResources(uint16_t sceneIndex, const char *outDir, const char *prefix) {
+	uint32_t dataOffset = getSceneDataOffset(sceneIndex);
+	if (dataOffset == 0) return;
+
+	fseek(resFile, dataOffset, SEEK_SET);
+	uint32_t resourceTable[32]; // 0x80 / 4 = 32 entries
+	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/%s_scene%03d_%02d.bin", outDir, prefix, sceneIndex, 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);
+		}
+	}
+}
+
+// Decrypt a string from the MACS2 format
+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;
+}
+
+// Extract strings for a scene
+static void extractStrings(uint16_t sceneIndex, const char *outDir) {
+	uint32_t strOffset = getSceneStringsOffset(sceneIndex);
+	if (strOffset == 0) return;
+
+	fseek(resFile, strOffset, 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_scene%03d.txt", outDir, sceneIndex);
+	FILE *out = fopen(path, "w");
+	if (!out) return;
+
+	fprintf(out, "; Scene %d strings\n", sceneIndex);
+	fprintf(out, "; Total data size: %u bytes\n\n", totalSize);
+
+	// Walk through the string data - each string is: uint16 length + encrypted bytes
+	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);
+#else
+	mkdir(path, 0755);
+#endif
+}
+
+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("  all      - Extract everything\n");
+	printf("\n");
+	printf("If scene_index is omitted, extracts from all scenes.\n");
+}
+
+int main(int argc, char **argv) {
+	if (argc < 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+		printHelp(argv[0]);
+		return 1;
+	}
+
+	const char *mode = argv[1];
+	const char *resPath = argv[2];
+	const char *outDir = argv[3];
+
+	resFile = fopen(resPath, "rb");
+	if (!resFile) {
+		fprintf(stderr, "Error: Cannot open '%s'\n", resPath);
+		return 1;
+	}
+
+	mkdirp(outDir);
+
+	int startScene = 1, endScene = 512;
+	if (argc >= 5) {
+		startScene = endScene = atoi(argv[4]);
+	}
+
+	bool doImages = !strcmp(mode, "images") || !strcmp(mode, "all");
+	bool doSounds = !strcmp(mode, "sounds") || !strcmp(mode, "all");
+	bool doStrings = !strcmp(mode, "strings") || !strcmp(mode, "all");
+
+	for (int scene = startScene; scene <= endScene; scene++) {
+		bool hasData = false;
+
+		if (doImages) {
+			uint32_t bgOff = getSceneBgOffset(scene);
+			if (bgOff != 0) {
+				if (!hasData) { printf("Scene %d:\n", scene); hasData = true; }
+				extractImage(scene, outDir);
+			}
+		}
+
+		if (doSounds) {
+			uint32_t dataOff = getSceneDataOffset(scene);
+			if (dataOff != 0) {
+				if (!hasData) { printf("Scene %d:\n", scene); hasData = true; }
+				extractResources(scene, outDir, "res");
+			}
+		}
+
+		if (doStrings) {
+			uint32_t strOff = getSceneStringsOffset(scene);
+			if (strOff != 0) {
+				if (!hasData) { printf("Scene %d:\n", scene); hasData = true; }
+				extractStrings(scene, outDir);
+			}
+		}
+	}
+
+	fclose(resFile);
+	return 0;
+}




More information about the Scummvm-git-logs mailing list