[Scummvm-git-logs] scummvm-tools master -> 774fa09a4e8310703392864bd5d0b753554a190a
mgerhardy
noreply at scummvm.org
Mon Aug 31 19:43:27 UTC 2026
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm-tools' repo located at https://api.github.com/repos/scummvm/scummvm-tools .
Summary:
2824fe1623 MACS2: fixed amiga extraction and allow to export portraits, too
0e9e8bc761 MACS2: support for ui label translations - like the verbs
774fa09a4e MACS2: reset version back to 1 - the game was never officially supported
Commit: 2824fe1623795a2edafa769bb1a6cc8854d5d029
https://github.com/scummvm/scummvm-tools/commit/2824fe1623795a2edafa769bb1a6cc8854d5d029
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-07-27T18:40:42+02:00
Commit Message:
MACS2: fixed amiga extraction and allow to export portraits, too
Changed paths:
engines/macs2/extract_macs2.cpp
diff --git a/engines/macs2/extract_macs2.cpp b/engines/macs2/extract_macs2.cpp
index dbcd7477..cb74df87 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 <map>
#include <set>
#include <string>
#include <vector>
@@ -875,6 +876,86 @@ static void extractMusic(const char *outDir) {
printf("Extracted %d music/sound resources.\n", count);
}
+// Parse one animation blob frame table and write frame 0 as BMP.
+// Returns true if a frame was written. Shared by items + dialogue portraits.
+static bool writeAnimBlobFrameBmp(const char *path, const std::vector<uint8_t> &blob,
+ const uint8_t *palette, uint16_t frameIndex = 0) {
+ if (blob.size() < 12)
+ return false;
+ uint16_t seqLen = (uint16_t)(blob[10] | (blob[11] << 8)) + 1;
+ uint32_t frameTableOffset = 0xB + seqLen; // matches engine: stream.seek(0xB + bp0E)
+ if (frameTableOffset + 2 > blob.size())
+ return false;
+ uint16_t frameCount = blob[frameTableOffset] | (blob[frameTableOffset + 1] << 8);
+ if (frameCount == 0 || frameIndex >= frameCount)
+ return false;
+ uint32_t p = frameTableOffset + 2;
+ for (uint16_t f = 0; f <= frameIndex; f++) {
+ if (p + 10 > blob.size())
+ return false;
+ uint16_t w = blob[p + 6] | (blob[p + 7] << 8);
+ uint16_t h = blob[p + 8] | (blob[p + 9] << 8);
+ p += 10;
+ if (w == 0 || h == 0 || p + (uint32_t)w * h > blob.size())
+ return false;
+ if (f == frameIndex) {
+ writeBMPEx(path, blob.data() + p, w, h, palette);
+ return true;
+ }
+ p += (uint32_t)w * h;
+ }
+ return false;
+}
+
+// Extract dialogue portrait images (blob slots 0x11 / 0x12) for DOS validation.
+static void extractPortraits(const char *outDir) {
+ uint8_t palette[768] = {};
+ fseek(resFile, 0x3010, SEEK_SET);
+ fread(palette, 1, 768, resFile);
+
+ printf("Extracting dialogue portraits (DOS blob slots 0x11/0x12)...\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);
+ fseek(resFile, 10, SEEK_CUR); // x,y,scene,orient,vscale
+
+ std::vector<uint8_t> portraitBlobs[2];
+ 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 == 0x11 || j == 0x12) && dataSize > 0) {
+ std::vector<uint8_t> blob(dataSize);
+ fread(blob.data(), 1, dataSize, resFile);
+ portraitBlobs[j - 0x11] = std::move(blob);
+ } else {
+ fseek(resFile, dataSize, SEEK_CUR);
+ }
+ fseek(resFile, 4, SEEK_CUR); // speed + mirror + pad
+ }
+
+ for (int slot = 0; slot < 2; slot++) {
+ if (portraitBlobs[slot].empty())
+ continue;
+ char path[512];
+ snprintf(path, sizeof(path), "%s/portrait_%03d_slot%02d_f0.bmp", outDir, i, 0x11 + slot);
+ if (writeAnimBlobFrameBmp(path, portraitBlobs[slot], palette, 0)) {
+ count++;
+ // Also dump frame 1 when present (mouth variant).
+ snprintf(path, sizeof(path), "%s/portrait_%03d_slot%02d_f1.bmp", outDir, i, 0x11 + slot);
+ writeAnimBlobFrameBmp(path, portraitBlobs[slot], palette, 1);
+ }
+ }
+ }
+ printf("Extracted %d dialogue portrait BMPs.\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
@@ -1155,6 +1236,241 @@ static bool isAmigaDataDir(const char *path) {
stat(mdir.c_str(), &st) == 0 && S_ISREG(st.st_mode));
}
+static bool decodeAmigaPlanarFrame(const uint8_t *planar, uint16_t width, uint16_t height,
+ uint16_t frameIndex, uint16_t frameCount,
+ std::vector<uint8_t> &outPixels) {
+ const uint32_t rowBytes = (width + 7) / 8;
+ const uint32_t planeBytes = rowBytes * height;
+ const uint32_t frameBytes = planeBytes * 6;
+ if (frameIndex >= frameCount)
+ return false;
+ const uint8_t *frameBase = planar + (uint32_t)frameIndex * frameBytes;
+ outPixels.assign((size_t)width * height, 0);
+ for (uint16_t y = 0; y < height; y++) {
+ for (uint16_t x = 0; x < width; x++) {
+ uint8_t color = 0;
+ const uint32_t bitIndex = x & 7;
+ const uint32_t byteInRow = x >> 3;
+ for (int plane = 0; plane < 5; plane++) {
+ const uint8_t *planeRow = frameBase + plane * planeBytes + y * rowBytes;
+ if (planeRow[byteInRow] & (0x80 >> bitIndex))
+ color |= (uint8_t)(1 << plane);
+ }
+ // Anim slots: planes 0..4 are color. Do not use plane 5 as a mask
+ // (clears nearly all pixels on character anims in the Amiga demo).
+ outPixels[y * width + x] = color;
+ }
+ }
+ return true;
+}
+
+// Complex MXOO: body+0x0C = 0x0101, then 21 x u32BE slot offsets, extra at +0x62.
+static int extractAmigaComplexMxoo(const uint8_t *mxoo, uint32_t mxooSize, const char *type,
+ uint16_t id, const char *outDir, const uint8_t *palette,
+ int &imagesOut) {
+ if (mxooSize < 24 || memcmp(mxoo, "MXOO", 4) != 0)
+ return 0;
+ uint32_t scriptOff = readU32BE(mxoo + 4);
+ uint32_t stringOff = readU32BE(mxoo + 8);
+ if (scriptOff <= 12 + 0x66 || scriptOff > mxooSize)
+ return 0;
+ const uint8_t *body = mxoo + 12;
+ if (readU16BE(body + 0x0C) != 0x0101)
+ return 0;
+
+ uint32_t slots[21];
+ for (int i = 0; i < 21; i++)
+ slots[i] = readU32BE(body + 0x0E + i * 4);
+ uint32_t extraOff = readU32BE(body + 0x62);
+
+ char path[512];
+ char fname[96];
+ int animFrames = 0;
+
+ // Object metadata JSON
+ snprintf(fname, sizeof(fname), "%s_%04u_object.json", type, id);
+ snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+ FILE *jf = fopen(path, "wb");
+ if (jf) {
+ fprintf(jf, "{\n \"resourceType\": \"%s\",\n \"resourceId\": %u,\n \"objectIndex\": %u,\n",
+ type, id, id + 1);
+ fprintf(jf, " \"scriptOffset\": %u,\n \"stringOffset\": %u,\n \"extraOffset\": %u,\n",
+ scriptOff, stringOff, extraOff);
+ fprintf(jf, " \"slots\": [\n");
+ for (int i = 0; i < 21; i++) {
+ fprintf(jf, " {\"index\": %d, \"offset\": ", i);
+ if (slots[i] == 0 || slots[i] == 0xFFFFFFFFu)
+ fprintf(jf, "null");
+ else
+ fprintf(jf, "%u", slots[i]);
+ fprintf(jf, "}%s\n", i + 1 < 21 ? "," : "");
+ }
+ fprintf(jf, " ],\n \"animations\": [\n");
+ }
+
+ bool firstAnim = true;
+ for (int slot = 0; slot < 21; slot++) {
+ if (slots[slot] == 0 || slots[slot] == 0xFFFFFFFFu)
+ continue;
+ uint32_t absOff = 12 + slots[slot];
+ if (absOff + 16 > mxooSize)
+ continue;
+ const uint8_t *p = mxoo + absOff;
+ uint16_t hint = readU16BE(p + 0);
+ uint16_t seqPos = readU16BE(p + 2);
+ uint16_t repeatCounter = readU16BE(p + 4);
+ uint16_t loopStart = readU16BE(p + 6);
+ uint16_t seqBytes = (hint >= 2) ? (uint16_t)(hint - 2) : 0;
+ uint16_t seqPadded = (uint16_t)((seqBytes + 1) & ~1);
+ uint32_t metaOff = absOff + 8 + seqPadded;
+ if (metaOff + 6 > mxooSize)
+ continue;
+ uint16_t fc = readU16BE(mxoo + metaOff + 0);
+ uint16_t w = readU16BE(mxoo + metaOff + 2);
+ uint16_t h = readU16BE(mxoo + metaOff + 4);
+ uint32_t pixelOff = metaOff + 6;
+ if (fc == 0 || w == 0 || w > 320 || h == 0 || h > 200 || fc > 256)
+ continue;
+ uint32_t rowBytes = (w + 7) / 8;
+ uint32_t planeBytes = rowBytes * h;
+ uint32_t pixelsNeeded = (uint32_t)fc * planeBytes * 6;
+ if (pixelOff + pixelsNeeded > mxooSize)
+ continue;
+
+ if (jf) {
+ if (!firstAnim)
+ fprintf(jf, ",\n");
+ firstAnim = false;
+ fprintf(jf,
+ " {\"slot\": %d, \"hint\": %u, \"seqPos\": %u, \"repeat\": %u, \"loopStart\": %u, "
+ "\"frameCount\": %u, \"width\": %u, \"height\": %u}",
+ slot, hint, seqPos, repeatCounter, loopStart, fc, w, h);
+ }
+
+ const uint8_t *planar = mxoo + pixelOff;
+ for (uint16_t f = 0; f < fc; f++) {
+ std::vector<uint8_t> pixels;
+ if (!decodeAmigaPlanarFrame(planar, w, h, f, fc, pixels))
+ continue;
+ snprintf(fname, sizeof(fname), "%s_%04u_slot%02d_f%02u.bmp", type, id, slot, f);
+ snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+ writeBMPEx(path, pixels.data(), w, h, palette);
+ imagesOut++;
+ animFrames++;
+ }
+ }
+
+ // Dialogue portrait (body+0x62 / slot 0x15): animateDialoguePortrait @ 0022f79c
+ // copies 14400 bytes and blits with D3=D4=D6=0x50 (80), D5=0xF0 (240).
+ // Full drawSprite plane loop: 6 separated planes, src width 240, height 80
+ // -> 30*80*6 = 14400. Color from planes 0..4 (plane 5 = cookie mask on HW).
+ // Atlas is 3Ã 80Ã80 frames side-by-side (not 320Ã72Ã5).
+ bool dumpedPortrait = false;
+ if (extraOff != 0 && extraOff != 0xFFFFFFFFu) {
+ const uint32_t absExtra = 12 + extraOff;
+ const uint16_t atlasW = 240;
+ const uint16_t atlasH = 80;
+ const uint16_t frameW = 80;
+ const uint16_t frameCount = 3;
+ const uint32_t rowBytes = (atlasW + 7) / 8; // 30
+ const uint32_t planeBytes = rowBytes * atlasH; // 2400
+ const uint32_t portraitBytes = planeBytes * 6; // 14400
+ if (absExtra + portraitBytes <= mxooSize &&
+ (scriptOff == 0 || absExtra + portraitBytes <= scriptOff)) {
+ const uint8_t *src = mxoo + absExtra;
+ std::vector<uint8_t> atlas((size_t)atlasW * atlasH, 0);
+ for (uint16_t y = 0; y < atlasH; y++) {
+ for (uint16_t x = 0; x < atlasW; x++) {
+ uint8_t color = 0;
+ const uint32_t bitIndex = x & 7;
+ const uint32_t byteInRow = x >> 3;
+ for (int plane = 0; plane < 5; plane++) {
+ const uint8_t *planeRow = src + plane * planeBytes + y * rowBytes;
+ if (planeRow[byteInRow] & (0x80 >> bitIndex))
+ color |= (uint8_t)(1 << plane);
+ }
+ atlas[y * atlasW + x] = color;
+ }
+ }
+ snprintf(fname, sizeof(fname), "%s_%04u_portrait.bmp", type, id);
+ snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+ writeBMPEx(path, atlas.data(), atlasW, atlasH, palette);
+ imagesOut++;
+ for (uint16_t f = 0; f < frameCount; f++) {
+ std::vector<uint8_t> frame((size_t)frameW * atlasH, 0);
+ for (uint16_t y = 0; y < atlasH; y++) {
+ memcpy(frame.data() + y * frameW,
+ atlas.data() + y * atlasW + f * frameW,
+ frameW);
+ }
+ snprintf(fname, sizeof(fname), "%s_%04u_portrait_f%02u.bmp", type, id, f);
+ snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+ writeBMPEx(path, frame.data(), frameW, atlasH, palette);
+ imagesOut++;
+ }
+ dumpedPortrait = true;
+ }
+ }
+
+ if (jf) {
+ fprintf(jf, "\n ],\n");
+ fprintf(jf, " \"portraitDumped\": %s,\n", dumpedPortrait ? "true" : "false");
+
+ // Script size
+ uint16_t scriptSize = 0;
+ if (scriptOff + 4 <= mxooSize)
+ scriptSize = readU16BE(mxoo + scriptOff + 2);
+ fprintf(jf, " \"scriptSize\": %u,\n", scriptSize);
+
+ // Strings
+ fprintf(jf, " \"strings\": [\n");
+ if (stringOff + 4 <= mxooSize) {
+ uint16_t strSize = readU16BE(mxoo + stringOff + 2);
+ uint32_t pos = stringOff + 4;
+ uint32_t end = pos + strSize;
+ bool first = true;
+ int idx = 0;
+ while (pos + 2 <= end && pos + 2 <= mxooSize) {
+ uint32_t entryOff = pos - (stringOff + 4);
+ uint16_t len = readU16BE(mxoo + pos);
+ pos += 2;
+ if (pos + len > end || pos + len > mxooSize)
+ break;
+ if (!first)
+ fprintf(jf, ",\n");
+ first = false;
+ fprintf(jf, " {\"index\": %d, \"offset\": %u, \"text\": \"", idx, (unsigned)entryOff);
+ for (uint16_t i = 0; i < len; i++) {
+ uint8_t c = mxoo[pos + i];
+ if (c == '"' || c == '\\')
+ fputc('\\', jf);
+ if (c >= 32 && c < 127)
+ fputc(c, jf);
+ else
+ fprintf(jf, "\\u%04x", c);
+ }
+ fprintf(jf, "\"}");
+ pos += len;
+ idx++;
+ }
+ }
+ fprintf(jf, "\n ]\n}\n");
+ fclose(jf);
+ }
+
+ // Also dump raw script bytecode
+ if (scriptOff + 4 <= mxooSize) {
+ uint16_t scriptSize = readU16BE(mxoo + scriptOff + 2);
+ if (scriptSize > 0 && scriptOff + 4 + scriptSize <= mxooSize) {
+ snprintf(fname, sizeof(fname), "%s_%04u_script.bin", type, id);
+ snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+ writeRawFile(path, mxoo + scriptOff + 4, scriptSize);
+ }
+ }
+
+ return (animFrames > 0 || dumpedPortrait) ? 1 : 0;
+}
+
static int extractAmiga(const char *gameDir, const char *outDir) {
std::string dataAPath = std::string(gameDir) + "/DataA";
std::string mdirPath = std::string(gameDir) + "/Mdir";
@@ -1242,13 +1558,127 @@ static int extractAmiga(const char *gameDir, const char *outDir) {
}
}
- // Default 32-color Amiga palette (grayscale fallback for sprites without scene palette)
+ // Seed COLOR0..31 from Info MXIN UI colors + synthetic face banks (same
+ // 12-bit values as engines/macs2 installAmigaPortraitPalette). writeBMPEx
+ // expects 6-bit VGA components.
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;
+ memset(defaultPalette, 0, sizeof(defaultPalette));
+ auto amiga12ToVga6 = [](uint16_t rgb, uint8_t &r6, uint8_t &g6, uint8_t &b6) {
+ uint8_t r4 = (rgb >> 8) & 0xF;
+ uint8_t g4 = (rgb >> 4) & 0xF;
+ uint8_t b4 = rgb & 0xF;
+ r6 = (uint8_t)((r4 * 63) / 15);
+ g6 = (uint8_t)((g4 * 63) / 15);
+ b6 = (uint8_t)((b4 * 63) / 15);
+ };
+ static const uint16_t kLowBank[16] = {
+ 0x0320, 0x0530, 0x0741, 0x0852, 0x0A64, 0x0B75, 0x0C86, 0x0D97,
+ 0x0EA8, 0x0EB8, 0x0DDC, 0x0EEE, 0x0C96, 0x0A74, 0x0887, 0x0776
+ };
+ static const uint16_t kHighBank[15] = {
+ 0x049E, 0x06BF, 0x08DF, 0x0C00, 0x0A00, 0x0800, 0x0600, 0x0EB8,
+ 0x0C96, 0x0A74, 0x0963, 0x0741, 0x0887, 0x0DDC, 0x0000
+ };
+ for (int i = 0; i < 16; i++) {
+ uint8_t r6, g6, b6;
+ amiga12ToVga6(kLowBank[i], r6, g6, b6);
+ defaultPalette[(1 + i) * 3 + 0] = r6;
+ defaultPalette[(1 + i) * 3 + 1] = g6;
+ defaultPalette[(1 + i) * 3 + 2] = b6;
+ }
+ for (int i = 0; i < 15; i++) {
+ uint8_t r6, g6, b6;
+ amiga12ToVga6(kHighBank[i], r6, g6, b6);
+ defaultPalette[(17 + i) * 3 + 0] = r6;
+ defaultPalette[(17 + i) * 3 + 1] = g6;
+ defaultPalette[(17 + i) * 3 + 2] = b6;
+ }
+ // Optional: log Info presence; keep synthetic COLOR1..31 for face tones
+ // (Info UI RGB is chrome, not portrait skin â see installAmigaPortraitPalette).
+ {
+ std::string infoPath = std::string(gameDir) + "/Info";
+ FILE *inf = fopen(infoPath.c_str(), "rb");
+ if (inf) {
+ uint8_t hdr[8];
+ if (fread(hdr, 1, sizeof(hdr), inf) == sizeof(hdr) &&
+ memcmp(hdr, "MXIN", 4) == 0 && readU16BE(hdr + 4) == 1) {
+ printf(" Using synthetic Amiga COLOR1..31 portrait palette (Info MXIN present)\n");
+ }
+ fclose(inf);
+ }
+ }
+
+ // Prefer a real scene copper palette for OO sprites/portraits (COLOR0..31).
+ // OO entries often appear before MM in DataA, so scan ahead once.
+ {
+ long savedPos = ftell(df);
+ fseek(df, 14 + firstBlockSize, SEEK_SET);
+ bool gotCopper = false;
+ for (uint16_t i = 0; i < totalResources - 1 && !gotCopper; i++) {
+ uint8_t ehdr[8];
+ if (fread(ehdr, 1, 8, df) != 8)
+ break;
+ char eType[3] = {(char)ehdr[0], (char)ehdr[1], '\0'};
+ uint32_t eCompSize = readU32BE(ehdr + 4);
+ if (eCompSize == 0 || eCompSize > dataASize)
+ break;
+ std::vector<uint8_t> payload(eCompSize);
+ if (fread(payload.data(), 1, eCompSize, df) != eCompSize)
+ break;
+ if (eType[0] != 'M' || eType[1] != 'M' || eCompSize < 16)
+ continue;
+ if (memcmp(payload.data(), "MXMM", 4) != 0)
+ continue;
+ // MXMM: size-prefixed chunks from offset 10; chunk0 = PP20 screen+copper
+ uint32_t pos = 10;
+ while (pos + 4 <= eCompSize) {
+ uint32_t chunkSize = readU32BE(payload.data() + pos);
+ pos += 4;
+ if (chunkSize == 0 || pos + chunkSize > eCompSize)
+ break;
+ const uint8_t *chunk = payload.data() + pos;
+ pos += chunkSize;
+ if (memcmp(chunk, "PP20", 4) != 0)
+ continue;
+ uint32_t origSize = pp20GetOrigSize(chunk, chunkSize);
+ if (origSize < 0xBB80 + 64)
+ continue;
+ std::vector<uint8_t> dec(origSize);
+ if (!pp20Decompress(chunk, chunkSize, dec.data(), origSize))
+ continue;
+ const uint32_t kCopper = 0xBB80;
+ auto amiga12ToRgb8 = [](uint16_t rgb, uint8_t &r, uint8_t &g, uint8_t &b) {
+ uint8_t r4 = (rgb >> 8) & 0xF;
+ uint8_t g4 = (rgb >> 4) & 0xF;
+ uint8_t b4 = rgb & 0xF;
+ r = (uint8_t)(r4 * 17);
+ g = (uint8_t)(g4 * 17);
+ b = (uint8_t)(b4 * 17);
+ };
+ uint16_t base16[16];
+ for (int bi = 0; bi < 16; bi++)
+ base16[bi] = readU16BE(dec.data() + kCopper + bi * 2);
+ const uint8_t *lineColors = dec.data() + kCopper + 0x20;
+ uint8_t pal32[32][3];
+ amiga12ToRgb8(base16[0], pal32[0][0], pal32[0][1], pal32[0][2]);
+ for (int ci = 0; ci < 16; ci++) {
+ uint16_t c = readU16BE(lineColors + ci * 2);
+ amiga12ToRgb8(c, pal32[1 + ci][0], pal32[1 + ci][1], pal32[1 + ci][2]);
+ }
+ for (int ci = 1; ci < 16; ci++)
+ amiga12ToRgb8(base16[ci], pal32[16 + ci][0], pal32[16 + ci][1], pal32[16 + ci][2]);
+ for (int si = 0; si < 32; si++) {
+ defaultPalette[si * 3 + 0] = (uint8_t)((pal32[si][0] * 63) / 255);
+ defaultPalette[si * 3 + 1] = (uint8_t)((pal32[si][1] * 63) / 255);
+ defaultPalette[si * 3 + 2] = (uint8_t)((pal32[si][2] * 63) / 255);
+ }
+ printf(" Seeded OO/portrait palette from MM_%04u copper (COLOR0..31)\n",
+ readU16BE(ehdr + 2));
+ gotCopper = true;
+ break;
+ }
+ }
+ fseek(df, savedPos, SEEK_SET);
}
// Extract all resources sequentially from offset 14 + firstBlockSize
@@ -1283,13 +1713,26 @@ static int extractAmiga(const char *gameDir, const char *outDir) {
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);
+ if (origSize >= 4 && memcmp(dec.data(), "MXOS", 4) == 0) {
+ snprintf(fname, sizeof(fname), "%s_%04d.mxos", eType, eId);
+ snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+ writeRawFile(path, dec.data(), origSize);
+ printf(" %s: PP20 %u -> %u bytes (PCM sound container)\n",
+ fname, eCompSize, origSize);
+ } else {
+ 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) {
+ int beforeImages = images;
+ if (extractAmigaComplexMxoo(dec.data(), origSize, eType, eId, outDir, defaultPalette, images))
+ printf(" %s_%04d: complex object (+%d anim BMPs, JSON, script)\n",
+ eType, eId, images - beforeImages);
+
uint32_t scriptOff = readU32BE(dec.data() + 4);
// Sprite body: from offset 12 to scriptOff
if (scriptOff > 32 && scriptOff <= origSize) {
@@ -1339,36 +1782,174 @@ static int extractAmiga(const char *gameDir, const char *outDir) {
}
}
} 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);
+ // Scene package: size-prefixed chunks; chunk0 = 6Ã8000 EHB planes + copper.
+ snprintf(fname, sizeof(fname), "%s_%04d.mxmm", eType, eId);
+ snprintf(path, sizeof(path), "%s/%s", outDir, fname);
+ writeRawFile(path, payload.data(), eCompSize);
+
+ uint32_t pos = 10;
+ int chunkIndex = 0;
+ bool wroteBg = false;
+ while (pos + 4 <= eCompSize) {
+ uint32_t chunkSize = readU32BE(payload.data() + pos);
+ pos += 4;
+ if (chunkSize == 0 || pos + chunkSize > eCompSize)
+ break;
+ const uint8_t *chunk = payload.data() + pos;
+ pos += chunkSize;
+
+ char chunkName[80];
+ snprintf(chunkName, sizeof(chunkName), "%s_%04d_chunk%02d", eType, eId, chunkIndex);
+
+ if (memcmp(chunk, "PP20", 4) == 0) {
+ uint32_t origSize = pp20GetOrigSize(chunk, chunkSize);
+ if (origSize > 0) {
+ std::vector<uint8_t> dec(origSize);
+ if (pp20Decompress(chunk, chunkSize, dec.data(), origSize)) {
+ snprintf(path, sizeof(path), "%s/%s.bin", outDir, chunkName);
+ writeRawFile(path, dec.data(), origSize);
+ printf(" %s: PP20 %u -> %u bytes\n", chunkName, chunkSize, origSize);
+
+ // BPLCON0=0x6200 -> 6 planes EHB; copper at 0xBB80 (FUN_00221f72 / FUN_0021f980).
+ if (chunkIndex == 0 && origSize >= 54432) {
+ const uint32_t kW = 320, kH = 200, kPlane = 8000, kCopper = 0xBB80;
+ std::vector<uint8_t> planar(kW * kH, 0);
+ for (uint32_t plane = 0; plane < 6; plane++) {
+ const uint8_t *planeBase = dec.data() + plane * kPlane;
+ for (uint32_t y = 0; y < kH; y++) {
+ for (uint32_t bx = 0; bx < 40; bx++) {
+ uint8_t b = planeBase[y * 40 + bx];
+ for (int bit = 0; bit < 8; bit++) {
+ uint32_t x = bx * 8 + bit;
+ if (b & (0x80 >> bit))
+ planar[y * kW + x] |= (uint8_t)(1 << plane);
+ }
+ }
+ }
+ }
+
+ auto amiga12ToRgb8 = [](uint16_t rgb, uint8_t &r, uint8_t &g, uint8_t &b) {
+ uint8_t r4 = (rgb >> 8) & 0xF;
+ uint8_t g4 = (rgb >> 4) & 0xF;
+ uint8_t b4 = rgb & 0xF;
+ r = (uint8_t)(r4 * 17);
+ g = (uint8_t)(g4 * 17);
+ b = (uint8_t)(b4 * 17);
+ };
+
+ uint16_t base16[16];
+ for (int bi = 0; bi < 16; bi++)
+ base16[bi] = readU16BE(dec.data() + kCopper + bi * 2);
+ const uint8_t *lineColors = dec.data() + kCopper + 0x20;
+
+ auto buildPal32 = [&](uint32_t y, uint8_t pal32[32][3]) {
+ amiga12ToRgb8(base16[0], pal32[0][0], pal32[0][1], pal32[0][2]);
+ for (int ci = 0; ci < 16; ci++) {
+ uint16_t c = readU16BE(lineColors + y * 32 + ci * 2);
+ amiga12ToRgb8(c, pal32[1 + ci][0], pal32[1 + ci][1], pal32[1 + ci][2]);
+ }
+ for (int ci = 1; ci < 16; ci++)
+ amiga12ToRgb8(base16[ci], pal32[16 + ci][0], pal32[16 + ci][1], pal32[16 + ci][2]);
+ };
+
+ // Reserve 0..31 COLOR + 32..63 EHB for OO sprite compatibility.
+ uint8_t staticPal[32][3];
+ buildPal32(0, staticPal);
+ uint8_t pal[768];
+ memset(pal, 0, sizeof(pal));
+ std::map<uint32_t, uint8_t> colorToIndex;
+ int colorCount = 64;
+ for (int si = 0; si < 32; si++) {
+ pal[si * 3 + 0] = (uint8_t)((staticPal[si][0] * 63) / 255);
+ pal[si * 3 + 1] = (uint8_t)((staticPal[si][1] * 63) / 255);
+ pal[si * 3 + 2] = (uint8_t)((staticPal[si][2] * 63) / 255);
+ uint32_t key = ((uint32_t)staticPal[si][0] << 16) |
+ ((uint32_t)staticPal[si][1] << 8) | staticPal[si][2];
+ colorToIndex[key] = (uint8_t)si;
+ }
+ for (int si = 0; si < 32; si++) {
+ uint8_t r = (uint8_t)(staticPal[si][0] / 2);
+ uint8_t g = (uint8_t)(staticPal[si][1] / 2);
+ uint8_t b = (uint8_t)(staticPal[si][2] / 2);
+ pal[(32 + si) * 3 + 0] = (uint8_t)((r * 63) / 255);
+ pal[(32 + si) * 3 + 1] = (uint8_t)((g * 63) / 255);
+ pal[(32 + si) * 3 + 2] = (uint8_t)((b * 63) / 255);
+ uint32_t key = ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
+ if (!colorToIndex.count(key))
+ colorToIndex[key] = (uint8_t)(32 + si);
+ }
+
+ std::vector<uint8_t> pixels(kW * kH, 0);
+ for (uint32_t y = 0; y < kH; y++) {
+ uint8_t pal32[32][3];
+ buildPal32(y, pal32);
+ for (uint32_t x = 0; x < kW; x++) {
+ uint8_t idx = planar[y * kW + x];
+ uint8_t r, g, b;
+ if (idx >= 32) {
+ uint8_t base = (uint8_t)(idx - 32);
+ r = (uint8_t)(pal32[base][0] / 2);
+ g = (uint8_t)(pal32[base][1] / 2);
+ b = (uint8_t)(pal32[base][2] / 2);
+ } else {
+ r = pal32[idx][0];
+ g = pal32[idx][1];
+ b = pal32[idx][2];
+ }
+ uint32_t key = ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
+ uint8_t outIdx;
+ auto it = colorToIndex.find(key);
+ if (it != colorToIndex.end()) {
+ outIdx = it->second;
+ } else if (colorCount < 256) {
+ outIdx = (uint8_t)colorCount;
+ colorToIndex[key] = outIdx;
+ pal[outIdx * 3 + 0] = (uint8_t)((r * 63) / 255);
+ pal[outIdx * 3 + 1] = (uint8_t)((g * 63) / 255);
+ pal[outIdx * 3 + 2] = (uint8_t)((b * 63) / 255);
+ colorCount++;
+ } else {
+ outIdx = (uint8_t)(idx & 31);
+ }
+ pixels[y * kW + x] = outIdx;
+ }
+ }
+
+ snprintf(path, sizeof(path), "%s/%s_%04d.bmp", outDir, eType, eId);
+ writeBMPEx(path, pixels.data(), (uint16_t)kW, (uint16_t)kH, pal);
+ images++;
+ wroteBg = true;
+ printf(" %s_%04d.bmp: scene background 320x200 EHB+copper (%d colors)\n",
+ eType, eId, colorCount);
+ }
+ } else {
+ snprintf(path, sizeof(path), "%s/%s.pp20", outDir, chunkName);
+ writeRawFile(path, chunk, chunkSize);
+ printf(" %s: %u bytes (PP20 decompress failed)\n", chunkName, chunkSize);
+ }
}
+ } else {
+ snprintf(path, sizeof(path), "%s/%s.bin", outDir, chunkName);
+ writeRawFile(path, chunk, chunkSize);
+ printf(" %s: %u bytes magic=%.4s\n", chunkName, chunkSize,
+ chunkSize >= 4 ? (const char *)chunk : "????");
}
- } 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);
+ chunkIndex++;
}
+ if (!wroteBg)
+ printf(" %s: %u bytes (scene package, %d chunks)\n", fname, eCompSize, chunkIndex);
+ else
+ printf(" %s: %u bytes (%d chunks)\n", fname, eCompSize, chunkIndex);
} 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);
+ int beforeImages = images;
+ if (extractAmigaComplexMxoo(payload.data(), eCompSize, eType, eId, outDir, defaultPalette, images))
+ printf(" %s_%04d: complex object (+%d anim BMPs, JSON, script)\n",
+ eType, eId, images - beforeImages);
} else {
// Unknown format, save raw
snprintf(fname, sizeof(fname), "%s_%04d.raw", eType, eId);
@@ -1394,6 +1975,7 @@ static void printHelp(const char *bin) {
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(" portraits - Extract DOS dialogue portraits (blob slots 0x11/0x12) as BMP\n");
printf(" helpimages - Extract help/map panel images as BMP\n");
printf(" all - Extract everything\n");
printf("\n");
@@ -1402,7 +1984,11 @@ static void printHelp(const char *bin) {
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("Amiga mode auto-detects DataA/Mdir and extracts PP20 resources,\n");
+ printf("complex OO anim frames (BMP), object JSON, scripts,\n");
+ printf("dialogue portraits (OO_*_portrait.bmp as 240x80x6 planar / 3x80 frames),\n");
+ printf("backgrounds (6-plane EHB + copper; palette 0..31 for sprites),\n");
+ printf("and OS_*.mxos PCM sound containers. No Protracker/AdLib music in demo.\n");
printf("If scene_index is omitted, extracts from all scenes.\n");
}
@@ -1457,12 +2043,17 @@ int main(int argc, char **argv) {
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 doPortraits = !strcmp(mode, "portraits") || !strcmp(mode, "all");
bool doHelpImages = !strcmp(mode, "helpimages") || !strcmp(mode, "all");
if (doItems) {
extractItems(outDir);
}
+ if (doPortraits) {
+ extractPortraits(outDir);
+ }
+
if (doHelpImages) {
extractHelpImages(outDir);
}
Commit: 0e9e8bc76199b81f8b5cdc619c4eb7228624a3d8
https://github.com/scummvm/scummvm-tools/commit/0e9e8bc76199b81f8b5cdc619c4eb7228624a3d8
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-30T15:06:43+02:00
Commit Message:
MACS2: support for ui label translations - like the verbs
Changed paths:
engines/macs2/TRANSLATE.md
engines/macs2/create_macs2_translation.cpp
engines/macs2/demacs2.cpp
diff --git a/engines/macs2/TRANSLATE.md b/engines/macs2/TRANSLATE.md
index 19c1e264..9905f17b 100644
--- a/engines/macs2/TRANSLATE.md
+++ b/engines/macs2/TRANSLATE.md
@@ -18,6 +18,8 @@ Hotspot overlay labels are appended as one entry per unique German noun (`msgctx
When packing, hotspot and object labels share one CP850 lookup table in the DAT file (duplicate German strings need only one translation).
+Action-bar / HUD chrome uses a separate DAT section (`msgctxt "uilabel"`, format version 3). Keys are the German source strings from `engines/macs2/actionbar.cpp` (for example `Benutzen %s mit %s`). Translators may reorder words and `%s` / `%d` placeholders. Existing version-1/2 DAT files still load; those games keep the German UI strings until the DAT is rebuilt with this tool.
+
The `\n` separators correspond to individual display lines in the game. Keep the same number of lines as the original.
### 3. Pack into binary
diff --git a/engines/macs2/create_macs2_translation.cpp b/engines/macs2/create_macs2_translation.cpp
index 66f3c8b2..92f30f70 100644
--- a/engines/macs2/create_macs2_translation.cpp
+++ b/engines/macs2/create_macs2_translation.cpp
@@ -40,10 +40,16 @@
* msgid "Bowiemesser"
* msgstr "Bowie knife"
*
+ * msgctxt "uilabel"
+ * msgid "Benutzen %s mit %s"
+ * msgstr "Use %s with %s"
+ *
* Each msgid groups consecutive strings that form one dialog/description unit.
* The \n separates individual lines that the engine displays separately.
* Hotspot and object overlay labels are embedded below (full game only).
* Keep in sync with engines/macs2/hotspot_names.cpp and gameobjects.cpp.
+ * UI labels (action bar / HUD chrome) are German source keys; keep in sync
+ * with engines/macs2/actionbar.cpp.
*/
#include <algorithm>
@@ -845,6 +851,24 @@ static const char *const kObjectLabels[] = {
static const uint32_t kObjectLabelsCount = sizeof(kObjectLabels) / sizeof(kObjectLabels[0]);
+// German source keys for the action-bar / HUD chrome (DAT version 3, msgctxt "uilabel").
+// Sync with ScummVM engines/macs2/actionbar.cpp.
+static const char *const kUiLabels[] = {
+ "Gehen",
+ "Schauen",
+ "Benutzen",
+ "Reden",
+ "Gehen %s",
+ "Schauen %s",
+ "Reden %s",
+ "Benutzen %s",
+ "Benutzen %s mit %s",
+ "--- Platz %d ---",
+ "Spielstand %d",
+};
+
+static const uint32_t kUiLabelsCount = sizeof(kUiLabels) / sizeof(kUiLabels[0]);
+
static void extractObjectLabels(FILE *out, int &totalEntries) {
fprintf(out, "# Object/NPC overlay labels (unique names, full game, CP850/latin-1 source)\n\n");
for (uint32_t i = 0; i < kObjectLabelsCount; ++i) {
@@ -861,6 +885,14 @@ static void extractHotspotLabels(FILE *out, int &totalEntries) {
}
}
+static void extractUiLabels(FILE *out, int &totalEntries) {
+ fprintf(out, "# Action-bar / HUD UI labels (German source; DAT version 3)\n\n");
+ for (uint32_t i = 0; i < kUiLabelsCount; ++i) {
+ writePoLabelEntry(out, "uilabel", kUiLabels[i]);
+ totalEntries++;
+ }
+}
+
static int doExtract(const char *resPath, const char *outPath) {
resFile = fopen(resPath, "rb");
if (!resFile) {
@@ -955,10 +987,12 @@ static int doExtract(const char *resPath, const char *outPath) {
extractHotspotLabels(out, totalEntries);
extractObjectLabels(out, totalEntries);
+ extractUiLabels(out, totalEntries);
fclose(out);
fclose(resFile);
- printf("Extracted %d entries (%u hotspot + %u object labels) to %s\n", totalEntries, kHotspotLabelsCount, kObjectLabelsCount, outPath);
+ printf("Extracted %d entries (%u hotspot + %u object + %u UI labels) to %s\n",
+ totalEntries, kHotspotLabelsCount, kObjectLabelsCount, kUiLabelsCount, outPath);
return 0;
}
@@ -974,13 +1008,14 @@ static int doPack(const char *poPath, const char *outPath) {
return 1;
}
- // Parse PO: msgctxt "scene:N:startIdx", "object:N:startIdx", "hotspotlabel", or "objectlabel"
+ // Parse PO: msgctxt "scene:N:startIdx", "object:N:startIdx", "hotspotlabel", "objectlabel", or "uilabel"
// 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<std::string, std::string> overlayLabelStrings;
+ std::map<std::string, std::string> uiLabelStrings;
std::string currentMsgid;
- enum PoCtxKind { kPoCtxNone, kPoCtxScene, kPoCtxObjectDialog, kPoCtxHotspotLabel, kPoCtxObjectLabel } ctxKind = kPoCtxNone;
+ enum PoCtxKind { kPoCtxNone, kPoCtxScene, kPoCtxObjectDialog, kPoCtxHotspotLabel, kPoCtxObjectLabel, kPoCtxUiLabel } ctxKind = kPoCtxNone;
char line[8192];
uint16_t currentId = 0;
@@ -993,7 +1028,11 @@ static int doPack(const char *poPath, const char *outPath) {
auto flushEntry = [&]() {
if (hasCtx && !currentMsgstr.empty()) {
std::string text = poUnescape(currentMsgstr);
- if (ctxKind == kPoCtxHotspotLabel || ctxKind == kPoCtxObjectLabel) {
+ if (ctxKind == kPoCtxUiLabel) {
+ std::string source = poUnescape(currentMsgid);
+ if (!source.empty())
+ uiLabelStrings[source] = text;
+ } else if (ctxKind == kPoCtxHotspotLabel || ctxKind == kPoCtxObjectLabel) {
std::string source = poUnescape(currentMsgid);
if (!source.empty())
overlayLabelStrings[source] = text;
@@ -1049,6 +1088,9 @@ static int doPack(const char *poPath, const char *outPath) {
} else if (!strncmp(line + 9, "objectlabel\"", 13)) {
ctxKind = kPoCtxObjectLabel;
hasCtx = true;
+ } else if (!strncmp(line + 9, "uilabel\"", 8)) {
+ ctxKind = kPoCtxUiLabel;
+ hasCtx = true;
}
continue;
}
@@ -1139,10 +1181,11 @@ static int doPack(const char *poPath, const char *outPath) {
}
fwrite("MCS2", 1, 4, out);
- writeU16(out, 2);
+ writeU16(out, 3);
writeU16(out, (uint16_t)sceneBlocks.size());
writeU16(out, (uint16_t)objectBlocks.size());
writeU16(out, (uint16_t)overlayLabelStrings.size());
+ writeU16(out, (uint16_t)uiLabelStrings.size());
long indexStart = ftell(out);
uint32_t indexSize = ((uint32_t)sceneBlocks.size() + (uint32_t)objectBlocks.size()) * 8;
@@ -1185,20 +1228,25 @@ static int doPack(const char *poPath, const char *outPath) {
// overlay labels must be appended after all scene/object string data.
fseek(out, 0, SEEK_END);
- for (const auto &kv : overlayLabelStrings) {
- const std::string source = utf8ToCp850(kv.first);
- const std::string translated = utf8ToCp850(kv.second);
- writeU16(out, (uint16_t)source.size());
- if (!source.empty())
- fwrite(source.data(), 1, source.size(), out);
- writeU16(out, (uint16_t)translated.size());
- if (!translated.empty())
- fwrite(translated.data(), 1, translated.size(), out);
- }
+ auto writeLabelMap = [&](const std::map<std::string, std::string> &labels) {
+ for (const auto &kv : labels) {
+ const std::string source = utf8ToCp850(kv.first);
+ const std::string translated = utf8ToCp850(kv.second);
+ writeU16(out, (uint16_t)source.size());
+ if (!source.empty())
+ fwrite(source.data(), 1, source.size(), out);
+ writeU16(out, (uint16_t)translated.size());
+ if (!translated.empty())
+ fwrite(translated.data(), 1, translated.size(), out);
+ }
+ };
+
+ writeLabelMap(overlayLabelStrings);
+ writeLabelMap(uiLabelStrings);
fclose(out);
- printf("Packed %zu scene + %zu object blocks + %zu overlay labels into %s\n",
- sceneBlocks.size(), objectBlocks.size(), overlayLabelStrings.size(), outPath);
+ printf("Packed %zu scene + %zu object blocks + %zu overlay + %zu UI labels into %s\n",
+ sceneBlocks.size(), objectBlocks.size(), overlayLabelStrings.size(), uiLabelStrings.size(), outPath);
return 0;
}
diff --git a/engines/macs2/demacs2.cpp b/engines/macs2/demacs2.cpp
index d1a087f2..6cf48f9f 100644
--- a/engines/macs2/demacs2.cpp
+++ b/engines/macs2/demacs2.cpp
@@ -447,6 +447,69 @@ static const char *getOpcodeName(uint8_t opcode) {
return "setPathfindingRemap";
case 0x4E:
return "waitForAdlib";
+ // Extended opcodes
+ case 0x4F:
+ return "setMainActor";
+ case 0x50:
+ return "loadDeltaAnim";
+ case 0x51:
+ return "playDeltaAnim";
+ case 0x52:
+ return "removeDeltaAnim";
+ case 0x53:
+ return "setButtonStep";
+ case 0x54:
+ return "testButtonAnimFrame";
+ case 0x55:
+ return "screenShot";
+ case 0x56:
+ return "waitObjectAnimStep";
+ case 0x57:
+ return "waitSpecialAnimStep";
+ case 0x58:
+ return "setObjectAdjust";
+ case 0x59:
+ return "reloadSpecialAnim";
+ case 0x5A:
+ return "playDiskDelta";
+ case 0x5B:
+ return "setDiskCache";
+ case 0x5C:
+ return "setMidiVolume";
+ case 0x5D:
+ return "setWaveVolume";
+ case 0x5E:
+ return "loadSpecAnimAnim";
+ case 0x5F:
+ return "setSpecAnimAnim";
+ case 0x60:
+ return "clearSpecAnimAnim";
+ case 0x61:
+ return "setDeltaRange";
+ case 0x62:
+ return "clearDeltaRange";
+ case 0x63:
+ return "addDeltaSfx";
+ case 0x64:
+ return "clearDeltaSfxList";
+ case 0x65:
+ return "showActionBar";
+ case 0x66:
+ return "hideActionBar";
+ case 0x67:
+ return "setCursorType";
+ case 0x68:
+ return "checkDeltaSpeed";
+ case 0x69:
+ return "loadDistanceMask";
+ case 0x6A:
+ return "loadAreaMask";
+ case 0x6B:
+ return "loadWalkMask";
+ case 0x6C:
+ return "loadShadowMask";
+ case 0x6D:
+ return "talkTo";
default:
return "???";
}
Commit: 774fa09a4e8310703392864bd5d0b753554a190a
https://github.com/scummvm/scummvm-tools/commit/774fa09a4e8310703392864bd5d0b753554a190a
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-30T15:16:16+02:00
Commit Message:
MACS2: reset version back to 1 - the game was never officially supported
Changed paths:
engines/macs2/TRANSLATE.md
engines/macs2/create_macs2_translation.cpp
diff --git a/engines/macs2/TRANSLATE.md b/engines/macs2/TRANSLATE.md
index 9905f17b..298522d3 100644
--- a/engines/macs2/TRANSLATE.md
+++ b/engines/macs2/TRANSLATE.md
@@ -18,7 +18,7 @@ Hotspot overlay labels are appended as one entry per unique German noun (`msgctx
When packing, hotspot and object labels share one CP850 lookup table in the DAT file (duplicate German strings need only one translation).
-Action-bar / HUD chrome uses a separate DAT section (`msgctxt "uilabel"`, format version 3). Keys are the German source strings from `engines/macs2/actionbar.cpp` (for example `Benutzen %s mit %s`). Translators may reorder words and `%s` / `%d` placeholders. Existing version-1/2 DAT files still load; those games keep the German UI strings until the DAT is rebuilt with this tool.
+Action-bar / HUD chrome uses a separate DAT section (`msgctxt "uilabel"`). Keys are the German source strings from `engines/macs2/actionbar.cpp` (for example `Benutzen %s mit %s`). Translators may reorder words and `%s` / `%d` placeholders. Rebuild the DAT after changing these strings.
The `\n` separators correspond to individual display lines in the game. Keep the same number of lines as the original.
diff --git a/engines/macs2/create_macs2_translation.cpp b/engines/macs2/create_macs2_translation.cpp
index 92f30f70..55ddee82 100644
--- a/engines/macs2/create_macs2_translation.cpp
+++ b/engines/macs2/create_macs2_translation.cpp
@@ -851,7 +851,7 @@ static const char *const kObjectLabels[] = {
static const uint32_t kObjectLabelsCount = sizeof(kObjectLabels) / sizeof(kObjectLabels[0]);
-// German source keys for the action-bar / HUD chrome (DAT version 3, msgctxt "uilabel").
+// German source keys for the action-bar / HUD chrome (msgctxt "uilabel").
// Sync with ScummVM engines/macs2/actionbar.cpp.
static const char *const kUiLabels[] = {
"Gehen",
@@ -886,7 +886,7 @@ static void extractHotspotLabels(FILE *out, int &totalEntries) {
}
static void extractUiLabels(FILE *out, int &totalEntries) {
- fprintf(out, "# Action-bar / HUD UI labels (German source; DAT version 3)\n\n");
+ fprintf(out, "# Action-bar / HUD UI labels (German source)\n\n");
for (uint32_t i = 0; i < kUiLabelsCount; ++i) {
writePoLabelEntry(out, "uilabel", kUiLabels[i]);
totalEntries++;
@@ -1181,7 +1181,7 @@ static int doPack(const char *poPath, const char *outPath) {
}
fwrite("MCS2", 1, 4, out);
- writeU16(out, 3);
+ writeU16(out, 1);
writeU16(out, (uint16_t)sceneBlocks.size());
writeU16(out, (uint16_t)objectBlocks.size());
writeU16(out, (uint16_t)overlayLabelStrings.size());
More information about the Scummvm-git-logs
mailing list