[Scummvm-git-logs] scummvm master -> f04ff1a7ababb95c46417212c8b68970f73c9012

mgerhardy noreply at scummvm.org
Wed Oct 21 17:05:09 UTC 2026


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

Summary:
f04ff1a7ab MACS2: support for adding multiple translation dat files to the game dir


Commit: f04ff1a7ababb95c46417212c8b68970f73c9012
    https://github.com/scummvm/scummvm/commit/f04ff1a7ababb95c46417212c8b68970f73c9012
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-10-21T19:04:03+02:00

Commit Message:
MACS2: support for adding multiple translation dat files to the game dir

even the demo supports those translations now - except the one
demo-only chapter at the end

Changed paths:
  A engines/macs2/translation.cpp
  A engines/macs2/translation.h
  R dists/engine-data/macs2_translation.dat
    dists/engine-data/README
    dists/engine-data/engine_data.mk
    dists/irix/scummvm.idb
    engines/macs2/actionbar.cpp
    engines/macs2/detection.h
    engines/macs2/detection_tables.h
    engines/macs2/dialogs.cpp
    engines/macs2/dialogs.h
    engines/macs2/macs2.cpp
    engines/macs2/module.mk
    engines/macs2/text.cpp


diff --git a/dists/engine-data/README b/dists/engine-data/README
index 7b3b202f176..15c2dec6087 100644
--- a/dists/engine-data/README
+++ b/dists/engine-data/README
@@ -81,9 +81,6 @@ scripts, from.
 lure.dat:
 This file contains essential game data used by Lure of the Temptress game.
 
-macs2_translation.dat:
-This file contains game translations for the MACS2 engine.
-
 macgui.dat:
 The file contains set of offsets for recreation of Classic Mac OS GUI, used
 by Director, MacVenture and WAGE engines.
diff --git a/dists/engine-data/engine_data.mk b/dists/engine-data/engine_data.mk
index e93eb634dcc..25341375dbd 100644
--- a/dists/engine-data/engine_data.mk
+++ b/dists/engine-data/engine_data.mk
@@ -50,9 +50,6 @@ endif
 ifdef ENABLE_PRINCE
 DIST_FILES_LIST += dists/engine-data/prince_translation.dat
 endif
-ifdef ENABLE_MACS2
-DIST_FILES_LIST += dists/engine-data/macs2_translation.dat
-endif
 ifdef ENABLE_QUEEN
 DIST_FILES_LIST += dists/engine-data/queen.tbl
 endif
diff --git a/dists/engine-data/macs2_translation.dat b/dists/engine-data/macs2_translation.dat
deleted file mode 100644
index 61df7641bbb..00000000000
Binary files a/dists/engine-data/macs2_translation.dat and /dev/null differ
diff --git a/dists/irix/scummvm.idb b/dists/irix/scummvm.idb
index 286fe645085..e8b22482c43 100644
--- a/dists/irix/scummvm.idb
+++ b/dists/irix/scummvm.idb
@@ -41,7 +41,6 @@ f 0644 root sys usr/ScummVM/share/scummvm/kyra.dat kyra.dat scummvm.sw.eoe
 f 0644 root sys usr/ScummVM/share/scummvm/lure.dat lure.dat scummvm.sw.eoe
 f 0644 root sys usr/ScummVM/share/scummvm/macgui.dat macgui.dat scummvm.sw.eoe
 f 0644 root sys usr/ScummVM/share/scummvm/macventure.dat macventure.dat scummvm.sw.eoe
-f 0644 root sys usr/ScummVM/share/scummvm/macs2_translation.dat macs2_translation.dat scummvm.sw.eoe
 f 0644 root sys usr/ScummVM/share/scummvm/monkey4-patch.m4b monkey4-patch.m4b scummvm.sw.eoe
 f 0644 root sys usr/ScummVM/share/scummvm/mort.dat mort.dat scummvm.sw.eoe
 f 0644 root sys usr/ScummVM/share/scummvm/myst3.dat myst3.dat scummvm.sw.eoe
