Added support for defining and choosing between alternative emulators.

This commit is contained in:
Leon Styhre 2021-08-22 15:26:38 +02:00
parent 7b111807ae
commit 5381f38231
15 changed files with 504 additions and 20 deletions

View file

@ -16,6 +16,7 @@ set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h
# GUIs
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.h
@ -66,6 +67,7 @@ set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp
# GUIs
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.cpp

View file

@ -73,7 +73,8 @@ CollectionSystemsManager::CollectionSystemsManager(Window* window)
mCollectionEnvData->mStartPath = "";
std::vector<std::string> exts;
mCollectionEnvData->mSearchExtensions = exts;
mCollectionEnvData->mLaunchCommand = "";
std::vector<std::pair<std::string, std::string>> commands;
mCollectionEnvData->mLaunchCommands = commands;
std::vector<PlatformIds::PlatformId> allPlatformIds;
allPlatformIds.push_back(PlatformIds::PLATFORM_IGNORE);
mCollectionEnvData->mPlatformIds = allPlatformIds;

View file

@ -756,7 +756,22 @@ void FileData::launchGame(Window* window)
command = metadata.get("launchcommand");
}
else {
command = mEnvData->mLaunchCommand;
std::string alternativeEmulator = getSystem()->getAlternativeEmulator();
for (auto launchCommand : mEnvData->mLaunchCommands) {
if (launchCommand.second == alternativeEmulator) {
command = launchCommand.first;
break;
}
}
if (!alternativeEmulator.empty() && command.empty()) {
LOG(LogWarning) << "The alternative emulator configured for system \""
<< getSystem()->getName()
<< "\" is invalid, falling back to the default command \""
<< getSystem()->getSystemEnvData()->mLaunchCommands.front().first << "\"";
}
if (command.empty())
command = mEnvData->mLaunchCommands.front().first;
}
std::string commandRaw = command;

View file

