[Scummvm-git-logs] scummvm master -> e0634b8fd1f693b65ecc13ad1a4919fb3423636d
sev-
noreply at scummvm.org
Mon Jul 21 12:13:37 UTC 2025
This automated email contains information about 12 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
087b6123b8 DETECTION: Add ADGF_ADDON flag
a80f5eb574 GOB: Add five Adibou2 add-ons to detection tables, with ADGF_ADDON flag
2e10337804 GUI: Ensure add-ons cannot be added as an independent game
3ab25845ae ENGINES: Add a warning message for not supported add-ons
66b134f400 DOCS: Mention "enable_unsupported_addon_warning" key
7ad9b0b20b GOB: Implement add-ons detection at game startup
e773b9443e GUI: Add-ons support in game launcher
807fd261f0 GUI: INTEGRITY: Support checking games with add-ons
b186077900 GOB: Add Adibou2 add-ons in game ids list
54824deb84 GOB: Add a few log messages in add-ons detection step
dcc50f046b GOB: Remove "extra" field in Adibou add-ons detection entries
e0634b8fd1 ENGINES: Move add-ons detection logic from GOB to base Engine class
Commit: 087b6123b8c20c5c8ab0e70dd123672ec8193137
https://github.com/scummvm/scummvm/commit/087b6123b8c20c5c8ab0e70dd123672ec8193137
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
DETECTION: Add ADGF_ADDON flag
To be used with add-on games, that cannot be run independently without
their base game.
Changed paths:
engines/advancedDetector.cpp
engines/advancedDetector.h
engines/game.h
diff --git a/engines/advancedDetector.cpp b/engines/advancedDetector.cpp
index fe1a469355a..c982003febd 100644
--- a/engines/advancedDetector.cpp
+++ b/engines/advancedDetector.cpp
@@ -162,6 +162,10 @@ static Common::String generatePreferredTarget(const ADGameDescription *desc, int
res = res + "-" + getLanguageCode(desc->language);
}
+ if (desc->flags & ADGF_ADDON) {
+ res = res + "-addon";
+ }
+
return res;
}
@@ -208,6 +212,9 @@ DetectedGame AdvancedMetaEngineDetectionBase::toDetectedGame(const ADDetectedGam
else if (desc->flags & ADGF_WARNING)
game.gameSupportLevel = kWarningGame;
+ if (desc->flags & ADGF_ADDON)
+ game.isAddOn = true;
+
game.setGUIOptions(desc->guiOptions + _guiOptions);
game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(desc->language));
diff --git a/engines/advancedDetector.h b/engines/advancedDetector.h
index 74c36fae1cb..25717d1f24e 100644
--- a/engines/advancedDetector.h
+++ b/engines/advancedDetector.h
@@ -137,6 +137,7 @@ struct ADGameFileDescription {
*/
enum ADGameFlags : uint {
ADGF_NO_FLAGS = 0u, ///< No flags.
+ ADGF_ADDON = (1u << 15), ///< An add-on game, that cannot be run independently without its base game.
ADGF_TAILMD5 = (1u << 16), ///< Calculate the MD5 for this entry from the end of the file.
ADGF_AUTOGENTARGET = (1u << 17), ///< Automatically generate gameid from @ref ADGameDescription::extra.
ADGF_UNSTABLE = (1u << 18), ///< Flag to designate not yet officially supported games that are not fit for public testing.
diff --git a/engines/game.h b/engines/game.h
index e427dfd3167..8aa25c1ca81 100644
--- a/engines/game.h
+++ b/engines/game.h
@@ -186,6 +186,11 @@ struct DetectedGame {
*/
bool canBeAdded;
+ /**
+ * The game is an add-on that cannot be run independently.
+ */
+ bool isAddOn = false;
+
Common::String gameId;
Common::String preferredTarget;
Common::String description;
Commit: a80f5eb574fc7100047e4e6765034bb5ad7484aa
https://github.com/scummvm/scummvm/commit/a80f5eb574fc7100047e4e6765034bb5ad7484aa
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
GOB: Add five Adibou2 add-ons to detection tables, with ADGF_ADDON flag
Changed paths:
engines/gob/detection/tables_adibou2.h
diff --git a/engines/gob/detection/tables_adibou2.h b/engines/gob/detection/tables_adibou2.h
index 4ef0f90ec5b..e828a048dce 100644
--- a/engines/gob/detection/tables_adibou2.h
+++ b/engines/gob/detection/tables_adibou2.h
@@ -278,6 +278,86 @@
0, 0, 0
},
+// -- Add-ons : Read/Count 4-5 years --
+{
+ {
+ "adibou2readcount45",
+ "ADIBOU 2 - Lecture/Calcul 4-5 ans",
+ AD_ENTRY2s("intro_ap.stk", "7ff46d8c804186d3a11bf6b921fac2c0", 40835594,
+ "appli_01.vmd", "11635be4aeaac46d199e7e37cf905240", 54402),
+ FR_FRA,
+ kPlatformWindows,
+ ADGF_ADDON,
+ GUIO0()
+ },
+ kFeatures640x480,
+ 0, 0, 0
+},
+
+// -- Add-ons : Read/Count 6-7 years --
+{
+ {
+ "adibou2readcount67",
+ "ADIBOU 2 - Lecture/Calcul 6-7 ans",
+ AD_ENTRY2s("intro_ap.stk", "0e91d0d693d5731353ad4738f4aa065c", 36540132,
+ "appli_03.vmd", "6bf95a48f366bdf8af3a198c7b723c77", 58858),
+ FR_FRA,
+ kPlatformWindows,
+ ADGF_ADDON,
+ GUIO0()
+ },
+ kFeatures640x480,
+ 0, 0, 0
+},
+
+// -- Add-ons : "Nature & Sciences" --
+{
+ {
+ "adibou2sciences",
+ "ADIBOU 2 - Je découvre la nature et les sciences",
+ AD_ENTRY2s("intro_ap.stk", "bff25481fc05bc5c6a3aaa8c17e89e5b", 3446050,
+ "FICHES.ITK", "1670cc3373df162aed3219368665a1ca", 51025920),
+ FR_FRA,
+ kPlatformWindows,
+ ADGF_ADDON,
+ GUIO0()
+ },
+ kFeatures640x480,
+ 0, 0, 0
+},
+
+// -- Add-ons : "Anglais" (English for non-native speakers) --
+{
+ {
+ "adibou2anglais",
+ "ADIBOU 2 - Anglais",
+ AD_ENTRY2s("intro_ap.stk", "1c83832cfeeace2a4b1b9ca448fc5322", 1967132,
+ "LIPSYNC.ITK", "90ea1687c8d40989b5ff52c7ecaaf8b3", 107792384),
+ FR_FRA,
+ kPlatformWindows,
+ ADGF_ADDON | ADGF_UNSTABLE,
+ GUIO0()
+ },
+ kFeatures640x480,
+ 0, 0, 0
+},
+
+// -- Add-ons : Music --
+{
+ {
+ "adibou2music",
+ "ADIBOU 2 - Musique",
+ AD_ENTRY2s("intro_ap.stk", "2147748e04ac11bd7155779e1456be07", 1631068,
+ "MUZIKO.ITK", "101cd1690f13bf458e3988822a46e942", 54806528),
+ FR_FRA,
+ kPlatformWindows,
+ ADGF_ADDON | ADGF_UNSTABLE,
+ GUIO0()
+ },
+ kFeatures640x480,
+ 0, 0, 0
+},
+
// -- Demos --
{
Commit: 2e10337804720739c0ee4459c3f3bef7c164da8b
https://github.com/scummvm/scummvm/commit/2e10337804720739c0ee4459c3f3bef7c164da8b
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
GUI: Ensure add-ons cannot be added as an independent game
Through single add or mass add
Changed paths:
engines/engine.cpp
engines/engine.h
gui/launcher.cpp
gui/massadd.cpp
diff --git a/engines/engine.cpp b/engines/engine.cpp
index f4dbbfeed6b..10fca8cfa88 100644
--- a/engines/engine.cpp
+++ b/engines/engine.cpp
@@ -814,6 +814,23 @@ bool Engine::warnUserAboutUnsupportedGame(Common::String msg) {
return true;
}
+void Engine::errorAddingAddOnWithoutBaseGame(Common::String addOnName, Common::String gameId) {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan != nullptr) {
+ ttsMan->pushState();
+ g_gui.initTextToSpeech();
+ }
+
+ Common::U32String messageFormat = _("The game \"%s\" you are trying to add is an add-on for \"%s\" that cannot be run independently."
+ " Please copy the add-on contents into a subdirectory of the base game, and start the base game itself.");
+ Common::U32String message = Common::U32String::format(messageFormat, addOnName.c_str(), gameId.c_str());
+
+ GUI::MessageDialog(message).runModal();
+
+ if (ttsMan != nullptr)
+ ttsMan->popState();
+}
+
void Engine::errorUnsupportedGame(Common::String extraMsg) {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan != nullptr) {
diff --git a/engines/engine.h b/engines/engine.h
index 90da7dcecb9..d60c11d434d 100644
--- a/engines/engine.h
+++ b/engines/engine.h
@@ -591,6 +591,16 @@ public:
*/
static bool warnUserAboutUnsupportedGame(Common::String msg = Common::String());
+ /**
+ /**
+ * Display an error message to the user that the game is an add-on than cannot be
+ * run independently.
+ *
+ * @param addOnName The name of the add-on.
+ * @param gameId The ID of the base game that this add-on requires.
+ */
+ static void errorAddingAddOnWithoutBaseGame(Common::String addOnName, Common::String gameId);
+
/**
* Display an error message to the user that the game is not supported.
*
diff --git a/gui/launcher.cpp b/gui/launcher.cpp
index 6cbf4a91f15..de514473761 100644
--- a/gui/launcher.cpp
+++ b/gui/launcher.cpp
@@ -730,6 +730,11 @@ bool LauncherDialog::doGameDetection(const Common::Path &path) {
if (0 <= idx && idx < (int)candidates.size()) {
const DetectedGame &result = candidates[idx];
+ if (result.isAddOn) {
+ Engine::errorAddingAddOnWithoutBaseGame(result.description, result.gameId);
+ return true;
+ }
+
Common::String domain = EngineMan.createTargetForGame(result);
// Display edit dialog for the new entry
diff --git a/gui/massadd.cpp b/gui/massadd.cpp
index 1eddf6d9879..9aca458f1f5 100644
--- a/gui/massadd.cpp
+++ b/gui/massadd.cpp
@@ -199,7 +199,7 @@ void MassAddDialog::handleTickle() {
}
// Run the detector on the dir
- DetectionResults detectionResults = EngineMan.detectGames(files, (ADGF_WARNING | ADGF_UNSUPPORTED), true);
+ DetectionResults detectionResults = EngineMan.detectGames(files, (ADGF_WARNING | ADGF_UNSUPPORTED | ADGF_ADDON), true);
if (detectionResults.foundUnknownGames()) {
Common::U32String report = detectionResults.generateUnknownGameReport(false, 80);
Commit: 3ab25845ae17556492ca0e4412936112a422da60
https://github.com/scummvm/scummvm/commit/3ab25845ae17556492ca0e4412936112a422da60
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
ENGINES: Add a warning message for not supported add-ons
Changed paths:
base/commandLine.cpp
engines/engine.cpp
engines/engine.h
diff --git a/base/commandLine.cpp b/base/commandLine.cpp
index a14faee4b81..a078cf7daca 100644
--- a/base/commandLine.cpp
+++ b/base/commandLine.cpp
@@ -332,6 +332,7 @@ void registerDefaults() {
ConfMan.registerDefault("cdrom", 0);
ConfMan.registerDefault("enable_unsupported_game_warning", true);
+ ConfMan.registerDefault("enable_unsupported_addon_warning", true);
#ifdef USE_FLUIDSYNTH
ConfMan.registerDefault("soundfont", "Roland_SC-55.sf2");
diff --git a/engines/engine.cpp b/engines/engine.cpp
index 10fca8cfa88..c1178a174fe 100644
--- a/engines/engine.cpp
+++ b/engines/engine.cpp
@@ -814,6 +814,32 @@ bool Engine::warnUserAboutUnsupportedGame(Common::String msg) {
return true;
}
+bool Engine::warnUserAboutUnsupportedAddOn(Common::String addOnName) {
+ if (ConfMan.getBool("enable_unsupported_addon_warning")) {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan != nullptr) {
+ ttsMan->pushState();
+ g_gui.initTextToSpeech();
+ }
+
+ Common::U32String messageFormat = _("WARNING: the game you are about to start contains the add-on \"%s\""
+ " which is not yet fully supported by ScummVM. As such, it is likely to be unstable, and any saved"
+ " game you make might not work in future versions of ScummVM.");
+
+ Common::U32String message = Common::U32String::format(messageFormat, addOnName.c_str());
+
+ GUI::MessageDialog alert(message, _("Start anyway"), _("Cancel"));
+ int status = alert.runModal();
+
+ if (ttsMan != nullptr)
+ ttsMan->popState();
+
+ return status == GUI::kMessageOK;
+ }
+
+ return true;
+}
+
void Engine::errorAddingAddOnWithoutBaseGame(Common::String addOnName, Common::String gameId) {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan != nullptr) {
diff --git a/engines/engine.h b/engines/engine.h
index d60c11d434d..c6e8651caf8 100644
--- a/engines/engine.h
+++ b/engines/engine.h
@@ -592,6 +592,15 @@ public:
static bool warnUserAboutUnsupportedGame(Common::String msg = Common::String());
/**
+ * Display a warning to the user that the game contains an add-not which is not
+ * fully supported.
+ *
+ * @param addOnName The name of the add-on.
+ *
+ * @return True if the user chooses to start anyway, false otherwise.
+ */
+ static bool warnUserAboutUnsupportedAddOn(Common::String addOnName);
+
/**
* Display an error message to the user that the game is an add-on than cannot be
* run independently.
Commit: 66b134f400fb4d85bd35b58b29a007a0f95b3e75
https://github.com/scummvm/scummvm/commit/66b134f400fb4d85bd35b58b29a007a0f95b3e75
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
DOCS: Mention "enable_unsupported_addon_warning" key
Changed paths:
doc/docportal/advanced_topics/configuration_file.rst
diff --git a/doc/docportal/advanced_topics/configuration_file.rst b/doc/docportal/advanced_topics/configuration_file.rst
index 34a29cc0e72..5258985a29d 100755
--- a/doc/docportal/advanced_topics/configuration_file.rst
+++ b/doc/docportal/advanced_topics/configuration_file.rst
@@ -184,6 +184,7 @@ There are many recognized configuration keys. In the table below, each key is ei
":ref:`enable_video_upscale <upscale>`",boolean,true,
":ref:`enable_tts <ttsenabled>`",boolean,false,
enable_unsupported_game_warning,boolean,true, Shows a warning when adding a game that is unsupported.
+ enable_unsupported_addon_warning,boolean,true, Shows a warning when starting a game including an add-on that is unsupported.
":ref:`extended_timer <extended>`",boolean,false,
extra,string, ,"Shows additional information about a game, such as version"
":ref:`english_speech <english>`",boolean,false,
Commit: 7ad9b0b20bf4f263059c419e832bf1ee83e3de65
https://github.com/scummvm/scummvm/commit/7ad9b0b20bf4f263059c419e832bf1ee83e3de65
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
GOB: Implement add-ons detection at game startup
The add-ons are registered in hidden games entries, that cannot be run,
and refer their parent game via a "parent=" key.
When starting a game known to contain add-ons, the following steps are
performed:
- If a subdirectory of the game directory matches an add-on from the
detection tables, we add a new entry for it (unless it already exists),
with proper "parent" key. If this add-on is marked as unstable, a warning
message is displayed
- If the path of an existing add-on entry does not exists anymore, the
entry is purged.
Changed paths:
engines/gob/gob.h
engines/gob/metaengine.cpp
diff --git a/engines/gob/gob.h b/engines/gob/gob.h
index 29a862e2d42..a35df455b98 100644
--- a/engines/gob/gob.h
+++ b/engines/gob/gob.h
@@ -87,6 +87,8 @@
* - Croustibat
*/
+class GobMetaEngine;
+
namespace Gob {
class Game;
@@ -258,7 +260,12 @@ public:
~GobEngine() override;
void initGame(const GOBGameDescription *gd);
+ Common::ErrorCode updateAddOns(const GobMetaEngine *metaEngine, const GOBGameDescription *gd) const;
+
GameType getGameType(const char *gameId) const;
+ bool gameTypeHasAddOns();
+ bool dirCanBeGameAddOn(Common::FSDirectory dir) const;
+ bool dirMustBeGameAddOn(Common::FSDirectory dir) const;
/**
* Used to obtain the game version as a fallback
diff --git a/engines/gob/metaengine.cpp b/engines/gob/metaengine.cpp
index d2dc956f3f5..c5ccebf85bb 100644
--- a/engines/gob/metaengine.cpp
+++ b/engines/gob/metaengine.cpp
@@ -27,8 +27,15 @@
#include "engines/advancedDetector.h"
+#include "common/config-manager.h"
+#include "common/hashmap.h"
+
#include "common/translation.h"
+#include "gui/chooser.h"
+#include "gui/message.h"
+#include "gui/unknown-game-dialog.h"
+
#include "gob/gameidtotype.h"
#include "gob/gob.h"
@@ -75,9 +82,16 @@ bool Gob::GobEngine::hasFeature(EngineFeature f) const {
}
Common::Error GobMetaEngine::createInstance(OSystem *syst, Engine **engine, const Gob::GOBGameDescription *gd) const {
- *engine = new Gob::GobEngine(syst);
- ((Gob::GobEngine *)*engine)->initGame(gd);
- return Common::kNoError;
+
+ Gob::GobEngine *gobEngine = new Gob::GobEngine(syst);
+ *engine = gobEngine;
+ gobEngine->initGame(gd);
+ Common::ErrorCode errorCode = Common::kNoError;
+
+ if (gobEngine->gameTypeHasAddOns())
+ errorCode = gobEngine->updateAddOns(this, gd);
+
+ return errorCode;
}
@@ -101,6 +115,158 @@ GameType GobEngine::getGameType(const char *gameId) const {
error("Unknown game ID: %s", gameId);
}
+bool GobEngine::gameTypeHasAddOns() {
+ return getGameType() == kGameTypeAdibou1 ||
+ getGameType() == kGameTypeAdibou2 ||
+ getGameType() == kGameTypeAdi2 ||
+ getGameType() == kGameTypeAdi4;
+}
+
+
+// Accelerator, to discard some directories we know have no chance to be add-ons
+bool GobEngine::dirCanBeGameAddOn(Common::FSDirectory dir) const {
+ if (getGameType() == kGameTypeAdibou2)
+ return dir.hasFile("intro_ap.stk");
+
+ return true;
+}
+
+// To display a warning if a directory likely to be an add-on does not match anything
+bool GobEngine::dirMustBeGameAddOn(Common::FSDirectory dir) const {
+ if (getGameType() == kGameTypeAdibou2)
+ return dir.hasFile("intro_ap.stk");
+
+ return false;
+}
+
+Common::ErrorCode GobEngine::updateAddOns(const GobMetaEngine *metaEngine, const GOBGameDescription *gd) const {
+ const Plugin *detectionPlugin = EngineMan.findDetectionPlugin(metaEngine->getName());
+ if (!detectionPlugin) {
+ warning("Engine plugin for GOB not present. Add-ons detection is disabled");
+ return Common::kNoError;
+ }
+
+ // Update silently the targets associated with the add-ons, unless some unsupported version is detected
+
+ // List already registered add-ons for this game, and detect removed ones
+ Common::ConfigManager::DomainMap::iterator iter = ConfMan.beginGameDomains();
+ Common::HashMap<Common::Path, bool, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> existingAddOnsPaths;
+
+ bool anyAddOnRemoved = false;
+ for (; iter != ConfMan.endGameDomains(); ++iter) {
+ Common::String name(iter->_key);
+ Common::ConfigManager::Domain &dom = iter->_value;
+
+ Common::String parent;
+ if (dom.tryGetVal("parent", parent) && parent == ConfMan.getActiveDomainName()) {
+ // Existing add-on, check if its path still exists
+ Common::Path addOnPath(Common::Path::fromConfig(dom.getVal("path")));
+ if (addOnPath.empty() || !Common::FSNode(addOnPath).isDirectory()) {
+ // Path does not exist, remove the add-on
+ ConfMan.removeGameDomain(name);
+ anyAddOnRemoved = true;
+ } else {
+ existingAddOnsPaths[addOnPath] = true;
+ }
+ }
+ }
+
+ if (anyAddOnRemoved)
+ ConfMan.flushToDisk();
+
+ // Look for newly added add-ons
+ bool anyAddOnAdded = false;
+ const Common::FSNode gameDataDir(ConfMan.getPath("path"));
+ Common::FSList subdirNodes;
+ gameDataDir.getChildren(subdirNodes, Common::FSNode::kListDirectoriesOnly);
+ for (const Common::FSNode &subdirNode : subdirNodes) {
+ Common::FSDirectory subdir(subdirNode);
+ if (dirCanBeGameAddOn(subdir)) {
+ Common::FSList files;
+ if (!subdirNode.getChildren(files, Common::FSNode::kListAll))
+ continue;
+
+ ADCacheMan.clear();
+
+ DetectedGames detectedGames = detectionPlugin->get<MetaEngineDetection>().detectGames(files);
+ DetectedGames detectedAddOns;
+ for (DetectedGame &game : detectedGames) {
+ if (game.isAddOn) {
+ detectedAddOns.push_back(game);
+ }
+ }
+
+ int idx = 0;
+ if (detectedAddOns.empty() && dirMustBeGameAddOn(subdir)) {
+ Common::U32String msgFormat(_("The directory '%s' looks like an add-on for the game '%s', but ScummVM could not find any matching add-on in it."));
+ Common::U32String msg = Common::U32String::format(msgFormat,
+ subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str(),
+ gd->desc.gameId);
+
+ GUI::MessageDialog alert(msg);
+ alert.runModal();
+ continue;
+ } else if (detectedAddOns.size() == 1) {
+ // Exact match
+ idx = 0;
+ } else {
+ // Display the candidates to the user and let her/him pick one
+ Common::U32StringArray list;
+ for (idx = 0; idx < (int)detectedAddOns.size(); idx++) {
+ Common::U32String description = detectedAddOns[idx].description;
+
+ if (detectedAddOns[idx].hasUnknownFiles) {
+ description += Common::U32String(" - ");
+ // Unknown game variant
+ description += _("Unknown variant");
+ }
+
+ list.push_back(description);
+ }
+
+ Common::U32String msgFormat(_("Directory '%s' matches several add-ons, please pick one."));
+ Common::U32String msg = Common::U32String::format(msgFormat,
+ subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
+
+ GUI::ChooserDialog dialog(msg);
+ dialog.setList(list);
+ idx = dialog.runModal();
+ if (idx < 0)
+ return Common::kUserCanceled;
+ }
+
+ if (0 <= idx && idx < (int)detectedAddOns.size()) {
+ DetectedGame &selectedAddOn = detectedAddOns[idx];
+ selectedAddOn.path = subdirNode.getPath();
+ selectedAddOn.shortPath = subdirNode.getDisplayName();
+
+ if (selectedAddOn.hasUnknownFiles) {
+ GUI::UnknownGameDialog dialog(selectedAddOn);
+ dialog.runModal();
+ continue; // Do not create an entry for unknown variants
+ }
+
+ if (selectedAddOn.gameSupportLevel != kStableGame) {
+ if (!warnUserAboutUnsupportedAddOn(selectedAddOn.description)) {
+ return Common::kUserCanceled;
+ }
+ }
+
+ if (!existingAddOnsPaths.contains(subdirNode.getPath())) {
+ Common::String domain = EngineMan.createTargetForGame(selectedAddOn);
+ ConfMan.set("parent", ConfMan.getActiveDomainName(), domain);
+ anyAddOnAdded = true;
+ }
+ }
+ }
+ }
+
+ if (anyAddOnAdded)
+ ConfMan.flushToDisk();
+
+ return Common::kNoError;
+}
+
void GobEngine::initGame(const GOBGameDescription *gd) {
if (gd->startTotBase == nullptr)
_startTot = "intro.tot";
Commit: e773b9443ee89332752a1bafe6565d3148c8e61c
https://github.com/scummvm/scummvm/commit/e773b9443ee89332752a1bafe6565d3148c8e61c
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
GUI: Add-ons support in game launcher
- Display add-ons greyed out under their base game in list mode (unless
some grouping is active)
- Add an "- Add-on" suffix to add'ons' description
- Hide add-ons in grid mode
- Ensure add-ons cannot be run, edited or removed from the launcher
- Ensure removing the base game also removes all its add-ons
Changed paths:
gui/launcher.cpp
gui/launcher.h
diff --git a/gui/launcher.cpp b/gui/launcher.cpp
index de514473761..354d1a1fe5b 100644
--- a/gui/launcher.cpp
+++ b/gui/launcher.cpp
@@ -463,6 +463,14 @@ void LauncherDialog::removeGame(int item) {
assert(item >= 0);
ConfMan.removeGameDomain(_domains[item]);
+ // Remove all the add-ons for this game
+ const Common::ConfigManager::DomainMap &domains = ConfMan.getGameDomains();
+ for (const auto &domain : domains) {
+ if (domain._value.getValOrDefault("parent") == _domains[item]) {
+ ConfMan.removeGameDomain(domain._key);
+ }
+ }
+
// Write config to disk
ConfMan.flushToDisk();
@@ -566,13 +574,16 @@ void LauncherDialog::loadGame(int item) {
PluginMan.loadDetectionPlugin(); // only for uncached manager
}
-Common::Array<LauncherEntry> LauncherDialog::generateEntries(const Common::ConfigManager::DomainMap &domains) {
+Common::Array<LauncherEntry> LauncherDialog::generateEntries(const Common::ConfigManager::DomainMap &domains, bool skipAddOns) {
Common::Array<LauncherEntry> domainList;
for (const auto &domain : domains) {
// Do not list temporary targets added when starting a game from the command line
if (domain._value.contains("id_came_from_command_line"))
continue;
+ if (skipAddOns && domain._value.contains("parent"))
+ continue;
+
Common::String description;
Common::String title;
@@ -761,6 +772,11 @@ bool LauncherDialog::doGameDetection(const Common::Path &path) {
void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
int item = getSelected();
+ bool isAddOn = false;
+ if (item >= 0) {
+ Common::ConfigManager::Domain *domain = ConfMan.getDomain(_domains[item]);
+ isAddOn = domain && domain->contains("parent");
+ }
switch (cmd) {
case kAddGameCmd:
@@ -777,15 +793,15 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
break;
#endif
case kRemoveGameCmd:
- if (item < 0) return;
+ if (item < 0 || isAddOn) return;
removeGame(item);
break;
case kEditGameCmd:
- if (item < 0) return;
+ if (item < 0 || isAddOn) return;
editGame(item);
break;
case kLoadGameCmd:
- if (item < 0) return;
+ if (item < 0 || isAddOn) return;
loadGame(item);
break;
#ifdef ENABLE_EVENTRECORDER
@@ -806,7 +822,7 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
break;
case kStartCmd:
// Start the selected game.
- if (item < 0) return;
+ if (item < 0 || isAddOn) return;
ConfMan.setActiveDomain(_domains[item]);
close();
break;
@@ -1121,7 +1137,36 @@ void LauncherSimple::updateListing(int selPos) {
const bool scanEntries = (numEntries == -1) || ((int)domains.size() <= numEntries);
// Turn it into a sorted list of entries
- Common::Array<LauncherEntry> domainList = generateEntries(domains);
+ Common::Array<LauncherEntry> domainList = generateEntries(domains, false);
+ Common::HashMap<Common::String, Common::Array<const LauncherEntry*>> addOnsMap;
+
+ for (const auto &curDomain : domainList) {
+ Common::String parentDomain;
+ if (curDomain.domain->tryGetVal("parent", parentDomain)) {
+ Common::Array<const LauncherEntry*> &gameAddOns = addOnsMap.getOrCreateVal(parentDomain);
+ gameAddOns.push_back(&curDomain);
+ }
+ }
+
+ if (!addOnsMap.empty()) {
+ // Rebuild the list by adding add-ons just next to their parent game
+ Common::Array<LauncherEntry> newDomainList;
+ for (const auto &curDomain : domainList) {
+ if (curDomain.domain->contains("parent"))
+ continue;
+
+ newDomainList.push_back(curDomain);
+ Common::Array<const LauncherEntry*> gameAddOns;
+ if (addOnsMap.tryGetVal(curDomain.key, gameAddOns)) {
+ // Add add-ons for this game
+ for (const auto *addOn : gameAddOns) {
+ newDomainList.push_back(*addOn);
+ }
+ }
+ }
+
+ domainList = newDomainList;
+ }
// And fill out our structures
for (const auto &curDomain : domainList) {
@@ -1138,8 +1183,16 @@ void LauncherSimple::updateListing(int selPos) {
// description += Common::String::format(" (%s)", _("Not found"));
}
}
+
+ bool isAddOn = curDomain.domain->contains("parent");
+ if (isAddOn)
+ color = ThemeEngine::kFontColorAlternate;
+
Common::U32String gameDesc = GUI::ListWidget::getThemeColor(color) + Common::U32String(curDomain.description);
+ if (isAddOn)
+ gameDesc += Common::U32String(" - ") + _("Add-on");
+
l.push_back(gameDesc);
_domains.push_back(curDomain.key);
}
@@ -1339,16 +1392,22 @@ void LauncherSimple::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
}
void LauncherSimple::updateButtons() {
- bool enable = (_list->getSelected() >= 0);
+ int item = _list->getSelected();
+ bool isAddOn = false;
+ if (item >= 0) {
+ const Common::ConfigManager::Domain *domain = ConfMan.getDomain(_domains[item]);
+ isAddOn = domain && domain->contains("parent");
+ }
+
+ bool enable = (item >= 0 && !isAddOn);
_startButton->setEnabled(enable);
_editButton->setEnabled(enable);
_removeButton->setEnabled(enable);
- int item = _list->getSelected();
bool en = enable;
- if (item >= 0)
+ if (item >= 0 && !isAddOn)
en = !(Common::checkGameGUIOption(GUIO_NOLAUNCHLOAD, ConfMan.get("guioptions", _domains[item])));
_loadButton->setEnabled(en);
@@ -1558,7 +1617,7 @@ void LauncherGrid::updateListing(int selPos) {
const Common::ConfigManager::DomainMap &domains = ConfMan.getGameDomains();
// Turn it into a sorted list of entries
- Common::Array<LauncherEntry> domainList = generateEntries(domains);
+ Common::Array<LauncherEntry> domainList = generateEntries(domains, true);
Common::Array<GridItemInfo> gridList;
diff --git a/gui/launcher.h b/gui/launcher.h
index 2831f6ae3a9..cc98bb92d69 100644
--- a/gui/launcher.h
+++ b/gui/launcher.h
@@ -202,7 +202,7 @@ protected:
*/
void loadGame(int item);
- Common::Array<LauncherEntry> generateEntries(const Common::ConfigManager::DomainMap &domains);
+ Common::Array<LauncherEntry> generateEntries(const Common::ConfigManager::DomainMap &domains, bool skipAddOns);
/**
* Select the target with the given name in the launcher game list.
Commit: 807fd261f070962d7c057b5a0148d39a0b33280a
https://github.com/scummvm/scummvm/commit/807fd261f070962d7c057b5a0148d39a0b33280a
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
GUI: INTEGRITY: Support checking games with add-ons
- If a game has add-ons, display a dialog to let the user choose which
part should be checked
- When checking the base game, ignore the add-ons directories
- When checking an add-on, use its directory as root
Changed paths:
gui/integrity-dialog.cpp
gui/integrity-dialog.h
diff --git a/gui/integrity-dialog.cpp b/gui/integrity-dialog.cpp
index d54d9a29b77..0806228ce24 100644
--- a/gui/integrity-dialog.cpp
+++ b/gui/integrity-dialog.cpp
@@ -30,6 +30,7 @@
#include "common/tokenizer.h"
#include "common/translation.h"
+#include "gui/chooser.h"
#include "gui/gui-manager.h"
#include "gui/launcher.h"
#include "gui/message.h"
@@ -71,6 +72,7 @@ struct ChecksumDialogState {
Common::String endpoint;
Common::Path gamePath;
+ Common::HashMap<Common::Path, bool, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> ignoredSubdirsMap;
Common::String gameid;
Common::String engineid;
Common::String extra;
@@ -126,6 +128,48 @@ IntegrityDialog::IntegrityDialog(Common::String endpoint, Common::String domain)
g_checksum_state = new ChecksumDialogState();
g_checksum_state->dialog = this;
+ Common::Array<Common::String> gameAddOns;
+
+ Common::ConfigManager::DomainMap::iterator iter = ConfMan.beginGameDomains();
+ for (; iter != ConfMan.endGameDomains(); ++iter) {
+ Common::String name(iter->_key);
+ Common::ConfigManager::Domain &dom = iter->_value;
+
+ Common::String parent;
+ if (dom.tryGetVal("parent", parent) && parent == domain)
+ gameAddOns.push_back(name);
+ }
+
+ if (!gameAddOns.empty()) {
+ // Ask the user to choose between the base game or one of its add-ons
+ Common::U32StringArray list;
+ list.push_back(ConfMan.get("description", domain));
+
+ for (Common::String &gameAddOn : gameAddOns) {
+ list.push_back(ConfMan.get("description", gameAddOn));
+ }
+
+ ChooserDialog dialog(_("This game includes add-ons, pick the part you want to be checked:"));
+ dialog.setList(list);
+ int idx = dialog.runModal();
+ if (idx < 0) {
+ // User cancelled the dialog
+ _close = true;
+ return;
+ }
+
+ if (idx >= 1 && idx < (int)gameAddOns.size() + 1) {
+ // User selected an add-on, change the selected domain
+ domain = gameAddOns[idx - 1];
+ } else {
+ // User selected the base game, ignore the add-ons subdirectories
+ for (Common::String &gameAddOn : gameAddOns) {
+ Common::Path addOnPath = ConfMan.getPath("path", gameAddOn);
+ g_checksum_state->ignoredSubdirsMap[addOnPath] = true;
+ }
+ }
+ }
+
setState(kChecksumStateCalculating);
refreshWidgets();
@@ -136,7 +180,7 @@ IntegrityDialog::IntegrityDialog(Common::String endpoint, Common::String domain)
g_checksum_state->extra = ConfMan.get("extra", domain);
g_checksum_state->platform = ConfMan.get("platform", domain);
g_checksum_state->language = ConfMan.get("language", domain);
- calculateTotalSize(g_checksum_state->gamePath);
+ calculateTotalSize(g_checksum_state->gamePath, g_checksum_state->ignoredSubdirsMap);
} else {
g_checksum_state->dialog = this;
@@ -311,7 +355,7 @@ void IntegrityDialog::setError(Common::U32String &msg) {
_cancelButton->setCmd(kCleanupCmd);
}
-void IntegrityDialog::calculateTotalSize(Common::Path gamePath) {
+void IntegrityDialog::calculateTotalSize(Common::Path gamePath, const Common::HashMap<Common::Path, bool, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> &ignoredSubdirsMap) {
const Common::FSNode dir(gamePath);
if (!dir.exists() || !dir.isDirectory())
@@ -326,9 +370,10 @@ void IntegrityDialog::calculateTotalSize(Common::Path gamePath) {
// Process the files and subdirectories in the current directory recursively
for (const auto &entry : fileList) {
- if (entry.isDirectory())
- calculateTotalSize(entry.getPath());
- else {
+ if (entry.isDirectory()) {
+ if (!ignoredSubdirsMap.contains(entry.getPath()))
+ calculateTotalSize(entry.getPath(), ignoredSubdirsMap);
+ } else {
Common::File file;
if (!file.open(entry))
continue;
@@ -407,7 +452,8 @@ Common::Array<Common::StringArray> IntegrityDialog::generateChecksums(Common::Pa
continue;
if (entry.isDirectory()) {
- generateChecksums(entry.getPath(), fileChecksums, gamePath);
+ if (!g_checksum_state->ignoredSubdirsMap.contains(entry.getPath()))
+ generateChecksums(entry.getPath(), fileChecksums, gamePath);
continue;
}
diff --git a/gui/integrity-dialog.h b/gui/integrity-dialog.h
index 2b917eef92e..c2b3b0987f5 100644
--- a/gui/integrity-dialog.h
+++ b/gui/integrity-dialog.h
@@ -83,7 +83,7 @@ public:
void checksumResponseCallback(const Common::JSONValue *r);
void errorCallback(const Networking::ErrorResponse &error);
- void calculateTotalSize(Common::Path gamePath);
+ void calculateTotalSize(Common::Path gamePath, const Common::HashMap<Common::Path, bool, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> &ignoredSubdirsMap);
Common::Array<Common::StringArray> generateChecksums(Common::Path currentPath, Common::Array<Common::StringArray> &fileChecksums, Common::Path gamePath);
Common::JSONValue *generateJSONRequest(Common::Path gamePath, Common::String gameid, Common::String engineid, Common::String extra, Common::String platform, Common::String language);
Commit: b186077900333af97823064c48bdcb0f3de4ec95
https://github.com/scummvm/scummvm/commit/b186077900333af97823064c48bdcb0f3de4ec95
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
GOB: Add Adibou2 add-ons in game ids list
Changed paths:
engines/gob/detection/tables.h
diff --git a/engines/gob/detection/tables.h b/engines/gob/detection/tables.h
index 162533e2ba7..3c96451ef89 100644
--- a/engines/gob/detection/tables.h
+++ b/engines/gob/detection/tables.h
@@ -74,6 +74,11 @@ static const PlainGameDescriptor gobGames[] = {
{"adi5", "ADI 5"},
{"adibou1", "Adibou 1"},
{"adibou2", "Adibou 2"},
+ {"adibou2readcount45", "Adibou 2 Read/Count 4-5 years"},
+ {"adibou2readcount67", "Adibou 2 Read/Count 6-7 years"},
+ {"adibou2sciences", "Adibou 2 Nature & Sciences"},
+ {"adibou2anglais", "Adibou 2 Anglais"},
+ {"adibou2music", "Adibou 2 Music"},
{"adibou3", "Adibou 3"},
{"adiboucuisine", "Adibou présente la Cuisine"},
{"adiboudessin", "Adibou présente le Dessin"},
Commit: 54824deb8424102f1afcfc746958c4b09d5972f6
https://github.com/scummvm/scummvm/commit/54824deb8424102f1afcfc746958c4b09d5972f6
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
GOB: Add a few log messages in add-ons detection step
Changed paths:
engines/gob/metaengine.cpp
diff --git a/engines/gob/metaengine.cpp b/engines/gob/metaengine.cpp
index c5ccebf85bb..ec2e423d6b2 100644
--- a/engines/gob/metaengine.cpp
+++ b/engines/gob/metaengine.cpp
@@ -150,7 +150,7 @@ Common::ErrorCode GobEngine::updateAddOns(const GobMetaEngine *metaEngine, const
// List already registered add-ons for this game, and detect removed ones
Common::ConfigManager::DomainMap::iterator iter = ConfMan.beginGameDomains();
- Common::HashMap<Common::Path, bool, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> existingAddOnsPaths;
+ Common::HashMap<Common::Path, Common::String, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> existingAddOnsPaths;
bool anyAddOnRemoved = false;
for (; iter != ConfMan.endGameDomains(); ++iter) {
@@ -163,10 +163,14 @@ Common::ErrorCode GobEngine::updateAddOns(const GobMetaEngine *metaEngine, const
Common::Path addOnPath(Common::Path::fromConfig(dom.getVal("path")));
if (addOnPath.empty() || !Common::FSNode(addOnPath).isDirectory()) {
// Path does not exist, remove the add-on
+ debug("Removing entry of deleted add-on '%s' (former path: '%s')",
+ name.c_str(),
+ addOnPath.toString(Common::Path::kNativeSeparator).c_str());
+
ConfMan.removeGameDomain(name);
anyAddOnRemoved = true;
} else {
- existingAddOnsPaths[addOnPath] = true;
+ existingAddOnsPaths[addOnPath] = name;
}
}
}
@@ -241,6 +245,9 @@ Common::ErrorCode GobEngine::updateAddOns(const GobMetaEngine *metaEngine, const
selectedAddOn.shortPath = subdirNode.getDisplayName();
if (selectedAddOn.hasUnknownFiles) {
+ debug("Detected an unknown variant of add-on '%s' (path: '%s')",
+ selectedAddOn.gameId.c_str(),
+ subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
GUI::UnknownGameDialog dialog(selectedAddOn);
dialog.runModal();
continue; // Do not create an entry for unknown variants
@@ -252,8 +259,16 @@ Common::ErrorCode GobEngine::updateAddOns(const GobMetaEngine *metaEngine, const
}
}
- if (!existingAddOnsPaths.contains(subdirNode.getPath())) {
+ Common::String addOnName;
+ if (existingAddOnsPaths.tryGetVal(subdirNode.getPath(), addOnName)) {
+ debug("Detected existing add-on '%s' (path: '%s')",
+ addOnName.c_str(),
+ subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
+ } else {
Common::String domain = EngineMan.createTargetForGame(selectedAddOn);
+ debug("Detected new add-on '%s' (path: '%s')",
+ domain.c_str(),
+ subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
ConfMan.set("parent", ConfMan.getActiveDomainName(), domain);
anyAddOnAdded = true;
}
Commit: dcc50f046b5c8640f4326c8f15a73f8426d16f9a
https://github.com/scummvm/scummvm/commit/dcc50f046b5c8640f4326c8f15a73f8426d16f9a
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
GOB: Remove "extra" field in Adibou add-ons detection entries
It lead to too long labels in launcher list.
Changed paths:
engines/gob/detection/tables_adibou2.h
diff --git a/engines/gob/detection/tables_adibou2.h b/engines/gob/detection/tables_adibou2.h
index e828a048dce..7feb723f57c 100644
--- a/engines/gob/detection/tables_adibou2.h
+++ b/engines/gob/detection/tables_adibou2.h
@@ -282,7 +282,7 @@
{
{
"adibou2readcount45",
- "ADIBOU 2 - Lecture/Calcul 4-5 ans",
+ "", // "Lecture/Calcul 4-5 ans"
AD_ENTRY2s("intro_ap.stk", "7ff46d8c804186d3a11bf6b921fac2c0", 40835594,
"appli_01.vmd", "11635be4aeaac46d199e7e37cf905240", 54402),
FR_FRA,
@@ -298,7 +298,7 @@
{
{
"adibou2readcount67",
- "ADIBOU 2 - Lecture/Calcul 6-7 ans",
+ "", // "Lecture/Calcul 6-7 ans"
AD_ENTRY2s("intro_ap.stk", "0e91d0d693d5731353ad4738f4aa065c", 36540132,
"appli_03.vmd", "6bf95a48f366bdf8af3a198c7b723c77", 58858),
FR_FRA,
@@ -314,7 +314,7 @@
{
{
"adibou2sciences",
- "ADIBOU 2 - Je découvre la nature et les sciences",
+ "", // "Je découvre la nature et les sciences"
AD_ENTRY2s("intro_ap.stk", "bff25481fc05bc5c6a3aaa8c17e89e5b", 3446050,
"FICHES.ITK", "1670cc3373df162aed3219368665a1ca", 51025920),
FR_FRA,
@@ -330,7 +330,7 @@
{
{
"adibou2anglais",
- "ADIBOU 2 - Anglais",
+ "",
AD_ENTRY2s("intro_ap.stk", "1c83832cfeeace2a4b1b9ca448fc5322", 1967132,
"LIPSYNC.ITK", "90ea1687c8d40989b5ff52c7ecaaf8b3", 107792384),
FR_FRA,
@@ -346,7 +346,7 @@
{
{
"adibou2music",
- "ADIBOU 2 - Musique",
+ "",
AD_ENTRY2s("intro_ap.stk", "2147748e04ac11bd7155779e1456be07", 1631068,
"MUZIKO.ITK", "101cd1690f13bf458e3988822a46e942", 54806528),
FR_FRA,
Commit: e0634b8fd1f693b65ecc13ad1a4919fb3423636d
https://github.com/scummvm/scummvm/commit/e0634b8fd1f693b65ecc13ad1a4919fb3423636d
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-07-21T14:13:28+02:00
Commit Message:
ENGINES: Move add-ons detection logic from GOB to base Engine class
Changed paths:
base/main.cpp
engines/engine.cpp
engines/engine.h
engines/gob/gob.h
engines/gob/metaengine.cpp
diff --git a/base/main.cpp b/base/main.cpp
index 4ebcea52ad8..652b5881677 100644
--- a/base/main.cpp
+++ b/base/main.cpp
@@ -217,6 +217,12 @@ static Common::Error runGame(const Plugin *enginePlugin, OSystem &system, const
err = metaEngine.createInstance(&system, &engine, game, meDescriptor);
}
+ if (err.getCode() == Common::kNoError) {
+ // Update add-on targets
+ if (engine != nullptr && engine->gameTypeHasAddOns())
+ err = engine->updateAddOns(&metaEngine);
+ }
+
// Check for errors
if (!engine || err.getCode() != Common::kNoError) {
diff --git a/engines/engine.cpp b/engines/engine.cpp
index c1178a174fe..fe69372146b 100644
--- a/engines/engine.cpp
+++ b/engines/engine.cpp
@@ -46,11 +46,13 @@
#include "base/version.h"
#include "gui/gui-manager.h"
+#include "gui/chooser.h"
#include "gui/debugger.h"
#include "gui/dialog.h"
#include "gui/EventRecorder.h"
#include "gui/message.h"
#include "gui/saveload.h"
+#include "gui/unknown-game-dialog.h"
#include "audio/mixer.h"
@@ -1134,4 +1136,158 @@ void PauseToken::operator=(PauseToken &&t2) {
_engine = t2._engine;
t2._engine = nullptr;
}
+
+bool Engine::gameTypeHasAddOns() const {
+ return false;
+}
+
+bool Engine::dirCanBeGameAddOn(Common::FSDirectory) const {
+ return true;
+}
+
+bool Engine::dirMustBeGameAddOn(Common::FSDirectory) const {
+ return false;
+}
+
+Common::ErrorCode Engine::updateAddOns(const MetaEngine *metaEngine) const {
+ const Plugin *detectionPlugin = EngineMan.findDetectionPlugin(metaEngine->getName());
+ if (!detectionPlugin) {
+ warning("Engine plugin for %s not present. Add-ons detection is disabled", metaEngine->getName());
+ return Common::kNoError;
+ }
+
+ // List already registered add-ons for this game, and detect removed ones
+ Common::ConfigManager::DomainMap::iterator iter = ConfMan.beginGameDomains();
+ Common::HashMap<Common::Path, Common::String, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> existingAddOnsPaths;
+
+ bool anyAddOnRemoved = false;
+ for (; iter != ConfMan.endGameDomains(); ++iter) {
+ Common::String name(iter->_key);
+ Common::ConfigManager::Domain &dom = iter->_value;
+
+ Common::String parent;
+ if (dom.tryGetVal("parent", parent) && parent == ConfMan.getActiveDomainName()) {
+ // Existing add-on, check if its path still exists
+ Common::Path addOnPath(Common::Path::fromConfig(dom.getVal("path")));
+ if (addOnPath.empty() || !Common::FSNode(addOnPath).isDirectory()) {
+ // Path does not exist, remove the add-on
+ debug("Removing entry of deleted add-on '%s' (former path: '%s')",
+ name.c_str(),
+ addOnPath.toString(Common::Path::kNativeSeparator).c_str());
+
+ ConfMan.removeGameDomain(name);
+ anyAddOnRemoved = true;
+ } else {
+ existingAddOnsPaths[addOnPath] = name;
+ }
+ }
+ }
+
+ if (anyAddOnRemoved)
+ ConfMan.flushToDisk();
+
+ // Look for newly added add-ons
+ bool anyAddOnAdded = false;
+ const Common::FSNode gameDataDir(ConfMan.getPath("path"));
+ Common::FSList subdirNodes;
+ gameDataDir.getChildren(subdirNodes, Common::FSNode::kListDirectoriesOnly);
+ for (const Common::FSNode &subdirNode : subdirNodes) {
+ Common::FSDirectory subdir(subdirNode);
+ if (dirCanBeGameAddOn(subdir)) {
+ Common::FSList files;
+ if (!subdirNode.getChildren(files, Common::FSNode::kListAll))
+ continue;
+
+ ADCacheMan.clear();
+
+ DetectedGames detectedGames = detectionPlugin->get<MetaEngineDetection>().detectGames(files);
+ DetectedGames detectedAddOns;
+ for (DetectedGame &game : detectedGames) {
+ if (game.isAddOn) {
+ detectedAddOns.push_back(game);
+ }
+ }
+
+ int idx = 0;
+ if (detectedAddOns.empty() && dirMustBeGameAddOn(subdir)) {
+ Common::U32String msgFormat(_("The directory '%s' looks like an add-on for the game '%s', but ScummVM could not find any matching add-on in it."));
+ Common::U32String msg = Common::U32String::format(msgFormat,
+ subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str(),
+ ConfMan.getActiveDomainName().c_str());
+
+ GUI::MessageDialog alert(msg);
+ alert.runModal();
+ continue;
+ } else if (detectedAddOns.size() == 1) {
+ // Exact match
+ idx = 0;
+ } else {
+ // Display the candidates to the user and let her/him pick one
+ Common::U32StringArray list;
+ for (idx = 0; idx < (int)detectedAddOns.size(); idx++) {
+ Common::U32String description = detectedAddOns[idx].description;
+
+ if (detectedAddOns[idx].hasUnknownFiles) {
+ description += Common::U32String(" - ");
+ // Unknown game variant
+ description += _("Unknown variant");
+ }
+
+ list.push_back(description);
+ }
+
+ Common::U32String msgFormat(_("Directory '%s' matches several add-ons, please pick one."));
+ Common::U32String msg = Common::U32String::format(msgFormat,
+ subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
+
+ GUI::ChooserDialog dialog(msg);
+ dialog.setList(list);
+ idx = dialog.runModal();
+ if (idx < 0)
+ return Common::kUserCanceled;
+ }
+
+ if (0 <= idx && idx < (int)detectedAddOns.size()) {
+ DetectedGame &selectedAddOn = detectedAddOns[idx];
+ selectedAddOn.path = subdirNode.getPath();
+ selectedAddOn.shortPath = subdirNode.getDisplayName();
+
+ if (selectedAddOn.hasUnknownFiles) {
+ debug("Detected an unknown variant of add-on '%s' (path: '%s')",
+ selectedAddOn.gameId.c_str(),
+ subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
+ GUI::UnknownGameDialog dialog(selectedAddOn);
+ dialog.runModal();
+ continue; // Do not create an entry for unknown variants
+ }
+
+ if (selectedAddOn.gameSupportLevel != kStableGame) {
+ if (!warnUserAboutUnsupportedAddOn(selectedAddOn.description)) {
+ return Common::kUserCanceled;
+ }
+ }
+
+ Common::String addOnName;
+ if (existingAddOnsPaths.tryGetVal(subdirNode.getPath(), addOnName)) {
+ debug("Detected existing add-on '%s' (path: '%s')",
+ addOnName.c_str(),
+ subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
+ } else {
+ Common::String domain = EngineMan.createTargetForGame(selectedAddOn);
+ debug("Detected new add-on '%s' (path: '%s')",
+ domain.c_str(),
+ subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
+ ConfMan.set("parent", ConfMan.getActiveDomainName(), domain);
+ anyAddOnAdded = true;
+ }
+ }
+ }
+ }
+
+ if (anyAddOnAdded)
+ ConfMan.flushToDisk();
+
+ return Common::kNoError;
+}
+
#endif
diff --git a/engines/engine.h b/engines/engine.h
index c6e8651caf8..33af4c26e26 100644
--- a/engines/engine.h
+++ b/engines/engine.h
@@ -22,6 +22,7 @@
#ifndef ENGINES_ENGINE_H
#define ENGINES_ENGINE_H
+#include "common/error.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/language.h"
@@ -39,6 +40,7 @@ class Mixer;
}
namespace Common {
class Error;
+class FSDirectory;
class EventManager;
class SaveFileManager;
class TimerManager;
@@ -690,6 +692,27 @@ public:
return 0;
}
+ /**
+ * Can the game type currently being played have add-ons?
+ */
+ virtual bool gameTypeHasAddOns() const;
+
+ /**
+ * To discard some directories we know have no chance to be add-ons
+ */
+ virtual bool dirCanBeGameAddOn(Common::FSDirectory dir) const;
+
+ /**
+ * To display a warning if a directory likely to be an add-on does not match anything
+ */
+ virtual bool dirMustBeGameAddOn(Common::FSDirectory dir) const;
+
+ /**
+ * Update the add-ons targets associated with a base game (silently, unless some unsupported version is detected).
+ */
+ Common::ErrorCode updateAddOns(const MetaEngine *metaEngine) const;
+
+
protected:
/**
* Syncs the engine's mixer using the default volume syncing behavior.
diff --git a/engines/gob/gob.h b/engines/gob/gob.h
index a35df455b98..36fbe992a15 100644
--- a/engines/gob/gob.h
+++ b/engines/gob/gob.h
@@ -260,12 +260,11 @@ public:
~GobEngine() override;
void initGame(const GOBGameDescription *gd);
- Common::ErrorCode updateAddOns(const GobMetaEngine *metaEngine, const GOBGameDescription *gd) const;
GameType getGameType(const char *gameId) const;
- bool gameTypeHasAddOns();
- bool dirCanBeGameAddOn(Common::FSDirectory dir) const;
- bool dirMustBeGameAddOn(Common::FSDirectory dir) const;
+ bool gameTypeHasAddOns() const override;
+ bool dirCanBeGameAddOn(Common::FSDirectory dir) const override;
+ bool dirMustBeGameAddOn(Common::FSDirectory dir) const override;
/**
* Used to obtain the game version as a fallback
diff --git a/engines/gob/metaengine.cpp b/engines/gob/metaengine.cpp
index ec2e423d6b2..f6dafb8f954 100644
--- a/engines/gob/metaengine.cpp
+++ b/engines/gob/metaengine.cpp
@@ -32,10 +32,6 @@
#include "common/translation.h"
-#include "gui/chooser.h"
-#include "gui/message.h"
-#include "gui/unknown-game-dialog.h"
-
#include "gob/gameidtotype.h"
#include "gob/gob.h"
@@ -82,16 +78,10 @@ bool Gob::GobEngine::hasFeature(EngineFeature f) const {
}
Common::Error GobMetaEngine::createInstance(OSystem *syst, Engine **engine, const Gob::GOBGameDescription *gd) const {
-
Gob::GobEngine *gobEngine = new Gob::GobEngine(syst);
*engine = gobEngine;
gobEngine->initGame(gd);
- Common::ErrorCode errorCode = Common::kNoError;
-
- if (gobEngine->gameTypeHasAddOns())
- errorCode = gobEngine->updateAddOns(this, gd);
-
- return errorCode;
+ return Common::kNoError;
}
@@ -115,7 +105,7 @@ GameType GobEngine::getGameType(const char *gameId) const {
error("Unknown game ID: %s", gameId);
}
-bool GobEngine::gameTypeHasAddOns() {
+bool GobEngine::gameTypeHasAddOns() const {
return getGameType() == kGameTypeAdibou1 ||
getGameType() == kGameTypeAdibou2 ||
getGameType() == kGameTypeAdi2 ||
@@ -139,149 +129,6 @@ bool GobEngine::dirMustBeGameAddOn(Common::FSDirectory dir) const {
return false;
}
-Common::ErrorCode GobEngine::updateAddOns(const GobMetaEngine *metaEngine, const GOBGameDescription *gd) const {
- const Plugin *detectionPlugin = EngineMan.findDetectionPlugin(metaEngine->getName());
- if (!detectionPlugin) {
- warning("Engine plugin for GOB not present. Add-ons detection is disabled");
- return Common::kNoError;
- }
-
- // Update silently the targets associated with the add-ons, unless some unsupported version is detected
-
- // List already registered add-ons for this game, and detect removed ones
- Common::ConfigManager::DomainMap::iterator iter = ConfMan.beginGameDomains();
- Common::HashMap<Common::Path, Common::String, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> existingAddOnsPaths;
-
- bool anyAddOnRemoved = false;
- for (; iter != ConfMan.endGameDomains(); ++iter) {
- Common::String name(iter->_key);
- Common::ConfigManager::Domain &dom = iter->_value;
-
- Common::String parent;
- if (dom.tryGetVal("parent", parent) && parent == ConfMan.getActiveDomainName()) {
- // Existing add-on, check if its path still exists
- Common::Path addOnPath(Common::Path::fromConfig(dom.getVal("path")));
- if (addOnPath.empty() || !Common::FSNode(addOnPath).isDirectory()) {
- // Path does not exist, remove the add-on
- debug("Removing entry of deleted add-on '%s' (former path: '%s')",
- name.c_str(),
- addOnPath.toString(Common::Path::kNativeSeparator).c_str());
-
- ConfMan.removeGameDomain(name);
- anyAddOnRemoved = true;
- } else {
- existingAddOnsPaths[addOnPath] = name;
- }
- }
- }
-
- if (anyAddOnRemoved)
- ConfMan.flushToDisk();
-
- // Look for newly added add-ons
- bool anyAddOnAdded = false;
- const Common::FSNode gameDataDir(ConfMan.getPath("path"));
- Common::FSList subdirNodes;
- gameDataDir.getChildren(subdirNodes, Common::FSNode::kListDirectoriesOnly);
- for (const Common::FSNode &subdirNode : subdirNodes) {
- Common::FSDirectory subdir(subdirNode);
- if (dirCanBeGameAddOn(subdir)) {
- Common::FSList files;
- if (!subdirNode.getChildren(files, Common::FSNode::kListAll))
- continue;
-
- ADCacheMan.clear();
-
- DetectedGames detectedGames = detectionPlugin->get<MetaEngineDetection>().detectGames(files);
- DetectedGames detectedAddOns;
- for (DetectedGame &game : detectedGames) {
- if (game.isAddOn) {
- detectedAddOns.push_back(game);
- }
- }
-
- int idx = 0;
- if (detectedAddOns.empty() && dirMustBeGameAddOn(subdir)) {
- Common::U32String msgFormat(_("The directory '%s' looks like an add-on for the game '%s', but ScummVM could not find any matching add-on in it."));
- Common::U32String msg = Common::U32String::format(msgFormat,
- subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str(),
- gd->desc.gameId);
-
- GUI::MessageDialog alert(msg);
- alert.runModal();
- continue;
- } else if (detectedAddOns.size() == 1) {
- // Exact match
- idx = 0;
- } else {
- // Display the candidates to the user and let her/him pick one
- Common::U32StringArray list;
- for (idx = 0; idx < (int)detectedAddOns.size(); idx++) {
- Common::U32String description = detectedAddOns[idx].description;
-
- if (detectedAddOns[idx].hasUnknownFiles) {
- description += Common::U32String(" - ");
- // Unknown game variant
- description += _("Unknown variant");
- }
-
- list.push_back(description);
- }
-
- Common::U32String msgFormat(_("Directory '%s' matches several add-ons, please pick one."));
- Common::U32String msg = Common::U32String::format(msgFormat,
- subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
-
- GUI::ChooserDialog dialog(msg);
- dialog.setList(list);
- idx = dialog.runModal();
- if (idx < 0)
- return Common::kUserCanceled;
- }
-
- if (0 <= idx && idx < (int)detectedAddOns.size()) {
- DetectedGame &selectedAddOn = detectedAddOns[idx];
- selectedAddOn.path = subdirNode.getPath();
- selectedAddOn.shortPath = subdirNode.getDisplayName();
-
- if (selectedAddOn.hasUnknownFiles) {
- debug("Detected an unknown variant of add-on '%s' (path: '%s')",
- selectedAddOn.gameId.c_str(),
- subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
- GUI::UnknownGameDialog dialog(selectedAddOn);
- dialog.runModal();
- continue; // Do not create an entry for unknown variants
- }
-
- if (selectedAddOn.gameSupportLevel != kStableGame) {
- if (!warnUserAboutUnsupportedAddOn(selectedAddOn.description)) {
- return Common::kUserCanceled;
- }
- }
-
- Common::String addOnName;
- if (existingAddOnsPaths.tryGetVal(subdirNode.getPath(), addOnName)) {
- debug("Detected existing add-on '%s' (path: '%s')",
- addOnName.c_str(),
- subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
- } else {
- Common::String domain = EngineMan.createTargetForGame(selectedAddOn);
- debug("Detected new add-on '%s' (path: '%s')",
- domain.c_str(),
- subdirNode.getPath().toString(Common::Path::kNativeSeparator).c_str());
- ConfMan.set("parent", ConfMan.getActiveDomainName(), domain);
- anyAddOnAdded = true;
- }
- }
- }
- }
-
- if (anyAddOnAdded)
- ConfMan.flushToDisk();
-
- return Common::kNoError;
-}
-
void GobEngine::initGame(const GOBGameDescription *gd) {
if (gd->startTotBase == nullptr)
_startTot = "intro.tot";
More information about the Scummvm-git-logs
mailing list