[Scummvm-git-logs] scummvm-tools master -> 934a64251c950aec46a0e149bad38953d6ab8dd1

mgerhardy noreply at scummvm.org
Tue Jul 14 14:11:07 UTC 2026


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

Summary:
d0aa56f4eb MACS2: fixed obj names
934a64251c MACS2: add hotspot labels from the sis game


Commit: d0aa56f4ebedd09fd7fc669b5971ab8d0be79581
    https://github.com/scummvm/scummvm-tools/commit/d0aa56f4ebedd09fd7fc669b5971ab8d0be79581
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-07-14T11:23:52+02:00

Commit Message:
MACS2: fixed obj names

Changed paths:
    engines/macs2/demacs2.cpp


diff --git a/engines/macs2/demacs2.cpp b/engines/macs2/demacs2.cpp
index 6545b39f..d1a087f2 100644
--- a/engines/macs2/demacs2.cpp
+++ b/engines/macs2/demacs2.cpp
@@ -1462,11 +1462,10 @@ static std::string formatVarAccess(uint16_t idx) {
 }
 
 static const char *getObjectName(uint16_t idx) {
-	// Full game object names from Macs2::GameObjects::init()
-	// Characters/NPCs
+	// Full-game character/NPC indices (validated against strings_object*.txt and scene dialogue)
 	switch (idx) {
 	case 1:
-		return "OBJ_PLAYER";
+		return "OBJ_PLAYER";        // Old Firehand
 	case 2:
 		return "OBJ_CAPTAIN";
 	case 3:
@@ -1476,37 +1475,37 @@ static const char *getObjectName(uint16_t idx) {
 	case 5:
 		return "OBJ_NARRATOR";
 	case 6:
-		return "OBJ_CORNEL";
+		return "OBJ_TRAMP";         // tramp henchman
 	case 7:
-		return "OBJ_BEAR";
+		return "OBJ_GIRL";          // girl threatened by panther
 	case 9:
-		return "OBJ_WOLF";
+		return "OBJ_PANTHER";
 	case 12:
 		return "OBJ_DROLL";
 	case 13:
 		return "OBJ_PATTERSON";
 	case 15:
-		return "OBJ_ROLLINS";
+		return "OBJ_CORNEL";        // Cornel Brinkley, tramp leader
 	case 18:
 		return "OBJ_SHIP_GUARD";
 	case 19:
-		return "OBJ_HILTON";
+		return "OBJ_TRAMP2";        // tramp henchman
 	case 22:
 		return "OBJ_WOMAN_SHIP";
 	case 33:
 		return "OBJ_SAILOR";
 	case 39:
-		return "OBJ_WAGON_OWNER";
+		return "OBJ_WAGON_OWNER";   // Branshky
 	case 53:
 		return "OBJ_THIEF";
 	case 69:
-		return "OBJ_SHERIFF";
+		return "OBJ_RAFTERS";       // rafter lumberjack
 	case 77:
-		return "OBJ_INDIAN_COMPANION";
+		return "OBJ_TRAMP3";        // tramp henchman
 	case 105:
 		return "OBJ_BARKEEPER";
 	case 110:
-		return "OBJ_COLONEL";
+		return "OBJ_MRS_BUTLER";
 	case 144:
 		return "OBJ_BANDIT_1";
 	case 145:
@@ -1514,15 +1513,15 @@ static const char *getObjectName(uint16_t idx) {
 	case 146:
 		return "OBJ_BANDIT_3";
 	case 147:
-		return "OBJ_WINNETOU";
+		return "OBJ_WINNETOU";      // farm assault scene
 	case 149:
-		return "OBJ_CHIEF";
+		return "OBJ_GROSSER_BAER";  // scene 45
 	case 167:
-		return "OBJ_WINNETOU_INDIANS";
+		return "OBJ_WINNETOU";      // scene 45 (second sprite)
 	case 168:
-		return "OBJ_INDIAN_ELDER";
+		return "OBJ_KLEINER_BAER";  // scene 45
 	case 181:
-		return "OBJ_WINNETOU_ENDING";
+		return "OBJ_WINNETOU";      // ending / silver lake scenes
 	// Items (full game, indices from _objectNames)
 	case 0x08:
 		return "OBJ_BOARD";
@@ -1573,13 +1572,15 @@ static const char *getObjectName(uint16_t idx) {
 	case 0x2C:
 		return "OBJ_ENVELOPE_SEALED";
 	case 0x2D:
-		return "OBJ_RACCOON_FUR";
+		return "OBJ_RACCOON_CAP";
 	case 0x2E:
 		return "OBJ_WHISKY_GLASS";
 	case 0x2F:
 		return "OBJ_LEATHER_BELT";
 	case 0x30:
 		return "OBJ_POKER";
+	case 0x31:
+		return "OBJ_WATCHDOG";
 	case 0x34:
 		return "OBJ_BOARD_SOLID";
 	case 0x36:
@@ -1595,13 +1596,13 @@ static const char *getObjectName(uint16_t idx) {
 	case 0x3C:
 		return "OBJ_PEBBLES";
 	case 0x3D:
-		return "OBJ_SUITCASE_DYNAMITE";
+		return "OBJ_SUITCASE_LAUNDRY";
 	case 0x3E:
-		return "OBJ_BANKNOTES";
+		return "OBJ_LAUNDRY";
 	case 0x3F:
 		return "OBJ_LEATHER_POUCH";
 	case 0x40:
-		return "OBJ_SLINGSHOT";
+		return "OBJ_FIRECRACKER_POUCH";
 	case 0x41:
 		return "OBJ_FIRECRACKERS";
 	case 0x42:
@@ -1612,6 +1613,8 @@ static const char *getObjectName(uint16_t idx) {
 		return "OBJ_PAPER";
 	case 0x47:
 		return "OBJ_KNIFE_RUSTY";
+	case 0x4A:
+		return "OBJ_SCARF";
 	case 0x4B:
 		return "OBJ_REEDS_DRY";
 	case 0x4C:


Commit: 934a64251c950aec46a0e149bad38953d6ab8dd1
    https://github.com/scummvm/scummvm-tools/commit/934a64251c950aec46a0e149bad38953d6ab8dd1
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-07-14T16:10:23+02:00

Commit Message:
MACS2: add hotspot labels from the sis game

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


diff --git a/.gitignore b/.gitignore
index ab28e83b..b7acfd68 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,11 +7,13 @@
 
 /.gdb_history
 /construct_mohawk
+/create_macs2_translation
 /create_sjisfnt
 /decine
 /decompile
 /degob
 /dekyra
+/demacs2
 /deprince
 /descumm
 /desword2
diff --git a/engines/macs2/TRANSLATE.md b/engines/macs2/TRANSLATE.md
index 08cb74ce..19c1e264 100644
--- a/engines/macs2/TRANSLATE.md
+++ b/engines/macs2/TRANSLATE.md
@@ -14,6 +14,10 @@ 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.
 
+Hotspot overlay labels are appended as one entry per unique German noun (`msgctxt "hotspotlabel"`). Object and NPC overlay names use `msgctxt "objectlabel"`. Both lists are embedded in `create_macs2_translation.cpp` and cover the **full game** only (`kSceneHotspotNames[]` and the non-demo branch of `gameobjects.cpp`); the demo reuses the same hotspot label strings for its subset of scene/hotspot indices, but object indices can differ between demo and full game.
+
+When packing, hotspot and object labels share one CP850 lookup table in the DAT file (duplicate German strings need only one translation).
+
 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 54aec097..adabcc2d 100644
--- a/engines/macs2/create_macs2_translation.cpp
+++ b/engines/macs2/create_macs2_translation.cpp
@@ -28,12 +28,22 @@
  *   3. Pack:    create_macs2_translation pack <translated.po> <macs2_translation.dat>
  *
  * PO format:
- *   msgctxt "scene:2"  (or "object:42")
+ *   msgctxt "scene:2:0"  (or "object:42:0")
  *   msgid "line1\nline2\nline3"
  *   msgstr "translated1\ntranslated2\ntranslated3"
  *
+ *   msgctxt "hotspotlabel"
+ *   msgid "Tür"
+ *   msgstr "Door"
+ *
+ *   msgctxt "objectlabel"
+ *   msgid "Bowiemesser"
+ *   msgstr "Bowie knife"
+ *
  * 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.
  */
 
 #include <algorithm>
@@ -224,6 +234,52 @@ static std::string cp850ToUtf8(const std::string &s) {
 	return out;
 }
 
+// UTF-8 to CP850 (subset used by MACS2 German strings)
+static std::string utf8ToCp850(const std::string &s) {
+	std::string out;
+	for (size_t i = 0; i < s.size();) {
+		unsigned char c = (unsigned char)s[i];
+		if (c < 0x80) {
+			out += (char)c;
+			i++;
+			continue;
+		}
+		uint32_t codepoint = 0;
+		if ((c & 0xE0) == 0xC0 && i + 1 < s.size()) {
+			codepoint = ((c & 0x1F) << 6) | (s[i + 1] & 0x3F);
+			i += 2;
+		} else if ((c & 0xF0) == 0xE0 && i + 2 < s.size()) {
+			codepoint = ((c & 0x0F) << 12) | ((s[i + 1] & 0x3F) << 6) | (s[i + 2] & 0x3F);
+			i += 3;
+		} else {
+			out += '?';
+			i++;
+			continue;
+		}
+		bool mapped = false;
+		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,
+		};
+		for (int j = 0; j < 128; ++j) {
+			if (cp850map[j] == codepoint) {
+				out += (char)(0x80 + j);
+				mapped = true;
+				break;
+			}
+		}
+		if (!mapped)
+			out += '?';
+	}
+	return out;
+}
+
 static std::string decryptString(const uint8_t *data, uint16_t length) {
 	std::string result;
 	for (int i = 1; i <= length; i++) {
@@ -473,6 +529,338 @@ static void writePoEntry(FILE *out, const char *ctx, int startIdx,
 	fprintf(out, "msgstr \"\"\n\n");
 }
 
+static void writePoLabelEntry(FILE *out, const char *ctx, const std::string &cp850) {
+	fprintf(out, "msgctxt \"%s\"\n", ctx);
+	fprintf(out, "msgid \"%s\"\n", poEscape(cp850ToUtf8(cp850)).c_str());
+	fprintf(out, "msgstr \"\"\n\n");
+}
+
+// Unique hotspot overlay nouns (CP850), full game only (kSceneHotspotNames[]).
+// Demo uses the same labels for its subset of scene/hotspot indices.
+// Sync with ScummVM engines/macs2/hotspot_names.cpp when labels change.
+static const char *const kHotspotLabels[] = {
+	"Abgrund",
+	"Angreifer",
+	"Asche",
+	"Aufbauten",
+	"Ausgang",
+	"Balken",
+	"Bar",
+	"Barmann",
+	"Baum",
+	"Berge",
+	"Bett",
+	"Beutel",
+	"Bild",
+	"Bildrahmen",
+	"Bildrand",
+	"Bl" "\x84" "tterdach",
+	"Boden",
+	"Bohlen",
+	"Brecheisen",
+	"Brett",
+	"Br" "\x81" "cke",
+	"Busch",
+	"B" "\x84" "renfell",
+	"Dach",
+	"Damm",
+	"Decke",
+	"Deckel",
+	"Deckung",
+	"Dickicht",
+	"Dynamit",
+	"Eichenfass",
+	"Eimer",
+	"Eingang",
+	"Emblem",
+	"Fackel",
+	"Farm",
+	"Fass",
+	"Felsbl" "\x94" "cke",
+	"Felsen",
+	"Felswand",
+	"Fenster",
+	"Flasche",
+	"Fluss",
+	"Fracht",
+	"Fr" "\x81" "chte",
+	"Gang",
+	"Gangsystem",
+	"Gebirge",
+	"Gef" "\x84" "hrt",
+	"Gegenstand",
+	"Gel" "\x84" "nder",
+	"Ger" "\x94" "ll",
+	"Gew" "\x94" "lbe",
+	"Gras",
+	"Gussofen",
+	"Gusspfannen",
+	"Hackenspitze",
+	"Hanfseil",
+	"Haus",
+	"Heizkessel",
+	"Heuwagen",
+	"Himmel",
+	"Holz",
+	"Holzbank",
+	"Holzdach",
+	"Holzeier",
+	"Holzfass",
+	"Holzpferd",
+	"Holzscheite",
+	"Holztisch",
+	"Holzt" "\x81" "r",
+	"Hund",
+	"H" "\x81" "tte",
+	"Insel",
+	"Kabine",
+	"Kachel",
+	"Kahn",
+	"Kaj" "\x81" "te",
+	"Kamin",
+	"Kammer",
+	"Kanone",
+	"Kerze",
+	"Kiselsteine",
+	"Kiste",
+	"Koffer",
+	"Kohlen",
+	"Konstruktion",
+	"Kopf",
+	"Ladung",
+	"Lagerfeuer",
+	"Lampe",
+	"Landschaft",
+	"Leiter",
+	"Leuchter",
+	"Loch",
+	"Lore",
+	"Luke",
+	"Mauer",
+	"Mine",
+	"Mist",
+	"Musketen",
+	"M" "\x94" "glichkeit",
+	"Ofen",
+	"Pfad",
+	"Pfahl",
+	"Pferd",
+	"Pferdekoppel",
+	"Pf" "\x81" "tze",
+	"Planken",
+	"Plateau",
+	"Plattform",
+	"Pumpe",
+	"Rasen",
+	"Raum",
+	"Ruhig",
+	"Schaufel",
+	"Schienen",
+	"Schilf",
+	"Schlange",
+	"Schleier",
+	"Schleifspuren",
+	"Schrank",
+	"Schublade",
+	"Sch" "\x81" "ssel",
+	"Sch" "\x84" "del",
+	"Sch" "\x84" "tze",
+	"See",
+	"Seekoffer",
+	"Seil",
+	"Speer",
+	"Spiegel",
+	"Spielkl" "\x94" "tze",
+	"Stahlkugeln",
+	"Stamm",
+	"Steg",
+	"Stein",
+	"Steine",
+	"Steinpyramide",
+	"Steins" "\x84" "ule",
+	"Steint" "\x81" "r",
+	"Stelle",
+	"Stofftasche",
+	"Stuhl",
+	"St" "\x81" "ck",
+	"St" "\x81" "tze",
+	"S" "\x84" "ulen",
+	"Tapete",
+	"Tasse",
+	"Teddyb" "\x84" "r",
+	"Teppich",
+	"Tier",
+	"Tintenfass",
+	"Tisch",
+	"Tonvase",
+	"Totempfahl",
+	"Treppe",
+	"Truhe",
+	"T" "\x81" "r",
+	"Ufer",
+	"Unkraut",
+	"Verkleidung",
+	"Vertiefungen",
+	"Vogelk" "\x84" "fig",
+	"Vorplatz",
+	"Wald",
+	"Wand",
+	"Wasser",
+	"Wasserkrug",
+	"Weg",
+	"Whiskyglas",
+	"Winde",
+	"Windlicht",
+	"Witterung",
+	"Wolken",
+	"W" "\x84" "nde",
+	"W" "\x84" "sche",
+	"Zelt",
+	"Zeug",
+};
+
+static const uint kHotspotLabelsCount = sizeof(kHotspotLabels) / sizeof(kHotspotLabels[0]);
+
+static const char *const kObjectLabels[] = {
+	"Axt",
+	"Axtklinge",
+	"Bandit",
+	"Blasebalg",
+	"Blechdose",
+	"Blecheimer",
+	"Bohlen",
+	"Bootsjunge",
+	"Bowiemesser",
+	"Branshky",
+	"Brecheisen",
+	"Brennholz",
+	"Brett",
+	"Brief",
+	"Brot",
+	"Brotmesser",
+	"B" "\x81" "cher",
+	"Clownpuppe",
+	"Cornel",
+	"Damenhut",
+	"Dieb",
+	"Droll",
+	"Dynamit",
+	"Eimer",
+	"Eisen",
+	"Eisenstange",
+	"Fackel",
+	"Feuerhaken",
+	"Figur",
+	"Flaschenzug",
+	"Griff",
+	"Grosser B" "\xe4" "r",
+	"Hackenspitze",
+	"Haken",
+	"Haken und Seil",
+	"Hanfschnur",
+	"Hanfseil",
+	"Hanfseile",
+	"Holzente",
+	"Holzfass",
+	"Holzfigur",
+	"Holzkohle",
+	"Holzpfahl",
+	"Holzw" "\x81" "rfel",
+	"Hutschachtel",
+	"Kacheln",
+	"Kakerlake",
+	"Kapit" "\xe4" "n",
+	"Kartoffeln",
+	"Kartonschachtel",
+	"Kerze",
+	"Kerzen",
+	"Kieselsteine",
+	"Kleider",
+	"Kleiner B" "\xe4" "r",
+	"Knallfr" "\x94" "sche",
+	"Koffer",
+	"Kohlenschaufel",
+	"Korkenzieher",
+	"Kuvert",
+	"Laib Brot",
+	"Lampe",
+	"Lampenschirm",
+	"Lampenschirme",
+	"Landkarte",
+	"Lederbeutel",
+	"Lederg" "\x81" "rtel",
+	"Lore",
+	"Matrose",
+	"Messer",
+	"Messingschl" "\x81" "ssel",
+	"Metalleimer",
+	"Mrs. Butler",
+	"Murmeln",
+	"Musketen",
+	"M" "\xe4" "dchen",
+	"Nase",
+	"Old Firehand",
+	"Panther",
+	"Papier",
+	"Papierdrachen",
+	"Passagierin",
+	"Patterson",
+	"Quarzsand",
+	"Rafter",
+	"Reservestiel",
+	"Salpeterpulver",
+	"Sand",
+	"Schal",
+	"Schaufel",
+	"Schaufelspitze",
+	"Schilfrohr",
+	"Schnapsflasche",
+	"Schraubenzieher",
+	"Schwarzpulver",
+	"Schwefel",
+	"Sch" "\x81" "rhaken",
+	"Sch" "\x81" "ssel",
+	"Sicheln",
+	"Socken",
+	"Spachtel",
+	"Spitzhacke",
+	"Stoffbeutel",
+	"Streichholz",
+	"Tasse",
+	"Teig",
+	"Tomahawk",
+	"Topflappen",
+	"Tramp",
+	"Vogelk" "\x84" "fig",
+	"Wachhund",
+	"Wachposten",
+	"Wagenrad",
+	"Waschb" "\x84" "rm" "\x81" "tze",
+	"Whiskyglas",
+	"Windlicht",
+	"Winnetou",
+	"Wirt",
+	"Wolle",
+};
+
+static const uint kObjectLabelsCount = sizeof(kObjectLabels) / sizeof(kObjectLabels[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 (uint i = 0; i < kObjectLabelsCount; ++i) {
+		writePoLabelEntry(out, "objectlabel", kObjectLabels[i]);
+		totalEntries++;
+	}
+}
+
+static void extractHotspotLabels(FILE *out, int &totalEntries) {
+	fprintf(out, "# Hotspot overlay labels (unique nouns, CP850 source in engine)\n\n");
+	for (uint i = 0; i < kHotspotLabelsCount; ++i) {
+		writePoLabelEntry(out, "hotspotlabel", kHotspotLabels[i]);
+		totalEntries++;
+	}
+}
+
 static int doExtract(const char *resPath, const char *outPath) {
 	resFile = fopen(resPath, "rb");
 	if (!resFile) {
@@ -565,9 +953,12 @@ static int doExtract(const char *resPath, const char *outPath) {
 		}
 	}
 
+	extractHotspotLabels(out, totalEntries);
+	extractObjectLabels(out, totalEntries);
+
 	fclose(out);
 	fclose(resFile);
-	printf("Extracted %d dialog entries to %s\n", totalEntries, outPath);
+	printf("Extracted %d entries (%u hotspot + %u object labels) to %s\n", totalEntries, kHotspotLabelsCount, kObjectLabelsCount, outPath);
 	return 0;
 }
 
@@ -583,13 +974,15 @@ static int doPack(const char *poPath, const char *outPath) {
 		return 1;
 	}
 
-	// Parse PO: msgctxt "scene:N:startIdx" or "object:N:startIdx"
+	// Parse PO: msgctxt "scene:N:startIdx", "object:N:startIdx", "hotspotlabel", or "objectlabel"
 	// 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::string currentMsgid;
+	enum PoCtxKind { kPoCtxNone, kPoCtxScene, kPoCtxObjectDialog, kPoCtxHotspotLabel, kPoCtxObjectLabel } ctxKind = kPoCtxNone;
 
 	char line[8192];
-	bool isScene = false;
 	uint16_t currentId = 0;
 	int currentStartIdx = 0;
 	bool hasCtx = false;
@@ -600,28 +993,36 @@ static int doPack(const char *poPath, const char *outPath) {
 	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;
+			if (ctxKind == kPoCtxHotspotLabel || ctxKind == kPoCtxObjectLabel) {
+				std::string source = poUnescape(currentMsgid);
+				if (!source.empty())
+					overlayLabelStrings[source] = text;
+			} else {
+				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 (ctxKind == kPoCtxScene)
+						sceneStrings[currentId][currentStartIdx] = lines;
+					else if (ctxKind == kPoCtxObjectDialog)
+						objectStrings[currentId][currentStartIdx] = lines;
 				}
-				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();
+		currentMsgid.clear();
 		inMsgstr = false;
 		inMsgid = false;
+		ctxKind = kPoCtxNone;
 	};
 
 	while (fgets(line, sizeof(line), in)) {
@@ -633,15 +1034,21 @@ 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;
+				ctxKind = kPoCtxScene;
 				currentId = (uint16_t)id;
 				currentStartIdx = idx;
 				hasCtx = true;
 			} else if (sscanf(line + 9, "object:%d:%d", &id, &idx) == 2) {
-				isScene = false;
+				ctxKind = kPoCtxObjectDialog;
 				currentId = (uint16_t)id;
 				currentStartIdx = idx;
 				hasCtx = true;
+			} else if (!strncmp(line + 9, "hotspotlabel\"", 13)) {
+				ctxKind = kPoCtxHotspotLabel;
+				hasCtx = true;
+			} else if (!strncmp(line + 9, "objectlabel\"", 13)) {
+				ctxKind = kPoCtxObjectLabel;
+				hasCtx = true;
 			}
 			continue;
 		}
@@ -660,6 +1067,14 @@ static int doPack(const char *poPath, const char *outPath) {
 		if (strncmp(line, "msgid ", 6) == 0) {
 			inMsgid = true;
 			inMsgstr = false;
+			currentMsgid.clear();
+			char *s = strchr(line + 6, '"');
+			if (s) {
+				s++;
+				char *e = strrchr(s, '"');
+				if (e)
+					currentMsgid = std::string(s, e - s);
+			}
 			continue;
 		}
 		if (line[0] == '"' && inMsgstr) {
@@ -670,8 +1085,12 @@ static int doPack(const char *poPath, const char *outPath) {
 			continue;
 		}
 		if (line[0] == '"' && inMsgid) {
+			char *s = line + 1;
+			char *e = strrchr(s, '"');
+			if (e)
+				currentMsgid += std::string(s, e - s);
 			continue;
-		} // skip msgid continuation
+		}
 		if (line[0] == '\0')
 			flushEntry();
 	}
@@ -720,9 +1139,10 @@ static int doPack(const char *poPath, const char *outPath) {
 	}
 
 	fwrite("MCS2", 1, 4, out);
-	writeU16(out, 1);
+	writeU16(out, 2);
 	writeU16(out, (uint16_t)sceneBlocks.size());
 	writeU16(out, (uint16_t)objectBlocks.size());
+	writeU16(out, (uint16_t)overlayLabelStrings.size());
 
 	long indexStart = ftell(out);
 	uint32_t indexSize = ((uint32_t)sceneBlocks.size() + (uint32_t)objectBlocks.size()) * 8;
@@ -760,9 +1180,20 @@ static int doPack(const char *poPath, const char *outPath) {
 		writeU32(out, objectOffsets[i]);
 	}
 
+	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);
+	}
+
 	fclose(out);
-	printf("Packed %zu scene + %zu object blocks into %s\n",
-		   sceneBlocks.size(), objectBlocks.size(), outPath);
+	printf("Packed %zu scene + %zu object blocks + %zu overlay labels into %s\n",
+		   sceneBlocks.size(), objectBlocks.size(), overlayLabelStrings.size(), outPath);
 	return 0;
 }
 




More information about the Scummvm-git-logs mailing list