@ -117,6 +117,30 @@ void parseGamelist(SystemData* system)
return;
}
pugi::xml_node alternativeEmulator = doc.child("alternativeEmulator");
if (alternativeEmulator) {
std::string label = alternativeEmulator.child("label").text().get();
if (label != "") {
bool validLabel = false;
for (auto command : system->getSystemEnvData()->mLaunchCommands) {
if (command.second == label)
validLabel = true;
}
if (validLabel) {
system->setAlternativeEmulator(label);
LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName()
<< "\" has a valid alternativeEmulator entry: \"" << label << "\"";
}
else {
system->setAlternativeEmulator("<INVALID>");
LOG(LogWarning) << "System \"" << system->getName()
<< "\" has an invalid alternativeEmulator entry that does "
"not match any command tag in es_systems.xml: \""
<< label << "\"";
}
}
}
std::string relativeTo = system->getStartPath();
bool showHiddenFiles = Settings::getInstance()->getBool("ShowHiddenFiles");
@ -221,7 +245,7 @@ void addFileDataNode(pugi::xml_node& parent,
}
}
void updateGamelist(SystemData* system)
void updateGamelist(SystemData* system, bool updateAlternativeEmulator)
{
// We do this by reading the XML again, adding changes and then writing them back,
// because there might be information missing in our systemdata which we would otherwise
@ -256,8 +280,39 @@ void updateGamelist(SystemData* system)
LOG(LogError) << "Couldn't find <gameList> node in gamelist \"" << xmlReadPath << "\"";
return;
}
if (updateAlternativeEmulator) {
pugi::xml_node alternativeEmulator = doc.child("alternativeEmulator");
if (system->getAlternativeEmulator() != "") {
if (!alternativeEmulator) {
doc.prepend_child("alternativeEmulator");
alternativeEmulator = doc.child("alternativeEmulator");
}
pugi::xml_node label = alternativeEmulator.child("label");
if (label && system->getAlternativeEmulator() !=
alternativeEmulator.child("label").text().get()) {
alternativeEmulator.remove_child(label);
alternativeEmulator.prepend_child("label").text().set(
system->getAlternativeEmulator().c_str());
}
else if (!label) {
alternativeEmulator.prepend_child("label").text().set(
system->getAlternativeEmulator().c_str());
}
}
else if (alternativeEmulator) {
doc.remove_child("alternativeEmulator");
}
}
}
else {
if (updateAlternativeEmulator && system->getAlternativeEmulator() != "") {
pugi::xml_node alternativeEmulator = doc.prepend_child("alternativeEmulator");
alternativeEmulator.prepend_child("label").text().set(
system->getAlternativeEmulator().c_str());
}
// Set up an empty gamelist to append to.
root = doc.append_child("gameList");
}
@ -312,15 +367,31 @@ void updateGamelist(SystemData* system)
}
// Now write the file.
if (numUpdated > 0) {
if (numUpdated > 0 || updateAlternativeEmulator) {
// Make sure the folders leading up to this path exist (or the write will fail).
std::string xmlWritePath(system->getGamelistPath(true));
Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(xmlWritePath));
LOG(LogDebug) << "Gamelist::updateGamelist(): Added/updated " << numUpdated
<< (numUpdated == 1 ? " entity in \"" : " entities in \"") << xmlReadPath
<< "\"";
if (updateAlternativeEmulator) {
if (system->getAlternativeEmulator() == "") {
LOG(LogDebug) << "Gamelist::updateGamelist(): Removed the "
"alternativeEmulator tag for system \""
<< system->getName() << "\" as the default emulator \""
<< system->getSystemEnvData()->mLaunchCommands.front().second
<< "\" was selected";
}
else {
LOG(LogDebug) << "Gamelist::updateGamelist(): "
"Added/updated the alternativeEmulator tag for system \""
<< system->getName() << "\" to \""
<< system->getAlternativeEmulator() << "\"";
}
}
if (numUpdated > 0) {
LOG(LogDebug) << "Gamelist::updateGamelist(): Added/updated " << numUpdated
<< (numUpdated == 1 ? " entity in \"" : " entities in \"")
<< xmlWritePath << "\"";
}
#if defined(_WIN64)
if (!doc.save_file(Utils::String::stringToWideString(xmlWritePath).c_str())) {
#else

View file

@ -15,6 +15,6 @@ class SystemData;
void parseGamelist(SystemData* system);
// Writes currently loaded metadata for a SystemData to gamelist.xml.
void updateGamelist(SystemData* system);
void updateGamelist(SystemData* system, bool updateAlternativeEmulator = false);
#endif // ES_APP_GAME_LIST_H

View file

@ -417,13 +417,27 @@ bool SystemData::loadConfig()
std::string name;
std::string fullname;
std::string path;
std::string cmd;
std::string themeFolder;
name = system.child("name").text().get();
fullname = system.child("fullname").text().get();
path = system.child("path").text().get();
auto nameFindFunc = [&] {
for (auto system : sSystemVector) {
if (system->mName == name) {
LOG(LogWarning) << "A system with the name \"" << name
<< "\" has already been loaded, skipping duplicate entry";
return true;
}
}
return false;
};
// If the name is matching a system that has already been loaded, then skip the entry.
if (nameFindFunc())
continue;
// If there is a %ROMPATH% variable set for the system, expand it. By doing this
// it's possible to use either absolute ROM paths in es_systems.xml or to utilize
// the ROM path configured as ROMDirectory in es_settings.xml. If it's set to ""
@ -461,7 +475,41 @@ bool SystemData::loadConfig()
// Convert extensions list from a string into a vector of strings.
std::vector<std::string> extensions = readList(system.child("extension").text().get());
cmd = system.child("command").text().get();
// Load all launch command tags for the system and if there are multiple tags, then
// the label attribute needs to be set on all entries as it's a requirement for the
// alternative emulator logic.
std::vector<std::pair<std::string, std::string>> commands;
for (pugi::xml_node entry = system.child("command"); entry;
entry = entry.next_sibling("command")) {
if (!entry.attribute("label")) {
if (commands.size() == 1) {
// The first command tag had a label but the second one doesn't.
LOG(LogError)
<< "Missing mandatory label attribute for alternative emulator "
"entry, only the default command tag will be processed for system \""
<< name << "\"";
break;
}
else if (commands.size() > 1) {
// At least two command tags had a label but this one doesn't.
LOG(LogError)
<< "Missing mandatory label attribute for alternative emulator "
"entry, no additional command tags will be processed for system \""
<< name << "\"";
break;
}
}
else if (!commands.empty() && commands.back().second == "") {
// There are more than one command tags and the first tag did not have a label.
LOG(LogError)
<< "Missing mandatory label attribute for alternative emulator "
"entry, only the default command tag will be processed for system \""
<< name << "\"";
break;
}
commands.push_back(
std::make_pair(entry.text().get(), entry.attribute("label").as_string()));
}
// Platform ID list
const std::string platformList =
@ -504,7 +552,7 @@ bool SystemData::loadConfig()
<< "A system in the es_systems.xml file has no name defined, skipping entry";
continue;
}
else if (fullname.empty() || path.empty() || extensions.empty() || cmd.empty()) {
else if (fullname.empty() || path.empty() || extensions.empty() || commands.empty()) {
LOG(LogError) << "System \"" << name
<< "\" is missing the fullname, path, "
"extension, or command tag, skipping entry";
@ -526,7 +574,7 @@ bool SystemData::loadConfig()
SystemEnvironmentData* envData = new SystemEnvironmentData;
envData->mStartPath = path;
envData->mSearchExtensions = extensions;
envData->mLaunchCommand = cmd;
envData->mLaunchCommands = commands;
envData->mPlatformIds = platformIds;
SystemData* newSys = new SystemData(name, fullname, envData, themeFolder);
@ -679,7 +727,7 @@ bool SystemData::createSystemDirectories()
std::string fullname;
std::string path;
std::string extensions;
std::string command;
std::vector<std::string> commands;
std::string platform;
std::string themeFolder;
const std::string systemInfoFileName = "/systeminfo.txt";
@ -690,7 +738,10 @@ bool SystemData::createSystemDirectories()
fullname = system.child("fullname").text().get();
path = system.child("path").text().get();
extensions = system.child("extension").text().get();
command = system.child("command").text().get();
for (pugi::xml_node entry = system.child("command"); entry;
entry = entry.next_sibling("command")) {
commands.push_back(entry.text().get());
}
platform = Utils::String::toLower(system.child("platform").text().get());
themeFolder = system.child("theme").text().as_string(name.c_str());
@ -757,7 +808,16 @@ bool SystemData::createSystemDirectories()
systemInfoFile << "Supported file extensions:" << std::endl;
systemInfoFile << extensions << std::endl << std::endl;
systemInfoFile << "Launch command:" << std::endl;
systemInfoFile << command << std::endl << std::endl;
systemInfoFile << commands.front() << std::endl << std::endl;
// Alternative emulator configuration entries.
if (commands.size() > 1) {
systemInfoFile << (commands.size() == 2 ? "Alternative launch command:" :
"Alternative launch commands:")
<< std::endl;
for (auto it = commands.cbegin() + 1; it != commands.cend(); it++)
systemInfoFile << (*it) << std::endl;
systemInfoFile << std::endl;
}
systemInfoFile << "Platform (for scraping):" << std::endl;
systemInfoFile << platform << std::endl << std::endl;
systemInfoFile << "Theme folder:" << std::endl;

View file

@ -27,7 +27,7 @@ class ThemeData;
struct SystemEnvironmentData {
std::string mStartPath;
std::vector<std::string> mSearchExtensions;
std::string mLaunchCommand;
std::vector<std::pair<std::string, std::string>> mLaunchCommands;
std::vector<PlatformIds::PlatformId> mPlatformIds;
};
@ -97,6 +97,9 @@ public:
bool getScrapeFlag() { return mScrapeFlag; }
void setScrapeFlag(bool scrapeflag) { mScrapeFlag = scrapeflag; }
std::string getAlternativeEmulator() { return mAlternativeEmulator; }
void setAlternativeEmulator(const std::string& command) { mAlternativeEmulator = command; }
static void deleteSystems();
// Loads the systems configuration file at getConfigPath() and creates the systems.
static bool loadConfig();
@ -153,6 +156,7 @@ private:
std::string mName;
std::string mFullName;
SystemEnvironmentData* mEnvData;
std::string mAlternativeEmulator;
std::string mThemeFolder;
std::shared_ptr<ThemeData> mTheme;

View file

@ -0,0 +1,231 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// GuiAlternativeEmulators.cpp
//
// User interface to select between alternative emulators per system
// based on configuration entries in es_systems.xml.
//
#include "guis/GuiAlternativeEmulators.h"
#include "Gamelist.h"
#include "SystemData.h"
#include "views/ViewController.h"
GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window)
: GuiComponent{window}
, mMenu{window, "ALTERNATIVE EMULATORS"}
, mHasSystems(false)
{
addChild(&mMenu);
mMenu.addButton("BACK", "back", [this] { delete this; });
// Horizontal sizes for the system and label entries.
float systemSizeX = mMenu.getSize().x / 3.27f;
float labelSizeX = mMenu.getSize().x / 1.53;
for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
it != SystemData::sSystemVector.cend(); it++) {
// Only include systems that have at least two command entries, unless the system
// has an invalid entry.
if ((*it)->getAlternativeEmulator() != "<INVALID>" &&
(*it)->getSystemEnvData()->mLaunchCommands.size() < 2)
continue;
ComponentListRow row;
// This transparent bracket is only added to generate a left margin.
auto bracket = std::make_shared<ImageComponent>(mWindow);
bracket->setImage(":/graphics/arrow.svg");
bracket->setOpacity(0);
bracket->setSize(bracket->getSize() / 3.0f);
row.addElement(bracket, false);
std::string name = (*it)->getName();
std::shared_ptr<TextComponent> systemText =
std::make_shared<TextComponent>(mWindow, name, Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
systemText->setSize(systemSizeX, systemText->getSize().y);
row.addElement(systemText, false);
std::string configuredLabel = (*it)->getAlternativeEmulator();
std::string label;
if (configuredLabel == "") {
label = (*it)->getSystemEnvData()->mLaunchCommands.front().second;
}
else {
for (auto command : (*it)->getSystemEnvData()->mLaunchCommands) {
if (command.second == configuredLabel) {
label = command.second;
break;
}
}
}
bool invalidEntry = false;
if (label.empty()) {
label = "<INVALID ENTRY>";
invalidEntry = true;
}
std::shared_ptr<TextComponent> labelText;
if (label == (*it)->getSystemEnvData()->mLaunchCommands.front().second) {
labelText = std::make_shared<TextComponent>(
mWindow, label, Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT), 0x777777FF,
ALIGN_RIGHT);
}
else {
// Mark any non-default value with bold and add a gear symbol as well.
labelText = std::make_shared<TextComponent>(
mWindow, label + (!invalidEntry ? " " + ViewController::GEAR_CHAR : ""),
Font::get(FONT_SIZE_MEDIUM, FONT_PATH_BOLD), 0x777777FF, ALIGN_RIGHT);
}
// Mark invalid entries with red color.
if (invalidEntry)
labelText->setColor(TEXTCOLOR_SCRAPERMARKED);
mCommandRows[name] = labelText;
labelText->setSize(labelSizeX, labelText->getSize().y);
row.addElement(labelText, false);
row.makeAcceptInputHandler([this, it] { selectorWindow(*it); });
mMenu.addRow(row);
mHasSystems = true;
}
// Add a dummy row if no enabled systems have any alternative emulators defined in
// es_systems.xml.
if (!mHasSystems) {
ComponentListRow row;
std::shared_ptr<TextComponent> systemText =
std::make_shared<TextComponent>(mWindow, "<NO ALTERNATIVE EMULATORS DEFINED>",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER);
row.addElement(systemText, true);
mMenu.addRow(row);
}
float width =
static_cast<float>(std::min(static_cast<int>(Renderer::getScreenHeight() * 1.05f),
static_cast<int>(Renderer::getScreenWidth() * 0.90f)));
setSize(mMenu.getSize());
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, Renderer::getScreenHeight() * 0.13f);
mMenu.setSize(width, Renderer::getScreenHeight() * 0.76f);
}
void GuiAlternativeEmulators::updateMenu(const std::string& systemName,
const std::string& label,
bool defaultEmulator)
{
// If another label was selected, then update the menu accordingly.
if (defaultEmulator) {
mCommandRows[systemName].get()->setFont(Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT));
mCommandRows[systemName].get()->setValue(label);
}
else {
// Mark any non-default value with bold and add a gear symbol as well.
mCommandRows[systemName].get()->setFont(Font::get(FONT_SIZE_MEDIUM, FONT_PATH_BOLD));
mCommandRows[systemName].get()->setValue(label + " " + ViewController::GEAR_CHAR);
}
mCommandRows[systemName].get()->setColor(DEFAULT_TEXTCOLOR);
}
void GuiAlternativeEmulators::selectorWindow(SystemData* system)
{
auto s = new GuiSettings(mWindow, system->getFullName());
std::string selectedLabel = system->getAlternativeEmulator();
std::string label;
for (auto entry : system->getSystemEnvData()->mLaunchCommands) {
ComponentListRow row;
if (entry.second == "")
label = "<REMOVE INVALID ENTRY>";
else
label = entry.second;
std::shared_ptr<TextComponent> labelText = std::make_shared<TextComponent>(
mWindow, label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER);
row.addElement(labelText, true);
row.makeAcceptInputHandler([this, s, system, labelText, entry, selectedLabel] {
if (entry.second != selectedLabel) {
if (entry.second == system->getSystemEnvData()->mLaunchCommands.front().second)
system->setAlternativeEmulator("");
else
system->setAlternativeEmulator(entry.second);
updateGamelist(system, true);
updateMenu(
system->getName(), labelText->getValue(),
(entry.second == system->getSystemEnvData()->mLaunchCommands.front().second));
}
delete s;
});
// This transparent bracket is only added to generate the correct help prompts.
auto bracket = std::make_shared<ImageComponent>(mWindow);
bracket->setImage(":/graphics/arrow.svg");
bracket->setOpacity(0);
bracket->setSize(bracket->getSize() / 3.0f);
row.addElement(bracket, false);
// Select the row that corresponds to the selected label.
if (selectedLabel == label)
s->addRow(row, true);
else
s->addRow(row, false);
}
// Adjust the width depending on the aspect ratio of the screen, to make the screen look
// somewhat coherent regardless of screen type. The 1.778 aspect ratio value is the 16:9
// reference.
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float maxWidthModifier = glm::clamp(0.70f * aspectValue, 0.50f, 0.92f);
float maxWidth = static_cast<float>(Renderer::getScreenWidth()) * maxWidthModifier;
s->setMenuSize(glm::vec2{maxWidth, s->getMenuSize().y});
auto menuSize = s->getMenuSize();
auto menuPos = s->getMenuPosition();
s->setMenuPosition(glm::vec3{(s->getSize().x - menuSize.x) / 2.0f,
(s->getSize().y - menuSize.y) / 3.0f, menuPos.z});
mWindow->pushGui(s);
}
bool GuiAlternativeEmulators::input(InputConfig* config, Input input)
{
if (input.value != 0 && (config->isMappedTo("b", input) || config->isMappedTo("back", input))) {
delete this;
return true;
}
return mMenu.input(config, input);
}
std::vector<HelpPrompt> GuiAlternativeEmulators::getHelpPrompts()
{
std::vector<HelpPrompt> prompts = mMenu.getHelpPrompts();
prompts.push_back(HelpPrompt("b", "back"));
if (mHasSystems)
prompts.push_back(HelpPrompt("a", "select"));
return prompts;
}
HelpStyle GuiAlternativeEmulators::getHelpStyle()
{
HelpStyle style = HelpStyle();
style.applyTheme(ViewController::get()->getState().getSystem()->getTheme(), "system");
return style;
}