diff --git a/engines/macs2/actionbar.cpp b/engines/macs2/actionbar.cpp
index 03071d1ccac..fdf76408495 100644
--- a/engines/macs2/actionbar.cpp
+++ b/engines/macs2/actionbar.cpp
@@ -37,7 +37,7 @@ namespace Macs2 {
 
 namespace {
 
-// German source keys for macs2_translation.dat msgctxt "uilabel".
+// German source keys for sis-translation-<lang>.dat msgctxt "uilabel".
 Common::String uiText(const char *source) {
 	if (g_engine)
 		return g_engine->translateUiLabel(source);
diff --git a/engines/macs2/detection.h b/engines/macs2/detection.h
index f2605fa2ebe..5733f38d141 100644
--- a/engines/macs2/detection.h
+++ b/engines/macs2/detection.h
@@ -53,10 +53,7 @@ extern const ADGameDescription gameDescriptions[];
 #define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
 #define GAMEOPTION_TTS GUIO_GAMEOPTIONS2
 #define GAMEOPTION_ENHANCEMENTS GUIO_GAMEOPTIONS3
-
-enum {
-	GF_TRANSLATED = 1 << 0,
-};
+#define GAMEOPTION_TRANSLATION GUIO_GAMEOPTIONS4
 
 } // End of namespace Macs2
 
diff --git a/engines/macs2/detection_tables.h b/engines/macs2/detection_tables.h
index b68607c592e..f7254e8b276 100644
--- a/engines/macs2/detection_tables.h
+++ b/engines/macs2/detection_tables.h
@@ -36,7 +36,7 @@ const ADGameDescription gameDescriptions[] = {
 	 Common::DE_DEU,
 	 Common::kPlatformDOS,
 	 ADGF_TESTING,
-	 GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_TTS, GAMEOPTION_ENHANCEMENTS)},
+	 GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_TTS, GAMEOPTION_ENHANCEMENTS, GAMEOPTION_TRANSLATION)},
 
 	// GMACS II Interpreter V1.00 - Written & Copyright (C) 1993 by Arndt Hasch - Copyright by LINEL
 	{"sis",
@@ -45,17 +45,7 @@ const ADGameDescription gameDescriptions[] = {
 	 Common::DE_DEU,
 	 Common::kPlatformDOS,
 	 ADGF_DEMO | ADGF_TESTING,
-	 GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_TTS, GAMEOPTION_ENHANCEMENTS)},
-
-	// English fan translation (original + macs2_translation.dat)
-	{"sis",
-	 "w/translation",
-	 AD_ENTRY2s("RESOURCE.MCS", "b8646d5cc2e45442a414220b1a65ba73", 8621636,
-	            "macs2_translation.dat", nullptr, AD_NO_SIZE),
-	 Common::EN_ANY,
-	 Common::kPlatformDOS,
-	 GF_TRANSLATED | ADGF_TESTING,
-	 GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_TTS, GAMEOPTION_ENHANCEMENTS)},
+	 GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_TTS, GAMEOPTION_ENHANCEMENTS, GAMEOPTION_TRANSLATION)},
 
 	// Amiga demo - DataA (MXMF) + Mdir (MXDR). GMACS II / LINEL.
 	{"sis",
diff --git a/engines/macs2/dialogs.cpp b/engines/macs2/dialogs.cpp
index ac3d15e769c..4eef49ca9a9 100644
--- a/engines/macs2/dialogs.cpp
+++ b/engines/macs2/dialogs.cpp
@@ -21,15 +21,27 @@
 
 #include "macs2/dialogs.h"
 #include "common/config-manager.h"
+#include "common/gui_options.h"
 #include "common/translation.h"
 #include "engines/enhancements.h"
 #include "gui/widget.h"
+#include "macs2/detection.h"
 
 namespace Macs2 {
 
 Macs2OptionsWidget::Macs2OptionsWidget(GuiObject *boss, const Common::String &name, const Common::String &domain) :
 		OptionsContainerWidget(boss, name, "Macs2GameOptionsDialog", domain) {
 
+	if (Common::checkGameGUIOption(GAMEOPTION_TRANSLATION, ConfMan.get("guioptions", domain))) {
+		GUI::StaticTextWidget *label = new GUI::StaticTextWidget(widgetsBoss(), _dialogLayout + ".translationLabel", _("Translation:"));
+		label->setAlign(Graphics::kTextAlignRight);
+		_translationPopUp = new GUI::PopUpWidget(widgetsBoss(), _dialogLayout + ".translation");
+		_translationPopUp->appendEntry(_("Original"), 0);
+		_translationPackages = listTranslationPackages(Common::FSNode(ConfMan.getPath("path", domain)), ConfMan.get("gameid", domain));
+		for (uint i = 0; i < _translationPackages.size(); ++i)
+			_translationPopUp->appendEntry(_translationPackages[i].displayName, i + 1);
+	}
+
 	_originalMenusCheckbox = new GUI::CheckboxWidget(widgetsBoss(), _dialogLayout + ".originalMenus",
 		_("Use original save/load screens"),
 		_("Use the original save/load screens instead of the ScummVM ones"));
@@ -77,6 +89,17 @@ Macs2OptionsWidget::Macs2OptionsWidget(GuiObject *boss, const Common::String &na
 }
 
 void Macs2OptionsWidget::load() {
+	if (_translationPopUp) {
+		_translationPopUp->setSelectedTag(0);
+		const Common::String code = ConfMan.get(kTranslationConfigKey, _domain);
+		for (uint i = 0; i < _translationPackages.size(); ++i) {
+			if (_translationPackages[i].code.equalsIgnoreCase(code)) {
+				_translationPopUp->setSelectedTag(i + 1);
+				break;
+			}
+		}
+	}
+
 	_originalMenusCheckbox->setState(ConfMan.getBool("original_menus", _domain));
 
 #ifdef USE_TTS
@@ -112,6 +135,14 @@ void Macs2OptionsWidget::load() {
 }
 
 bool Macs2OptionsWidget::save() {
+	if (_translationPopUp) {
+		const uint32 selected = _translationPopUp->getSelectedTag();
+		if (selected > 0 && selected <= _translationPackages.size())
+			ConfMan.set(kTranslationConfigKey, _translationPackages[selected - 1].code, _domain);
+		else
+			ConfMan.removeKey(kTranslationConfigKey, _domain);
+	}
+
 	ConfMan.setBool("original_menus", _originalMenusCheckbox->getState(), _domain);
 
 #ifdef USE_TTS
@@ -153,7 +184,14 @@ bool Macs2OptionsWidget::save() {
 void Macs2OptionsWidget::defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const {
 	layouts.addDialog(layoutName, overlayedLayout)
 		.addLayout(GUI::ThemeLayout::kLayoutVertical, 5)
-			.addPadding(0, 0, 0, 0)
+			.addPadding(0, 0, 0, 0);
+	if (_translationPopUp) {
+		layouts.addLayout(GUI::ThemeLayout::kLayoutHorizontal)
+			.addWidget("translationLabel", "OptionsLabel")
+			.addWidget("translation", "PopUp")
+		.closeLayout();
+	}
+	layouts
 			.addWidget("originalMenus", "Checkbox")
 #ifdef USE_TTS
 			.addWidget("ttsEnabled", "Checkbox")
diff --git a/engines/macs2/dialogs.h b/engines/macs2/dialogs.h
index a2d791d7b38..dcf2a086fb9 100644
--- a/engines/macs2/dialogs.h
+++ b/engines/macs2/dialogs.h
@@ -24,6 +24,8 @@
 
 #include "gui/ThemeEval.h"
 #include "gui/widget.h"
+#include "gui/widgets/popup.h"
+#include "macs2/translation.h"
 
 namespace Macs2 {
 
@@ -46,6 +48,8 @@ private:
 	void defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const override;
 
 	GUI::CheckboxWidget *_originalMenusCheckbox = nullptr;
+	GUI::PopUpWidget *_translationPopUp = nullptr;
+	Common::Array<TranslationPackage> _translationPackages;
 #ifdef USE_TTS
 	GUI::CheckboxWidget *_ttsCheckbox = nullptr;
 #endif
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index e91fcdcfad7..b92162da54b 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -48,6 +48,7 @@
 #include "macs2/detection.h"
 #include "macs2/hotspot_names.h"
 #include "macs2/music.h"
+#include "macs2/translation.h"
 #include "macs2/view1.h"
 
 namespace Macs2 {
@@ -2455,9 +2456,23 @@ int Macs2Engine::computeStringIndex(Common::MemoryReadStream *stream, int target
 }
 
 void Macs2Engine::loadTranslation() {
-	Common::SeekableReadStream *f = SearchMan.createReadStreamForMember("macs2_translation.dat");
+	const Common::String code = ConfMan.get(kTranslationConfigKey);
+	if (code.empty() || isAmiga())
+		return;
+
+	const Common::FSNode gameDataDir(ConfMan.getPath("path"));
+	const Common::Array<TranslationPackage> packages = listTranslationPackages(gameDataDir, getGameId());
+	Common::String fileName;
+	for (const TranslationPackage &package : packages) {
+		if (package.code.equalsIgnoreCase(code)) {
+			fileName = package.fileName;
+			break;
+		}
+	}
+
+	Common::SeekableReadStream *f = fileName.empty() ? nullptr : gameDataDir.getChild(fileName).createReadStream();
 	if (!f) {
-		warning("Cannot open macs2_translation.dat");
+		warning("Cannot open %s translation package for '%s'", getGameId().c_str(), code.c_str());
 		return;
 	}
 
@@ -2465,14 +2480,14 @@ void Macs2Engine::loadTranslation() {
 	char magic[4];
 	f->read(magic, 4);
 	if (memcmp(magic, "MCS2", 4) != 0) {
-		warning("Invalid macs2_translation.dat magic");
+		warning("Invalid %s magic", fileName.c_str());
 		delete f;
 		return;
 	}
 
 	const uint16 version = f->readUint16LE();
 	if (version != 1) {
-		warning("Unsupported macs2_translation.dat version %u", version);
+		warning("Unsupported %s version %u", fileName.c_str(), version);
 		delete f;
 		return;
 	}
@@ -2515,7 +2530,7 @@ void Macs2Engine::loadTranslation() {
 			for (uint16 k = 0; k < len; k++) {
 				s += (char)f->readByte();
 			}
-			entry.strings.push_back(s);
+			entry.strings.push_back(normalizeTranslationString(s));
 		}
 		stringDataEnd = MAX(stringDataEnd, (uint32)f->pos());
 		_sceneTranslations[sceneIndex[i].id] = entry;
@@ -2531,7 +2546,7 @@ void Macs2Engine::loadTranslation() {
 			for (uint16 k = 0; k < len; k++) {
 				s += (char)f->readByte();
 			}
-			entry.strings.push_back(s);
+			entry.strings.push_back(normalizeTranslationString(s));
 		}
 		stringDataEnd = MAX(stringDataEnd, (uint32)f->pos());
 		_objectTranslations[objectIndex[i].id] = entry;
@@ -2550,7 +2565,7 @@ void Macs2Engine::loadTranslation() {
 				val += (char)f->readByte();
 			}
 			if (!key.empty() && !val.empty()) {
-				out[key] = val;
+				out[key] = normalizeTranslationString(val);
 			}
 		}
 	};
@@ -2564,12 +2579,12 @@ void Macs2Engine::loadTranslation() {
 	readLabelMap(numUiLabels, _uiLabelTranslations);
 
 	delete f;
-	debug("Loaded macs2_translation.dat: %u scenes, %u objects, %u overlay labels, %u UI labels",
-		  numScenes, numObjects, (uint)_hotspotLabelTranslations.size(), (uint)_uiLabelTranslations.size());
+	debug("Loaded %s: %u scenes, %u objects, %u overlay labels, %u UI labels",
+		  fileName.c_str(), numScenes, numObjects, (uint)_hotspotLabelTranslations.size(), (uint)_uiLabelTranslations.size());
 }
 
 Common::String Macs2Engine::translateHotspotLabel(const Common::String &cp850Name) const {
-	if (cp850Name.empty() || !(getFeatures() & GF_TRANSLATED)) {
+	if (cp850Name.empty()) {
 		return cp850Name;
 	}
 	auto it = _hotspotLabelTranslations.find(cp850Name);
@@ -2580,7 +2595,7 @@ Common::String Macs2Engine::translateHotspotLabel(const Common::String &cp850Nam
 }
 
 Common::String Macs2Engine::translateUiLabel(const Common::String &source) const {
-	if (source.empty() || !(getFeatures() & GF_TRANSLATED)) {
+	if (source.empty()) {
 		return source;
 	}
 	auto it = _uiLabelTranslations.find(source);
@@ -2620,17 +2635,22 @@ Common::StringArray Macs2Engine::decodeStrings(Common::MemoryReadStream *stream,
 	}
 
 	// Apply translation if available
-	if (getFeatures() & GF_TRANSLATED) {
+	if (!_sceneTranslations.empty() || !_objectTranslations.empty()) {
 		const int baseIndex = computeStringIndex(stream, offset);
 		const TranslationEntry *entry = nullptr;
-		if (objectId != 0 && _objectTranslations.contains(objectId)) {
-			entry = &_objectTranslations[objectId];
+		const int translationObjectId = isDemo() ? remapDemoTranslationObjectId(objectId) : objectId;
+		bool sceneTranslation = false;
+		if (translationObjectId != 0 && _objectTranslations.contains(translationObjectId)) {
+			entry = &_objectTranslations[translationObjectId];
 		} else if (sceneId != 0 && _sceneTranslations.contains(sceneId)) {
 			entry = &_sceneTranslations[sceneId];
+			sceneTranslation = true;
 		}
 		if (entry) {
 			for (int i = 0; i < numStrings; i++) {
-				const int idx = baseIndex + i;
+				int idx = baseIndex + i;
+				if (isDemo() && sceneTranslation)
+					idx = remapDemoTranslationSceneStringIndex(sceneId, idx);
 				if (idx >= 0 && idx < (int)entry->strings.size() && !entry->strings[idx].empty()) {
 					result[i] = entry->strings[idx];
 				}
@@ -3026,10 +3046,7 @@ Common::Error Macs2Engine::run() {
 	loadBootstrapResources();
 	initInventoryIconIndices();
 
-	// Load translation data if available
-	if (getFeatures() & GF_TRANSLATED) {
-		loadTranslation();
-	}
+	loadTranslation();
 
 	// Initialize graphics mode (taller framebuffer when action bar verb UI is enabled)
 	int gfxH = screenHeight();
diff --git a/engines/macs2/module.mk b/engines/macs2/module.mk
index 5caf65419a5..edecd594a2a 100644
--- a/engines/macs2/module.mk
+++ b/engines/macs2/module.mk
@@ -18,6 +18,7 @@ MODULE_OBJS = \
 	saveload.o \
 	scriptexecutor.o \
 	text.o \
+	translation.o \
 	actionbar.o \
 	view1.o
 
diff --git a/engines/macs2/text.cpp b/engines/macs2/text.cpp
index 5d44376083f..080c386850d 100644
--- a/engines/macs2/text.cpp
+++ b/engines/macs2/text.cpp
@@ -52,6 +52,21 @@ bool Text::findGlyph(char c, GlyphData &out, const GlyphData *glyphs, uint16 num
 }
 
 void Text::addDialogueFontFallbacks() {
+	GlyphData existingPunctuation, colon, comma;
+	if (_numGlyphs < kMaxGlyphs && !findGlyph(';', existingPunctuation) &&
+		findGlyph(':', colon) && findGlyph(',', comma) &&
+		colon._width == comma._width && colon._height > comma._height / 2) {
+		// Combine the colon's upper dot with the comma's tail.
+		GlyphData &semicolon = _glyphs[_numGlyphs++];
+		semicolon = comma;
+		semicolon._ascii = ';';
+		const uint rows = comma._height / 2 + 1;
+		for (uint y = 0; y < rows; ++y) {
+			for (uint x = 0; x < comma._width; ++x)
+				semicolon._data[y * comma._width + x] = colon._data[y * colon._width + x];
+		}
+	}
+
 	// Swedish CP850 characters missing from the original DOS dialogue font.
 	// Match its transparent/outline/foreground palette entries and cell
 	// dimensions to preserve text alignment and line spacing.
diff --git a/engines/macs2/translation.cpp b/engines/macs2/translation.cpp
new file mode 100644
index 00000000000..caefcffa80e
--- /dev/null
+++ b/engines/macs2/translation.cpp
@@ -0,0 +1,113 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM 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/>.
+ *
+ */
+
+#include "macs2/translation.h"
+#include "common/algorithm.h"
+#include "common/language.h"
+#include "common/ptr.h"
+#include "common/stream.h"
+
+namespace Macs2 {
+
+Common::String normalizeTranslationString(const Common::String &text) {
+	Common::String result;
+	for (char c : text) {
+		if ((byte)c != 0xf0) // CP850 soft hyphen; also used to pad unused dialogue lines.
+			result += c;
+	}
+	// An empty DAT entry falls back to German; keep an intentional blank nonempty.
+	if (!text.empty() && result.empty())
+		result = " ";
+	return result;
+}
+
+uint16 remapDemoTranslationObjectId(uint16 objectId) {
+	// The DOS demo renumbers inventory objects, retaining the NPC IDs.
+	static const uint16 fullGameIds[] = {
+		0, 1, 85, 87, 96, 97, 6, 98, 99, 101, 102, 103, 12, 107, 108, 15,
+		109, 113, 114, 19, 115, 117, 118, 119, 120, 126, 130, 134, 138, 139, 141
+	};
+	return objectId < ARRAYSIZE(fullGameIds) ? fullGameIds[objectId] : objectId;
+}
+
+int remapDemoTranslationSceneStringIndex(int sceneId, int stringIndex) {
+	// The DOS demo omits some full-game dialogue and inserts a chapter-end message.
+	if (sceneId == 30 && stringIndex == 249)
+		return -1;
+
+	static const struct {
+		int sceneId;
+		int firstString;
+		int offset;
+	} ranges[] = {
+		{30, 250, -1}, {30, 297, 1}, {30, 299, 3}, {30, 304, 5},
+		{30, 308, 23}, {30, 313, 26}, {30, 318, 32},
+		{31, 77, 17}, {31, 80, 25},
+		{32, 3, 2}, {32, 4, 5}, {32, 66, 10}, {32, 70, 17},
+		{32, 73, 21}, {32, 75, 25},
+		{33, 6, 3}, {33, 82, 11}
+	};
+	int offset = 0;
+	for (const auto &range : ranges) {
+		if (range.sceneId == sceneId && stringIndex >= range.firstString)
+			offset = range.offset;
+	}
+	return stringIndex + offset;
+}
+
+Common::Array<TranslationPackage> listTranslationPackages(const Common::FSNode &gameDataDir, const Common::String &gameId) {
+	Common::Array<TranslationPackage> packages;
+	Common::FSList files;
+	if (!gameDataDir.getChildren(files, Common::FSNode::kListFilesOnly))
+		return packages;
+
+	Common::String prefix = gameId + "-translation-";
+	prefix.toLowercase();
+	const Common::String suffix = ".dat";
+	for (const Common::FSNode &file : files) {
+		Common::String name = file.getName();
+		name.toLowercase();
+		if (!name.hasPrefix(prefix) || !name.hasSuffix(suffix))
+			continue;
+
+		const Common::String code = name.substr(prefix.size(), name.size() - prefix.size() - suffix.size());
+		if (code.empty())
+			continue;
+
+		Common::ScopedPtr<Common::SeekableReadStream> stream(file.createReadStream());
+		if (!stream || stream->size() < 14 || stream->readUint32BE() != MKTAG('M', 'C', 'S', '2') || stream->readUint16LE() != 1)
+			continue;
+
+		TranslationPackage package;
+		package.code = code;
+		const char *description = Common::getLanguageDescription(Common::parseLanguage(code));
+		package.displayName = description ? description : code;
+		package.fileName = file.getName();
+		packages.push_back(package);
+	}
+
+	Common::sort(packages.begin(), packages.end(), [](const TranslationPackage &a, const TranslationPackage &b) {
+		return a.code < b.code;
+	});
+	return packages;
+}
+
+} // End of namespace Macs2
diff --git a/engines/macs2/translation.h b/engines/macs2/translation.h
new file mode 100644
index 00000000000..045aed50696
--- /dev/null
+++ b/engines/macs2/translation.h
@@ -0,0 +1,46 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM 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/>.
+ *
+ */
+
+#ifndef MACS2_TRANSLATION_H
+#define MACS2_TRANSLATION_H
+
+#include "common/array.h"
+#include "common/fs.h"
+#include "common/str.h"
+
+namespace Macs2 {
+
+static const char *const kTranslationConfigKey = "macs2_translation";
+
+struct TranslationPackage {
+	Common::String code;
+	Common::String displayName;
+	Common::String fileName;
+};
+
+Common::Array<TranslationPackage> listTranslationPackages(const Common::FSNode &gameDataDir, const Common::String &gameId);
+Common::String normalizeTranslationString(const Common::String &text);
+uint16 remapDemoTranslationObjectId(uint16 objectId);
+int remapDemoTranslationSceneStringIndex(int sceneId, int stringIndex);
+
+} // End of namespace Macs2
+
+#endif // MACS2_TRANSLATION_H




More information about the Scummvm-git-logs mailing list