[Scummvm-git-logs] scummvm master -> 18c3591548d0665b4b92a1810f3a57f9f2797ba4

spleen1981 noreply at scummvm.org
Sun Dec 31 15:59:44 UTC 2023


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

Summary:
18c3591548 LIBRETRO: add Libretro Playlist Generator to ScummVM GUI


Commit: 18c3591548d0665b4b92a1810f3a57f9f2797ba4
    https://github.com/scummvm/scummvm/commit/18c3591548d0665b4b92a1810f3a57f9f2797ba4
Author: Giovanni Cascione (ing.cascione at gmail.com)
Date: 2023-12-31T16:59:38+01:00

Commit Message:
LIBRETRO: add Libretro Playlist Generator to ScummVM GUI

Changed paths:
  A backends/platform/libretro/include/libretro-options-widget.h
  A backends/platform/libretro/src/libretro-options-widget.cpp
    backends/platform/libretro/Makefile.common
    backends/platform/libretro/include/libretro-os.h
    backends/platform/libretro/src/libretro-os-base.cpp
    backends/platform/libretro/src/libretro-os-utils.cpp


diff --git a/backends/platform/libretro/Makefile.common b/backends/platform/libretro/Makefile.common
index 0ac38c13550..308bde8f444 100644
--- a/backends/platform/libretro/Makefile.common
+++ b/backends/platform/libretro/Makefile.common
@@ -128,7 +128,8 @@ LIBRETRO_OBJS := $(CORE_PATH)/libretro-core.o \
 	$(CORE_PATH)/libretro-os-utils.o \
 	$(CORE_PATH)/libretro-threads.o \
 	$(CORE_PATH)/libretro-timer.o \
-	$(CORE_PATH)/libretro-mapper.o
+	$(CORE_PATH)/libretro-mapper.o \
+	$(CORE_PATH)/libretro-options-widget.o
 
 OBJS += $(LIBRETRO_OBJS)
 
