[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