Added the ability to make complementary game systems customizations.

This commit is contained in:
Leon Styhre 2021-09-05 17:39:11 +02:00
parent 1ad55cdcec
commit 8a51919f31
3 changed files with 401 additions and 322 deletions

View file

@ -1320,8 +1320,13 @@ void CollectionSystemsManager::addEnabledCollectionsToDisplayedSystems(
std::vector<std::string> CollectionSystemsManager::getSystemsFromConfig() std::vector<std::string> CollectionSystemsManager::getSystemsFromConfig()
{ {
std::vector<std::string> systems; std::vector<std::string> systems;
std::string path = SystemData::getConfigPath(false); std::vector<std::string> configPaths = SystemData::getConfigPath(false);
// Here we don't honor the <loadExclusive> tag which may be present in the custom es_systems.xml
// file under ~/.emulationstation/custom_systems as we really want to include all the themes
// supported by ES-DE. Otherwise a user may accidentally create a custom collection that
// corresponds to a supported theme.
for (auto path : configPaths) {
if (!Utils::FileSystem::exists(path)) if (!Utils::FileSystem::exists(path))
return systems; return systems;
@ -1345,8 +1350,11 @@ std::vector<std::string> CollectionSystemsManager::getSystemsFromConfig()
system = system.next_sibling("system")) { system = system.next_sibling("system")) {
// Theme folder. // Theme folder.
std::string themeFolder = system.child("theme").text().get(); std::string themeFolder = system.child("theme").text().get();
if (std::find(systems.cbegin(), systems.cend(), themeFolder) == systems.cend())
systems.push_back(themeFolder); systems.push_back(themeFolder);
} }
}
std::sort(systems.begin(), systems.end()); std::sort(systems.begin(), systems.end());
return systems; return systems;
} }

View file