diff --git a/backends/platform/libretro/include/libretro-options-widget.h b/backends/platform/libretro/include/libretro-options-widget.h
new file mode 100644
index 00000000000..365042f947d
--- /dev/null
+++ b/backends/platform/libretro/include/libretro-options-widget.h
@@ -0,0 +1,47 @@
+/* Copyright (C) 2024 Giovanni Cascione <ing.cascione at gmail.com>
+ *
+ * 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 "common/translation.h"
+#include "gui/browser.h"
+#include "gui/gui-manager.h"
+#include "gui/ThemeEval.h"
+#include "gui/widget.h"
+#include "gui/widgets/list.h"
+#include "gui/widgets/popup.h"
+#include "gui/widgets/richtext.h"
+
+#define COMMON_HOOKS_FOLDER "scummvm_hooks"
+
+class LibretroOptionsWidget final : public GUI::OptionsContainerWidget {
+public:
+	explicit LibretroOptionsWidget(GuiObject *boss, const Common::String &name, const Common::String &domain);
+	~LibretroOptionsWidget() override;
+	void load() override;
+	bool save() override;
+
+private:
+	void defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const override;
+	void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override;
+	bool generatePlaylist(Common::String playlistPath);
+	bool cleanFolder(Common::String &path);
+
+	GUI::StaticTextWidget *_playlistPath;
+	GUI::StaticTextWidget *_playlistStatus;
+
+	GUI::PopUpWidget *_playlistVersion;
+	GUI::PopUpWidget *_hooksLocation;
+	GUI::CheckboxWidget *_hooksClear;
+};
diff --git a/backends/platform/libretro/include/libretro-os.h b/backends/platform/libretro/include/libretro-os.h
index 95090f81078..4d79afd1c0f 100644
--- a/backends/platform/libretro/include/libretro-os.h
+++ b/backends/platform/libretro/include/libretro-os.h
@@ -144,6 +144,8 @@ public:
 	PaletteManager *getPaletteManager() override { return this; }
 	Graphics::Surface *lockScreen() override { return &_gameScreen; }
 	void unlockScreen() override {}
+	GUI::OptionsContainerWidget *buildBackendOptionsWidget(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const override;
+	void applyBackendSettings() override;
 protected:
 	void setPalette(const byte *colors, uint start, uint num) override;
 	void grabPalette(byte *colors, uint start, uint num) const override;
@@ -165,6 +167,8 @@ public:
 	void logMessage(LogMessageType::Type type, const char *message) override;
 	int testGame(const char *filedata, bool autodetect);
 	void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0) override {}
+	const char * const *buildHelpDialogData() override;
+	Common::String getSaveDir(void);
 private:
 	bool parseGameName(const Common::String &gameName, Common::String &engineId, Common::String &gameId);
 
diff --git a/backends/platform/libretro/src/libretro-options-widget.cpp b/backends/platform/libretro/src/libretro-options-widget.cpp
new file mode 100644
index 00000000000..a7ccf6f8da4
--- /dev/null
+++ b/backends/platform/libretro/src/libretro-options-widget.cpp
@@ -0,0 +1,294 @@
+/* Copyright (C) 2024 Giovanni Cascione <ing.cascione at gmail.com>
+ *
+ * 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/>.
+ *
+ */
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+#include <streams/file_stream.h>
+#include "backends/platform/libretro/include/libretro-options-widget.h"
+#include "backends/platform/libretro/include/libretro-core.h"
+#include "backends/platform/libretro/include/libretro-fs.h"
+#include "backends/platform/libretro/include/libretro-os.h"
+#include "gui/launcher.h"
+
+
+enum {
+	kPlaylistPathCmd = 'pchp',
+	kPlaylistPathClearCmd = 'pclp',
+	kPlaylistGenerateCmd = 'pgen'
+};
+
+enum {
+	kPlaylistFormatJSON = 0,
+	kPlaylistFormat6lines,
+};
+
+enum {
+	kHooksLocationSave = 0,
+	kHooksLocationGame,
+};
+
+LibretroOptionsWidget::LibretroOptionsWidget(GuiObject *boss, const Common::String &name, const Common::String &domain) :
+	OptionsContainerWidget(boss, name, "LibretroOptionsDialog", false, domain) {
+
+	new GUI::StaticTextWidget(widgetsBoss(), "LibretroOptionsDialog.PlaylistHeader", _("LIBRETRO PLAYLIST GENERATOR"));
+	new GUI::StaticTextWidget(widgetsBoss(), "LibretroOptionsDialog.PlaylistSubheader", _("(check '? > Libretro playlist' for detailed info)"));
+	(new GUI::ButtonWidget(widgetsBoss(), "LibretroOptionsDialog.PlaylistPathButton", _("Playlists Path"), Common::U32String(), kPlaylistPathCmd))->setTarget(this);
+	_playlistPath = new GUI::StaticTextWidget(widgetsBoss(), "LibretroOptionsDialog.PlaylistPath", _("Specifies where your playlist will be saved."));
+	GUI::addClearButton(widgetsBoss(), "LibretroOptionsDialog.PlaylistPathButtonClear", kPlaylistPathClearCmd);
+
+	new GUI::StaticTextWidget(widgetsBoss(), "LibretroOptionsDialog.PlaylistVersionText", _("Playlist format:"));
+	_playlistVersion = new GUI::PopUpWidget(widgetsBoss(), "LibretroOptionsDialog.PlaylistVersion");
+	_playlistVersion->appendEntry(Common::U32String("JSON "), kPlaylistFormatJSON);
+	_playlistVersion->appendEntry(Common::U32String("6-lines"), kPlaylistFormat6lines);
+
+	new GUI::StaticTextWidget(widgetsBoss(), "LibretroOptionsDialog.HooksLocationText", _("Hooks location:"));
+	_hooksLocation = new GUI::PopUpWidget(widgetsBoss(), "LibretroOptionsDialog.HooksLocation");
+	_hooksLocation->appendEntry(_("All in save folder"), kHooksLocationSave);
+	_hooksLocation->appendEntry(_("One in each game folder"), kHooksLocationGame);
+
+	_hooksClear = new GUI::CheckboxWidget(widgetsBoss(), "LibretroOptionsDialog.HooksClear", _("Clear existing hooks"));
+
+	(new GUI::ButtonWidget(widgetsBoss(), "LibretroOptionsDialog.PlaylistGenerateButton", _("Generate playlist"), Common::U32String(), kPlaylistGenerateCmd))->setTarget(this);
+
+	new GUI::StaticTextWidget(widgetsBoss(), "LibretroOptionsDialog.PlaylistStatusText", _("Status: "));
+	_playlistStatus = new GUI::StaticTextWidget(widgetsBoss(), "LibretroOptionsDialog.PlaylistStatus", Common::String("-"));
+}
+
+LibretroOptionsWidget::~LibretroOptionsWidget() {
+}
+
+void LibretroOptionsWidget::defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const {
+	layouts.addDialog(layoutName, overlayedLayout)
+	.addLayout(GUI::ThemeLayout::kLayoutVertical, 8)
+	.addPadding(16, 16, 16, 16)
+	.addWidget("PlaylistHeader", "", -1, layouts.getVar("Globals.Line.Height"))
+	.addWidget("PlaylistSubheader", "", -1, layouts.getVar("Globals.Line.Height"))
+	.addLayout(GUI::ThemeLayout::kLayoutHorizontal, 10, GUI::ThemeLayout::kItemAlignCenter)
+	.addPadding(0, 0, 0, 0)
+	.addWidget("PlaylistPathButton", "Button")
+	.addWidget("PlaylistPath", "", -1, layouts.getVar("Globals.Line.Height"))
+	.addWidget("PlaylistPathButtonClear", "SearchClearButton", layouts.getVar("Globals.Line.Height"), layouts.getVar("Globals.Line.Height"))
+	.closeLayout()
+
+	.addLayout(GUI::ThemeLayout::kLayoutHorizontal)
+	.addPadding(0, 0, 0, 0)
+	.addWidget("PlaylistVersionText", "OptionsLabel")
+	.addWidget("PlaylistVersion", "PopUp")
+	.closeLayout()
+
+	.addLayout(GUI::ThemeLayout::kLayoutHorizontal)
+	.addPadding(0, 0, 0, 0)
+	.addWidget("HooksLocationText", "OptionsLabel")
+	.addWidget("HooksLocation", "PopUp")
+	.closeLayout()
+
+	.addLayout(GUI::ThemeLayout::kLayoutHorizontal)
+	.addPadding(0, 0, 0, 0)
+	.addWidget("HooksClear", "Checkbox")
+	.closeLayout()
+
+	.addLayout(GUI::ThemeLayout::kLayoutHorizontal)
+	.addPadding(0, 0, 0, 0)
+	.addWidget("PlaylistGenerateButton", "WideButton")
+	.addSpace(layouts.getVar("Globals.Line.Height") * 2)
+	.addWidget("PlaylistStatusText", "", -1, layouts.getVar("Globals.Line.Height"))
+	.addWidget("PlaylistStatus", "", -1, layouts.getVar("Globals.Line.Height"))
+	.closeLayout()
+
+	.closeLayout()
+	.closeDialog();
+}
+
+bool LibretroOptionsWidget::cleanFolder(Common::String &path) {
+	bool res = true;
+	Common::ArchiveMemberList listHooks;
+	Common::FSNode(Common::Path(path)).listChildren(listHooks, "*." CORE_EXTENSIONS);
+	for (Common::ArchiveMemberPtr hook : listHooks) {
+		Common::FSNode *fshook = dynamic_cast<Common::FSNode *>(hook.get());
+		if (fshook->isDirectory())
+			continue;
+		if (remove(fshook->getPath().toString().c_str()) == 0)
+			retro_log_cb(RETRO_LOG_INFO, "Hook file deleted in '%s'.\n", fshook->getPath().toString().c_str());
+		else {
+			res = false;
+			retro_log_cb(RETRO_LOG_WARN, "Failed to delete hook file in '%s'.\n", fshook->getPath().toString().c_str());
+		}
+	}
+	return res;
+}
+
+bool LibretroOptionsWidget::generatePlaylist(Common::String playlistPath) {
+	bool isFirstEntry = true;
+	bool success = true;
+	bool cleanSuccess = true;
+	Common::String playlistElement;
+	Common::String playlistFooter;
+	Common::String path;
+	char separator[2] = {0};
+	Common::String hookPath;
+	Common::String hookFilePath;
+	Common::String title;
+
+	/* Create playlist file */
+	RFILE *playlistFile = filestream_open(Common::String(playlistPath + "/" + CORE_NAME + ".lpl").c_str(), RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
+	if (!playlistFile) {
+		_playlistStatus->setLabel(_("Failed, can't access playlist file"));
+		retro_log_cb(RETRO_LOG_ERROR, "Failed to access playlst file at '%s'.\n", Common::String(playlistPath + CORE_NAME + ".lpl").c_str());
+		return false;
+	} else
+		retro_log_cb(RETRO_LOG_INFO, "Playlist file created in '%s'.\n", Common::String(playlistPath + CORE_NAME + ".lpl").c_str());
+
+	/* Create common hook folder */
+	if (ConfMan.getInt("libretro_hooks_location", _domain) != kHooksLocationGame) {
+		hookPath = LibRetroFilesystemNode(Common::String(LIBRETRO_G_SYSTEM->getSaveDir())).getPath() + "/" + COMMON_HOOKS_FOLDER;
+		LibRetroFilesystemNode(hookPath).createDirectory();
+		if (ConfMan.getBool("libretro_hooks_clear", _domain)) {
+			cleanSuccess = cleanFolder(hookPath);
+		}
+	}
+
+	/* Setup playlist template */
+	if (ConfMan.getInt("libretro_playlist_version", _domain) != kPlaylistFormat6lines) {
+		const char *cCorePath = retro_get_core_dir();
+		LibRetroFilesystemNode corePath(".");
+		if (cCorePath)
+			corePath = LibRetroFilesystemNode(Common::String(cCorePath));
+		filestream_printf(playlistFile, "{\n  \"version\": \"1.5\",\n  \"default_core_path\": \"%s\",\n  \"default_core_name\": \"ScummVM\",\n  \"label_display_mode\": 0,\n  \"right_thumbnail_mode\": 2,\n  \"left_thumbnail_mode\": 3,\n  \"sort_mode\": 0,\n  \"items\": [", corePath.exists() ? corePath.getPath().c_str() : "");
+		playlistElement = "%s\n    {\n      \"path\": \"%s\",\n      \"label\": \"%s\",\n      \"core_path\": \"DETECT\",\n      \"core_name\": \"DETECT\",\n      \"crc32\": \"DETECT\",\n      \"db_name\": \"" CORE_NAME ".lpl\"\n    }";
+		playlistFooter = "\n  ]\n}";
+	} else
+		playlistElement = "%s%s\n%s\nDETECT\nDETECT\nDETECT\nScummVM.lpl\n";
+
+	/* Crawl ScummVM game list */
+	Common::ConfigManager::DomainMap::iterator iter = ConfMan.beginGameDomains();
+	for (; iter != ConfMan.endGameDomains(); ++iter) {
+
+		if (!iter->_value.tryGetVal("path", path) || iter->_value.contains("id_came_from_command_line"))
+			continue;
+
+		Common::Path cleanPath = Common::Path::fromConfig(path);
+		if (!Common::FSNode(cleanPath).isDirectory())
+			continue;
+
+		if (ConfMan.getInt("libretro_hooks_location", _domain) == kHooksLocationGame) {
+			hookPath = LibRetroFilesystemNode(cleanPath.toString()).getPath();
+			if (ConfMan.getBool("libretro_hooks_clear", _domain))
+				if (!cleanFolder(hookPath)) cleanSuccess = false;
+		}
+
+		title = iter->_key;
+		iter->_value.tryGetVal("description", title);
+		hookFilePath = hookPath + + "/" + iter->_key.c_str() + "." + CORE_EXTENSIONS;
+
+		filestream_printf(playlistFile, playlistElement.c_str(), separator, hookFilePath.c_str(), title.c_str());
+
+		if (isFirstEntry && !ConfMan.getInt("libretro_playlist_version", _domain) == kPlaylistFormat6lines) {
+			*separator = ',';
+			isFirstEntry = false;
+		}
+
+		/* Create hook file */
+		RFILE *hookFile = filestream_open(hookFilePath.c_str(), RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
+		if (!hookFile) {
+			retro_log_cb(RETRO_LOG_ERROR, "Failed to access/create hook file at '%s'.\n", hookFilePath.c_str());
+			success = false;
+			break;
+		} else
+			retro_log_cb(RETRO_LOG_INFO, "Hook file created in '%s'.\n", hookFilePath.c_str());
+
+
+		filestream_printf(hookFile, "%s", iter->_key.c_str());
+		filestream_close(hookFile);
+	}
+
+	filestream_printf(playlistFile, playlistFooter.c_str());
+	filestream_close(playlistFile);
+
+	Common::String response;
+	if (success){
+		response = _("Done");
+		if (!cleanSuccess)
+			response += " (" +  _("cleaning failed") + ")";
+	} else
+		response = _("Failed, can't create hook files");
+
+	_playlistStatus->setLabel(response);
+
+	return success;
+}
+
+void LibretroOptionsWidget::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
+	switch (cmd) {
+	case kPlaylistPathCmd: {
+		GUI::BrowserDialog browser(_("Select Playlist directory"), true);
+		if (browser.runModal() > 0) {
+			Common::FSNode dir(browser.getResult());
+			_playlistPath->setLabel(dir.getPath().toString());
+		}
+		break;
+	}
+	case kPlaylistPathClearCmd: {
+		_playlistPath->setLabel(_c("None", "path"));
+		break;
+	}
+	case kPlaylistGenerateCmd: {
+		save();
+		_playlistStatus->setLabel(Common::String("-"));
+		generatePlaylist(ConfMan.get("libretro_playlist_path"));
+		break;
+	}
+	default:
+		GUI::OptionsContainerWidget::handleCommand(sender, cmd, data);
+	}
+
+}
+
+void LibretroOptionsWidget::load() {
+	_playlistPath->setLabel(ConfMan.get("libretro_playlist_path"));
+
+	if (ConfMan.getInt("libretro_playlist_version", _domain) == kPlaylistFormat6lines)
+		_playlistVersion->setSelected(kPlaylistFormat6lines);
+	else
+		_playlistVersion->setSelected(kPlaylistFormatJSON);
+
+	if (ConfMan.getInt("libretro_hooks_location", _domain) == kHooksLocationGame)
+		_hooksLocation->setSelected(kHooksLocationGame);
+	else
+		_hooksLocation->setSelected(kHooksLocationSave);
+
+	_hooksClear->setState(ConfMan.getBool("libretro_hooks_clear", _domain));
+}
+
+bool LibretroOptionsWidget::save() {
+	Common::String data = _playlistPath->getLabel();
+	if (data.empty())
+		ConfMan.set("libretro_playlist_path", "", _domain);
+	else
+		ConfMan.set("libretro_playlist_path", data, _domain);
+
+	if (_playlistVersion->getSelected() == kPlaylistFormat6lines)
+		ConfMan.setInt("libretro_playlist_version", kPlaylistFormat6lines, _domain);
+	else
+		ConfMan.setInt("libretro_playlist_version", kPlaylistFormatJSON, _domain);
+
+	if (_hooksLocation->getSelected() == kHooksLocationGame)
+		ConfMan.setInt("libretro_hooks_location", kHooksLocationGame, _domain);
+	else
+		ConfMan.setInt("libretro_hooks_location", kHooksLocationSave, _domain);
+
+	ConfMan.setBool("libretro_hooks_clear", _hooksClear->getState(), _domain);
+
+	return true;
+}
diff --git a/backends/platform/libretro/src/libretro-os-base.cpp b/backends/platform/libretro/src/libretro-os-base.cpp
index c200a92e258..896d589a349 100644
--- a/backends/platform/libretro/src/libretro-os-base.cpp
+++ b/backends/platform/libretro/src/libretro-os-base.cpp
@@ -88,7 +88,7 @@ void OSystem_libretro::initBackend() {
 		retro_log_cb(RETRO_LOG_DEBUG, "Default save path set to: %s\n", s_saveDir.c_str());
 	}
 
-	//Check current paths
+	//Check current path settings
 	if (!checkPathSetting("savepath", s_saveDir))
 		retro_osd_notification("ScummVM save folder not found.");
 	if (!checkPathSetting("themepath", s_themeDir))
@@ -97,6 +97,17 @@ void OSystem_libretro::initBackend() {
 		retro_osd_notification("ScummVM extra folder not found. Some engines/features (e.g. Virtual Keyboard) will not work without relevant datafiles.");
 	checkPathSetting("soundfont", s_soundfontPath, false);
 	checkPathSetting("browser_lastpath", s_homeDir);
+	checkPathSetting("libretro_playlist_path", s_homeDir);
+
+	//Check other settings
+	if (! ConfMan.hasKey("libretro_playlist_version"))
+		ConfMan.set("libretro_playlist_version", 0);
+
+	if (! ConfMan.hasKey("libretro_hooks_location"))
+		ConfMan.set("libretro_hooks_location", 0);
+
+	if (! ConfMan.hasKey("libretro_hooks_clear"))
+		ConfMan.set("libretro_hooks_clear", 0);
 
 	_savefileManager = new DefaultSaveFileManager();
 
@@ -158,7 +169,7 @@ void OSystem_libretro::destroy() {
 }
 
 bool OSystem_libretro::checkPathSetting(const char *setting, Common::String const &defaultPath, bool isDirectory) {
-	Common::String setPath(ConfMan.get(setting));
+	Common::String setPath(Common::Path::fromConfig(setting).toString());
 
 	if (setPath.empty() || ! (isDirectory ? LibRetroFilesystemNode(setPath).isDirectory() : LibRetroFilesystemNode(setPath).exists()))
 		ConfMan.removeKey(setting, Common::ConfigManager::kApplicationDomain);
diff --git a/backends/platform/libretro/src/libretro-os-utils.cpp b/backends/platform/libretro/src/libretro-os-utils.cpp
index 5a815fa3316..da338e1fb34 100644
--- a/backends/platform/libretro/src/libretro-os-utils.cpp
+++ b/backends/platform/libretro/src/libretro-os-utils.cpp
@@ -25,6 +25,7 @@
 #include "backends/platform/libretro/include/libretro-defs.h"
 #include "backends/platform/libretro/include/libretro-core.h"
 #include "backends/platform/libretro/include/libretro-os.h"
+#include "backends/platform/libretro/include/libretro-options-widget.h"
 
 void OSystem_libretro::getTimeAndDate(TimeDate &t, bool skipRecord) const {
 	uint32 curTime = (uint32)(cpu_features_get_time_usec() / 1000000);
@@ -153,3 +154,77 @@ int OSystem_libretro::testGame(const char *filedata, bool autodetect) {
 	PluginManager::destroy();
 	return res;
 }
+
+GUI::OptionsContainerWidget *OSystem_libretro::buildBackendOptionsWidget(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const {
+	if (target.equalsIgnoreCase(Common::ConfigManager::kApplicationDomain))
+		return new LibretroOptionsWidget(boss, name, target);
+	else
+		return nullptr;
+
+}
+
+void OSystem_libretro::applyBackendSettings() {
+	return;
+}
+
+static const char * const helpTabs[] = {
+_s("Libretro playlist"),
+"",
+_s(
+"## Libretro playlists for ScummVM core\n"
+"Playlists used in Libretro frontends (e.g. Retroarch) are plain text lists used to directly launch a game with a specific core from the user interface. Those lists are structured to pass to the core the path of a specific content file to be loaded (e.g. ROM).\n"
+"\n"
+"ScummVM core can accept as content the path to any of the files inside a valid game folder, the detection system will try to autodetect the game from the content file parent folder and run the game with default ScummVM options.\n"
+"\n"
+"The core also supports dedicated per game **hook** plain text files with **." CORE_EXTENSIONS "** extension, which can be used as target in the playlist to specify one of the following ScummVM identifiers:\n"
+"\n"
+"  - **game ID**: this is a unique identifier for any game supported by ScummVM. In this case hook files must be placed inside each game folder, and there is no need to add the game from within ScummVM. Game will be launched with default ScummVM options.\n"
+"\n"
+"  - **target**: this is the game identifier from ScummVM configuration file (e.g. 'scummvm.ini'). In this case the game must be added from ScummVM GUI first, and the hook files can be placed anywhere, as the path for the game files is already part of the target configuration. The game will be launched with the options set in ScummVM\n"
+"\n"
+"## Creating ScummVM core playlist\n"
+"ScummVM core playlist can be created in the following ways:\n"
+"\n"
+"  1. Manually (hook files to be created manually - optional)\n"
+"\n"
+"  2. Automatically from Retroarch scanner (hook files not used)\n"
+"\n"
+"  3. Automatically from ScummVM GUI (hook files created automatically)\n"
+"\n"
+"First two methods are not covered here, as outside of ScummVM scope. Detailed info can be found in [Libretro documentation](https://docs.libretro.com/guides/roms-playlists-thumbnails/).\n"
+"Note that Retroarch scanner is based on a third party database instead of ScummVM game detection system, hence it is not guaranteed to work properly.\n"
+"\n"
+"Third method is covered in the following subheading.\n"
+"\n"
+"## ScummVM Playlist Generator\n"
+"ScummVM core includes a tool to generate a Libretro playlist and needed hook files based on current ScummVM games list.\n"
+"\n"
+" - Load the core from RetroArch and start it to reach the ScummVM GUI (i.e. the Launcher)\n"
+"\n"
+" - Add games to the list as required using the GUI buttons ('Mass Add' available).\n"
+"\n"
+" - Select **Global Options** and then the **Backend** tab.\n"
+"\n"
+" - Check or select the path of frontend playlists. A '" CORE_NAME ".lpl' file will be created or overwritten in there.\n"
+"\n"
+" - Check the 'Hooks location' setting, to have one '." CORE_EXTENSIONS "' in each game folder or all of them in a '" COMMON_HOOKS_FOLDER "' folder in the 'save' path.\n"
+"\n"
+" - Check the 'Playlist version' setting. JSON format should be selected, 6-lines format is deprecated and provided for backwards compatibility only.\n"
+"\n"
+" - Select the 'Clear existing hooks' checkbox to remove any existing '." CORE_EXTENSIONS "' file in the working folders.\n"
+"\n"
+" - Press the 'Generate playlist' button.\n"
+"\n"
+"Operation status will be shown in the same dialog, while details will be given in frontend logs."
+),
+
+0 // End of list
+};
+
+const char * const *OSystem_libretro::buildHelpDialogData() {
+	return helpTabs;
+}
+
+Common::String OSystem_libretro::getSaveDir(void) {
+	return s_saveDir;
+}




More information about the Scummvm-git-logs mailing list