[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