@ -387,9 +387,17 @@ bool SystemData::loadConfig()
LOG(LogInfo) << "Populating game systems..."; LOG(LogInfo) << "Populating game systems...";
std::string path = getConfigPath(true); std::vector<std::string> configPaths = getConfigPath(true);
const std::string rompath = FileData::getROMDirectory(); const std::string rompath = FileData::getROMDirectory();
bool onlyProcessCustomFile = false;
for (auto path : configPaths) {
// If the loadExclusive tag is present in the custom es_systems.xml file, then skip
// processing of the bundled configuration file.
if (onlyProcessCustomFile)
break;
LOG(LogInfo) << "Parsing systems configuration file \"" << path << "\"..."; LOG(LogInfo) << "Parsing systems configuration file \"" << path << "\"...";
pugi::xml_document doc; pugi::xml_document doc;
@ -404,6 +412,19 @@ bool SystemData::loadConfig()
return true; return true;
} }
pugi::xml_node loadExclusive = doc.child("loadExclusive");
if (loadExclusive) {
if (path == configPaths.front() && configPaths.size() > 1) {
LOG(LogInfo) << "Only loading custom file as the <loadExclusive> tag is present";
onlyProcessCustomFile = true;
}
else {
LOG(LogWarning) << "A <loadExclusive> tag is present in the bundled es_systems.xml "
"file, ignoring it as this is only supposed to be used for the "
"custom es_systems.xml file";
}
}
// Actually read the file. // Actually read the file.
pugi::xml_node systemList = doc.child("systemList"); pugi::xml_node systemList = doc.child("systemList");
@ -448,10 +469,12 @@ bool SystemData::loadConfig()
#endif #endif
path = Utils::String::replace(path, "//", "/"); path = Utils::String::replace(path, "//", "/");
// Check that the ROM directory for the system is valid or otherwise abort the processing. // Check that the ROM directory for the system is valid or otherwise abort the
// processing.
if (!Utils::FileSystem::exists(path)) { if (!Utils::FileSystem::exists(path)) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name
<< "\" as the defined ROM directory \"" << path << "\" does not exist"; << "\" as the defined ROM directory \"" << path
<< "\" does not exist";
continue; continue;
} }
if (!Utils::FileSystem::isDirectory(path)) { if (!Utils::FileSystem::isDirectory(path)) {
@ -465,9 +488,9 @@ bool SystemData::loadConfig()
// as that would lead to an infite loop, meaning the application would never start. // as that would lead to an infite loop, meaning the application would never start.
std::string resolvedRompath = Utils::FileSystem::getCanonicalPath(rompath); std::string resolvedRompath = Utils::FileSystem::getCanonicalPath(rompath);
if (resolvedRompath.find(Utils::FileSystem::getCanonicalPath(path)) == 0) { if (resolvedRompath.find(Utils::FileSystem::getCanonicalPath(path)) == 0) {
LOG(LogWarning) << "Skipping system \"" << name LOG(LogWarning)
<< "\" as the defined ROM directory \"" << path << "Skipping system \"" << name << "\" as the defined ROM directory \""
<< "\" is an infinitely recursive symlink"; << path << "\" is an infinitely recursive symlink";
continue; continue;
} }
} }
@ -501,7 +524,8 @@ bool SystemData::loadConfig()
} }
else if (!commands.empty() && commands.back().second == "") { else if (!commands.empty() && commands.back().second == "") {
// There are more than one command tags and the first tag did not have a label. // 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 " LOG(LogError)
<< "Missing mandatory label attribute for alternative emulator "
"entry, only the first command tag will be processed for system \"" "entry, only the first command tag will be processed for system \""
<< name << "\""; << name << "\"";
break; break;
@ -582,7 +606,8 @@ bool SystemData::loadConfig()
// If the option to show hidden games has been disabled, then check whether all // If the option to show hidden games has been disabled, then check whether all
// games for the system are hidden. That will flag the system as empty. // games for the system are hidden. That will flag the system as empty.
if (!Settings::getInstance()->getBool("ShowHiddenGames")) { if (!Settings::getInstance()->getBool("ShowHiddenGames")) {
std::vector<FileData*> recursiveGames = newSys->getRootFolder()->getChildrenRecursive(); std::vector<FileData*> recursiveGames =
newSys->getRootFolder()->getChildrenRecursive();
onlyHidden = true; onlyHidden = true;
for (auto it = recursiveGames.cbegin(); it != recursiveGames.cend(); it++) { for (auto it = recursiveGames.cbegin(); it != recursiveGames.cend(); it++) {
if ((*it)->getType() != FOLDER) { if ((*it)->getType() != FOLDER) {
@ -602,6 +627,7 @@ bool SystemData::loadConfig()
sSystemVector.push_back(newSys); sSystemVector.push_back(newSys);
} }
} }
}
// Sort systems by their full names. // Sort systems by their full names.
std::sort(std::begin(sSystemVector), std::end(sSystemVector), std::sort(std::begin(sSystemVector), std::end(sSystemVector),
@ -634,8 +660,10 @@ void SystemData::deleteSystems()
sSystemVector.clear(); sSystemVector.clear();
} }
std::string SystemData::getConfigPath(bool legacyWarning) std::vector<std::string> SystemData::getConfigPath(bool legacyWarning)
{ {
std::vector<std::string> paths;
if (legacyWarning) { if (legacyWarning) {
std::string legacyConfigFile = std::string legacyConfigFile =
Utils::FileSystem::getHomePath() + "/.emulationstation/es_systems.cfg"; Utils::FileSystem::getHomePath() + "/.emulationstation/es_systems.cfg";
@ -662,7 +690,7 @@ std::string SystemData::getConfigPath(bool legacyWarning)
if (Utils::FileSystem::exists(path)) { if (Utils::FileSystem::exists(path)) {
LOG(LogInfo) << "Found custom systems configuration file"; LOG(LogInfo) << "Found custom systems configuration file";
return path; paths.push_back(path);
} }
#if defined(_WIN64) #if defined(_WIN64)
@ -674,18 +702,16 @@ std::string SystemData::getConfigPath(bool legacyWarning)
path = ResourceManager::getInstance()->getResourcePath(":/systems/unix/es_systems.xml", true); path = ResourceManager::getInstance()->getResourcePath(":/systems/unix/es_systems.xml", true);
#endif #endif
return path; paths.push_back(path);
return paths;
} }
bool SystemData::createSystemDirectories() bool SystemData::createSystemDirectories()
{ {
std::string path = getConfigPath(false); std::vector<std::string> configPaths = getConfigPath(true);
const std::string rompath = FileData::getROMDirectory(); const std::string rompath = FileData::getROMDirectory();
if (!Utils::FileSystem::exists(path)) { bool onlyProcessCustomFile = false;
LOG(LogInfo) << "Systems configuration file does not exist, aborting";
return true;
}
LOG(LogInfo) << "Generating ROM directory structure..."; LOG(LogInfo) << "Generating ROM directory structure...";
@ -706,13 +732,42 @@ bool SystemData::createSystemDirectories()
LOG(LogInfo) << "Base ROM directory \"" << rompath << "\" already exists"; LOG(LogInfo) << "Base ROM directory \"" << rompath << "\" already exists";
} }
LOG(LogInfo) << "Parsing systems configuration file \"" << path << "\"..."; if (configPaths.size() > 1) {
// If the loadExclusive tag is present in the custom es_systems.xml file, then skip
// processing of the bundled configuration file.
pugi::xml_document doc;
#if defined(_WIN64)
pugi::xml_parse_result res =
doc.load_file(Utils::String::stringToWideString(configPath).c_str());
#else
pugi::xml_parse_result res = doc.load_file(configPaths.front().c_str());
#endif
if (res) {
pugi::xml_node loadExclusive = doc.child("loadExclusive");
if (loadExclusive)
onlyProcessCustomFile = true;
}
}
// Process the custom es_systems.xml file after the bundled file, as any systems with identical
// <path> tags will be overwritten by the last occurrence.
std::reverse(configPaths.begin(), configPaths.end());
std::vector<std::pair<std::string, std::string>> systemsVector;
for (auto configPath : configPaths) {
// If the loadExclusive tag is present.
if (onlyProcessCustomFile && configPath == configPaths.front())
continue;
LOG(LogInfo) << "Parsing systems configuration file \"" << configPath << "\"...";
pugi::xml_document doc; pugi::xml_document doc;
#if defined(_WIN64) #if defined(_WIN64)
pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str()); pugi::xml_parse_result res =
doc.load_file(Utils::String::stringToWideString(configPath).c_str());
#else #else
pugi::xml_parse_result res = doc.load_file(path.c_str()); pugi::xml_parse_result res = doc.load_file(configPath.c_str());
#endif #endif
if (!res) { if (!res) {
@ -729,8 +784,6 @@ bool SystemData::createSystemDirectories()
return true; return true;
} }
std::vector<std::string> systemsVector;
for (pugi::xml_node system = systemList.child("system"); system; for (pugi::xml_node system = systemList.child("system"); system;
system = system.next_sibling("system")) { system = system.next_sibling("system")) {
std::string systemDir; std::string systemDir;
@ -799,7 +852,8 @@ bool SystemData::createSystemDirectories()
#if defined(_WIN64) #if defined(_WIN64)
systemInfoFile.open( systemInfoFile.open(
Utils::String::stringToWideString(rompath + systemDir + systemInfoFileName).c_str()); Utils::String::stringToWideString(rompath + systemDir + systemInfoFileName)
.c_str());
#else #else
systemInfoFile.open(rompath + systemDir + systemInfoFileName); systemInfoFile.open(rompath + systemDir + systemInfoFileName);
#endif #endif
@ -813,6 +867,9 @@ bool SystemData::createSystemDirectories()
} }
systemInfoFile << "System name:" << std::endl; systemInfoFile << "System name:" << std::endl;
if (configPaths.size() != 1 && configPath == configPaths.back())
systemInfoFile << name << " (custom system)" << std::endl << std::endl;
else
systemInfoFile << name << std::endl << std::endl; systemInfoFile << name << std::endl << std::endl;
systemInfoFile << "Full system name:" << std::endl; systemInfoFile << "Full system name:" << std::endl;
systemInfoFile << fullname << std::endl << std::endl; systemInfoFile << fullname << std::endl << std::endl;
@ -835,7 +892,18 @@ bool SystemData::createSystemDirectories()
systemInfoFile << themeFolder << std::endl; systemInfoFile << themeFolder << std::endl;
systemInfoFile.close(); systemInfoFile.close();
systemsVector.push_back(systemDir + ": " + fullname); auto systemIter = std::find_if(systemsVector.cbegin(), systemsVector.cend(),
[systemDir](std::pair<std::string, std::string> system) {
return system.first == systemDir;
});
if (systemIter != systemsVector.cend())
systemsVector.erase(systemIter);
if (configPaths.size() != 1 && configPath == configPaths.back())
systemsVector.push_back(std::make_pair(systemDir + " (custom system)", fullname));
else
systemsVector.push_back(std::make_pair(systemDir, fullname));
if (replaceInfoFile) { if (replaceInfoFile) {
LOG(LogInfo) << "Replaced existing system information file \"" LOG(LogInfo) << "Replaced existing system information file \""
@ -846,6 +914,7 @@ bool SystemData::createSystemDirectories()
<< rompath + systemDir + systemInfoFileName << "\""; << rompath + systemDir + systemInfoFileName << "\"";
} }
} }
}
// Also generate a systems.txt file directly in the ROM directory root that contains the // Also generate a systems.txt file directly in the ROM directory root that contains the
// mappings between the system directory names and the full system names. This makes it // mappings between the system directory names and the full system names. This makes it
@ -870,8 +939,10 @@ bool SystemData::createSystemDirectories()
systemsFileSuccess = false; systemsFileSuccess = false;
} }
else { else {
for (std::string systemEntry : systemsVector) { std::sort(systemsVector.begin(), systemsVector.end());
systemsFile << systemEntry << std::endl; for (auto systemEntry : systemsVector) {
systemsFile << systemEntry.first.append(": ").append(systemEntry.second)
<< std::endl;
} }
systemsFile.close(); systemsFile.close();
} }

View file

@ -102,9 +102,9 @@ public:
std::string getLaunchCommandFromLabel(const std::string& label); std::string getLaunchCommandFromLabel(const std::string& label);
static void deleteSystems(); static void deleteSystems();
// Loads the systems configuration file at getConfigPath() and creates the systems. // Loads the systems configuration file(s) at getConfigPath() and creates the systems.
static bool loadConfig(); static bool loadConfig();
static std::string getConfigPath(bool legacyWarning); static std::vector<std::string> getConfigPath(bool legacyWarning);
// Generates the game system directories and information files based on es_systems.xml. // Generates the game system directories and information files based on es_systems.xml.
static bool createSystemDirectories(); static bool createSystemDirectories();