View file

@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// GuiAlternativeEmulators.h
//
// User interface to select between alternative emulators per system
// based on configuration entries in es_systems.xml.
//
#ifndef ES_APP_GUIS_GUI_ALTERNATIVE_EMULATORS_H
#define ES_APP_GUIS_GUI_ALTERNATIVE_EMULATORS_H
#include "GuiComponent.h"
#include "guis/GuiSettings.h"
template <typename T> class OptionListComponent;
class GuiAlternativeEmulators : public GuiComponent
{
public:
GuiAlternativeEmulators(Window* window);
private:
void updateMenu(const std::string& systemName, const std::string& label, bool defaultEmulator);
void selectorWindow(SystemData* system);
virtual bool input(InputConfig* config, Input input) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override;
HelpStyle getHelpStyle() override;
MenuComponent mMenu;
bool mHasSystems;
std::map<std::string, std::shared_ptr<TextComponent>> mCommandRows;
std::shared_ptr<OptionListComponent<std::string>> mCommandSelection;
};
#endif // ES_APP_GUIS_GUI_ALTERNATIVE_EMULATORS_H

View file

@ -20,6 +20,7 @@
#include "components/OptionListComponent.h"
#include "components/SliderComponent.h"
#include "components/SwitchComponent.h"
#include "guis/GuiAlternativeEmulators.h"
#include "guis/GuiCollectionSystemsOptions.h"
#include "guis/GuiComplexTextEditPopup.h"
#include "guis/GuiDetectDevice.h"
@ -772,6 +773,18 @@ void GuiMenu::openOtherOptions()
{
auto s = new GuiSettings(mWindow, "OTHER SETTINGS");
// Alternative emulators GUI.
ComponentListRow alternativeEmulatorsRow;
alternativeEmulatorsRow.elements.clear();
alternativeEmulatorsRow.addElement(
std::make_shared<TextComponent>(mWindow, "ALTERNATIVE EMULATORS",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
alternativeEmulatorsRow.addElement(makeArrow(mWindow), false);
alternativeEmulatorsRow.makeAcceptInputHandler(
std::bind([this] { mWindow->pushGui(new GuiAlternativeEmulators(mWindow)); }));
s->addRow(alternativeEmulatorsRow);
// Game media directory.
ComponentListRow rowMediaDir;
auto media_directory = std::make_shared<TextComponent>(mWindow, "GAME MEDIA DIRECTORY",

View file

@ -198,8 +198,28 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window,
};
std::string staticTextString = "Default value from es_systems.xml:";
std::string defaultLaunchCommand =
scraperParams.system->getSystemEnvData()->mLaunchCommand;
std::string defaultLaunchCommand;
std::string alternativeEmulator = scraperParams.system->getAlternativeEmulator();
for (auto launchCommand :
scraperParams.system->getSystemEnvData()->mLaunchCommands) {
if (launchCommand.second == alternativeEmulator) {
defaultLaunchCommand = launchCommand.first;
break;
}
}
if (!alternativeEmulator.empty() && defaultLaunchCommand.empty()) {
LOG(LogWarning)
<< "The alternative emulator defined for system \""
<< scraperParams.system->getName()
<< "\" is invalid, falling back to the default command \""
<< scraperParams.system->getSystemEnvData()->mLaunchCommands.front().first
<< "\"";
}
if (defaultLaunchCommand.empty())
defaultLaunchCommand =
scraperParams.system->getSystemEnvData()->mLaunchCommands.front().first;
row.makeAcceptInputHandler([this, title, staticTextString, defaultLaunchCommand, ed,
updateVal, multiLine] {

View file

@ -613,6 +613,16 @@ int main(int argc, char* argv[])
}
}
// Check if any of the enabled systems has an invalid alternative emulator entry,
// which means that a label is present in the gamelist.xml file which is not matching
// any command tag in es_systems.xml.
for (auto system : SystemData::sSystemVector) {
if (system->getAlternativeEmulator() == "<INVALID>") {
ViewController::get()->invalidAlternativeEmulatorDialog();
break;
}
}
// Don't generate controller events while we're loading.
SDL_GameControllerEventState(SDL_DISABLE);

View file

@ -41,12 +41,14 @@ const std::string ViewController::FOLDER_CHAR = Utils::String::wideStringToStrin
const std::string ViewController::TICKMARK_CHAR = Utils::String::wideStringToString(L"\uF14A");
const std::string ViewController::CONTROLLER_CHAR = Utils::String::wideStringToString(L"\uF11b");
const std::string ViewController::FILTER_CHAR = Utils::String::wideStringToString(L"\uF0b0");
const std::string ViewController::GEAR_CHAR = Utils::String::wideStringToString(L"\uF013");
#else
const std::string ViewController::FAVORITE_CHAR = "\uF005";
const std::string ViewController::FOLDER_CHAR = "\uF07C";
const std::string ViewController::TICKMARK_CHAR = "\uF14A";
const std::string ViewController::CONTROLLER_CHAR = "\uF11b";
const std::string ViewController::FILTER_CHAR = "\uF0b0";
const std::string ViewController::GEAR_CHAR = "\uF013";
#endif
ViewController* ViewController::get()
@ -193,6 +195,17 @@ void ViewController::noGamesDialog()
mWindow->pushGui(mNoGamesMessageBox);
}
void ViewController::invalidAlternativeEmulatorDialog()
{
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
"AT LEAST ONE OF YOUR SYSTEMS HAS AN\n"
"INVALID ALTERNATIVE EMULATOR CONFIGURED\n"
"WITH NO MATCHING ENTRY IN THE SYSTEMS\n"
"CONFIGURATION FILE, PLEASE REVIEW YOUR\n"
"SETUP USING THE 'ALTERNATIVE EMULATORS'\n"
"ENTRY UNDER THE 'OTHER SETTINGS' MENU"));
}
void ViewController::goToStart()
{
// If the system view does not exist, then create it. We do this here as it would

View file

@ -38,6 +38,7 @@ public:
// These functions are called from main().
void invalidSystemsFileDialog();
void noGamesDialog();
void invalidAlternativeEmulatorDialog();
// Try to completely populate the GameListView map.
// Caches things so there's no pauses during transitions.
@ -127,6 +128,7 @@ public:
static const std::string TICKMARK_CHAR;
static const std::string CONTROLLER_CHAR;
static const std::string FILTER_CHAR;
static const std::string GEAR_CHAR;
private:
ViewController(Window* window);

View file

@ -223,7 +223,11 @@ private:
HelpStyle mHelpStyle;
void open() { mWindow->pushGui(new OptionListPopup(mWindow, getHelpStyle(), this, mName)); }
void open()
{
// Open the list popup.
mWindow->pushGui(new OptionListPopup(mWindow, getHelpStyle(), this, mName));
}
void onSelectedChanged()
{