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,33 +1320,41 @@ 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);
if (!Utils::FileSystem::exists(path)) // Here we don't honor the <loadExclusive> tag which may be present in the custom es_systems.xml
return systems; // 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))
return systems;
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(path).c_str());
#else #else
pugi::xml_parse_result res = doc.load_file(path.c_str()); pugi::xml_parse_result res = doc.load_file(path.c_str());
#endif #endif
if (!res) if (!res)
return systems; return systems;
// Actually read the file. // Actually read the file.
pugi::xml_node systemList = doc.child("systemList"); pugi::xml_node systemList = doc.child("systemList");
if (!systemList) if (!systemList)
return systems; return systems;
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")) {
// Theme folder. // Theme folder.
std::string themeFolder = system.child("theme").text().get(); std::string themeFolder = system.child("theme").text().get();
systems.push_back(themeFolder); if (std::find(systems.cbegin(), systems.cend(), themeFolder) == systems.cend())
systems.push_back(themeFolder);
}
} }
std::sort(systems.begin(), systems.end()); std::sort(systems.begin(), systems.end());
return systems; return systems;
} }

View file

@ -387,219 +387,245 @@ 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();
LOG(LogInfo) << "Parsing systems configuration file \"" << path << "\"..."; bool onlyProcessCustomFile = false;
pugi::xml_document doc; 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 << "\"...";
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(path).c_str());
#else #else
pugi::xml_parse_result res = doc.load_file(path.c_str()); pugi::xml_parse_result res = doc.load_file(path.c_str());
#endif #endif
if (!res) { if (!res) {
LOG(LogError) << "Couldn't parse es_systems.xml: " << res.description(); LOG(LogError) << "Couldn't parse es_systems.xml: " << res.description();
return true; return true;
} }
// Actually read the file. pugi::xml_node loadExclusive = doc.child("loadExclusive");
pugi::xml_node systemList = doc.child("systemList"); if (loadExclusive) {
if (path == configPaths.front() && configPaths.size() > 1) {
if (!systemList) { LOG(LogInfo) << "Only loading custom file as the <loadExclusive> tag is present";
LOG(LogError) << "es_systems.xml is missing the <systemList> tag"; onlyProcessCustomFile = true;
return true;
}
for (pugi::xml_node system = systemList.child("system"); system;
system = system.next_sibling("system")) {
std::string name;
std::string fullname;
std::string path;
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; 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";
}
}
// If the name is matching a system that has already been loaded, then skip the entry. // Actually read the file.
if (nameFindFunc()) pugi::xml_node systemList = doc.child("systemList");
continue;
// If there is a %ROMPATH% variable set for the system, expand it. By doing this if (!systemList) {
// it's possible to use either absolute ROM paths in es_systems.xml or to utilize LOG(LogError) << "es_systems.xml is missing the <systemList> tag";
// the ROM path configured as ROMDirectory in es_settings.xml. If it's set to "" return true;
// in this configuration file, the default hardcoded path $HOME/ROMs/ will be used. }
path = Utils::String::replace(path, "%ROMPATH%", rompath);
for (pugi::xml_node system = systemList.child("system"); system;
system = system.next_sibling("system")) {
std::string name;
std::string fullname;
std::string path;
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 ""
// in this configuration file, the default hardcoded path $HOME/ROMs/ will be used.
path = Utils::String::replace(path, "%ROMPATH%", rompath);
#if defined(_WIN64) #if defined(_WIN64)
path = Utils::String::replace(path, "\\", "/"); path = Utils::String::replace(path, "\\", "/");
#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
if (!Utils::FileSystem::exists(path)) { // processing.
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name if (!Utils::FileSystem::exists(path)) {
<< "\" as the defined ROM directory \"" << path << "\" does not exist"; LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name
continue; << "\" as the defined ROM directory \"" << path
} << "\" does not exist";
if (!Utils::FileSystem::isDirectory(path)) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name
<< "\" as the defined ROM directory \"" << path
<< "\" is not actually a directory";
continue;
}
if (Utils::FileSystem::isSymlink(path)) {
// Make sure that the symlink is not pointing to somewhere higher in the hierarchy
// as that would lead to an infite loop, meaning the application would never start.
std::string resolvedRompath = Utils::FileSystem::getCanonicalPath(rompath);
if (resolvedRompath.find(Utils::FileSystem::getCanonicalPath(path)) == 0) {
LOG(LogWarning) << "Skipping system \"" << name
<< "\" as the defined ROM directory \"" << path
<< "\" is an infinitely recursive symlink";
continue; continue;
} }
} if (!Utils::FileSystem::isDirectory(path)) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name
<< "\" as the defined ROM directory \"" << path
<< "\" is not actually a directory";
continue;
}
if (Utils::FileSystem::isSymlink(path)) {
// Make sure that the symlink is not pointing to somewhere higher in the hierarchy
// as that would lead to an infite loop, meaning the application would never start.
std::string resolvedRompath = Utils::FileSystem::getCanonicalPath(rompath);
if (resolvedRompath.find(Utils::FileSystem::getCanonicalPath(path)) == 0) {
LOG(LogWarning)
<< "Skipping system \"" << name << "\" as the defined ROM directory \""
<< path << "\" is an infinitely recursive symlink";
continue;
}
}
// Convert extensions list from a string into a vector of strings. // Convert extensions list from a string into a vector of strings.
std::vector<std::string> extensions = readList(system.child("extension").text().get()); std::vector<std::string> extensions = readList(system.child("extension").text().get());
// Load all launch command tags for the system and if there are multiple tags, then // 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 // the label attribute needs to be set on all entries as it's a requirement for the
// alternative emulator logic. // alternative emulator logic.
std::vector<std::pair<std::string, std::string>> commands; std::vector<std::pair<std::string, std::string>> commands;
for (pugi::xml_node entry = system.child("command"); entry; for (pugi::xml_node entry = system.child("command"); entry;
entry = entry.next_sibling("command")) { entry = entry.next_sibling("command")) {
if (!entry.attribute("label")) { if (!entry.attribute("label")) {
if (commands.size() == 1) { if (commands.size() == 1) {
// The first command tag had a label but the second one doesn't. // 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 first 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) LOG(LogError)
<< "Missing mandatory label attribute for alternative emulator " << "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;
} }
else if (commands.size() > 1) { commands.push_back(
// At least two command tags had a label but this one doesn't. std::make_pair(entry.text().get(), entry.attribute("label").as_string()));
LOG(LogError) }
<< "Missing mandatory label attribute for alternative emulator "
"entry, no additional command tags will be processed for system \"" // Platform ID list
<< name << "\""; const std::string platformList =
Utils::String::toLower(system.child("platform").text().get());
if (platformList == "") {
LOG(LogWarning) << "No platform defined for system \"" << name
<< "\", scraper searches will be inaccurate";
}
std::vector<std::string> platformStrs = readList(platformList);
std::vector<PlatformIds::PlatformId> platformIds;
for (auto it = platformStrs.cbegin(); it != platformStrs.cend(); it++) {
std::string str = *it;
PlatformIds::PlatformId platformId = PlatformIds::getPlatformId(str);
if (platformId == PlatformIds::PLATFORM_IGNORE) {
// When platform is PLATFORM_IGNORE, do not allow other platforms.
platformIds.clear();
platformIds.push_back(platformId);
break; 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 first 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 // If there's a platform entry defined but it does not match the list of supported
const std::string platformList = // platforms, then generate a warning.
Utils::String::toLower(system.child("platform").text().get()); if (str != "" && platformId == PlatformIds::PLATFORM_UNKNOWN)
LOG(LogWarning) << "Unknown platform \"" << str << "\" defined for system \""
if (platformList == "") { << name << "\", scraper searches will be inaccurate";
LOG(LogWarning) << "No platform defined for system \"" << name else if (platformId != PlatformIds::PLATFORM_UNKNOWN)
<< "\", scraper searches will be inaccurate"; platformIds.push_back(platformId);
}
std::vector<std::string> platformStrs = readList(platformList);
std::vector<PlatformIds::PlatformId> platformIds;
for (auto it = platformStrs.cbegin(); it != platformStrs.cend(); it++) {
std::string str = *it;
PlatformIds::PlatformId platformId = PlatformIds::getPlatformId(str);
if (platformId == PlatformIds::PLATFORM_IGNORE) {
// When platform is PLATFORM_IGNORE, do not allow other platforms.
platformIds.clear();
platformIds.push_back(platformId);
break;
} }
// If there's a platform entry defined but it does not match the list of supported // Theme folder.
// platforms, then generate a warning. themeFolder = system.child("theme").text().as_string(name.c_str());
if (str != "" && platformId == PlatformIds::PLATFORM_UNKNOWN)
LOG(LogWarning) << "Unknown platform \"" << str << "\" defined for system \""
<< name << "\", scraper searches will be inaccurate";
else if (platformId != PlatformIds::PLATFORM_UNKNOWN)
platformIds.push_back(platformId);
}
// Theme folder. // Validate.
themeFolder = system.child("theme").text().as_string(name.c_str());
// Validate. if (name.empty()) {
LOG(LogError)
<< "A system in the es_systems.xml file has no name defined, skipping entry";
continue;
}
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";
continue;
}
if (name.empty()) { // Convert path to generic directory seperators.
LOG(LogError) path = Utils::FileSystem::getGenericPath(path);
<< "A system in the es_systems.xml file has no name defined, skipping entry";
continue;
}
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";
continue;
}
// Convert path to generic directory seperators.
path = Utils::FileSystem::getGenericPath(path);
#if defined(_WIN64) #if defined(_WIN64)
if (!Settings::getInstance()->getBool("ShowHiddenFiles") && if (!Settings::getInstance()->getBool("ShowHiddenFiles") &&
Utils::FileSystem::isHidden(path)) { Utils::FileSystem::isHidden(path)) {
LOG(LogWarning) << "Skipping hidden ROM folder \"" << path << "\""; LOG(LogWarning) << "Skipping hidden ROM folder \"" << path << "\"";
continue; continue;
} }
#endif #endif
// Create the system runtime environment data. // Create the system runtime environment data.
SystemEnvironmentData* envData = new SystemEnvironmentData; SystemEnvironmentData* envData = new SystemEnvironmentData;
envData->mStartPath = path; envData->mStartPath = path;
envData->mSearchExtensions = extensions; envData->mSearchExtensions = extensions;
envData->mLaunchCommands = commands; envData->mLaunchCommands = commands;
envData->mPlatformIds = platformIds; envData->mPlatformIds = platformIds;
SystemData* newSys = new SystemData(name, fullname, envData, themeFolder); SystemData* newSys = new SystemData(name, fullname, envData, themeFolder);
bool onlyHidden = false; bool onlyHidden = false;
// 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 =
onlyHidden = true; newSys->getRootFolder()->getChildrenRecursive();
for (auto it = recursiveGames.cbegin(); it != recursiveGames.cend(); it++) { onlyHidden = true;
if ((*it)->getType() != FOLDER) { for (auto it = recursiveGames.cbegin(); it != recursiveGames.cend(); it++) {
onlyHidden = (*it)->getHidden(); if ((*it)->getType() != FOLDER) {
if (!onlyHidden) onlyHidden = (*it)->getHidden();
break; if (!onlyHidden)
break;
}
} }
} }
}
if (newSys->getRootFolder()->getChildrenByFilename().size() == 0 || onlyHidden) { if (newSys->getRootFolder()->getChildrenByFilename().size() == 0 || onlyHidden) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name
<< "\" as no files matched any of the defined file extensions"; << "\" as no files matched any of the defined file extensions";
delete newSys; delete newSys;
} }
else { else {
sSystemVector.push_back(newSys); sSystemVector.push_back(newSys);
}
} }
} }
@ -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,144 +732,187 @@ 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
pugi::xml_document doc; // processing of the bundled configuration file.
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(configPaths.front().c_str());
#endif #endif
if (res) {
if (!res) { pugi::xml_node loadExclusive = doc.child("loadExclusive");
LOG(LogError) << "Couldn't parse es_systems.xml"; if (loadExclusive)
LOG(LogError) << res.description(); onlyProcessCustomFile = true;
return true;
}
// Actually read the file.
pugi::xml_node systemList = doc.child("systemList");
if (!systemList) {
LOG(LogError) << "es_systems.xml is missing the <systemList> tag";
return true;
}
std::vector<std::string> systemsVector;
for (pugi::xml_node system = systemList.child("system"); system;
system = system.next_sibling("system")) {
std::string systemDir;
std::string name;
std::string fullname;
std::string path;
std::string extensions;
std::vector<std::string> commands;
std::string platform;
std::string themeFolder;
const std::string systemInfoFileName = "/systeminfo.txt";
bool replaceInfoFile = false;
std::ofstream systemInfoFile;
name = system.child("name").text().get();
fullname = system.child("fullname").text().get();
path = system.child("path").text().get();
extensions = system.child("extension").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());
// Check that the %ROMPATH% variable is actually used for the path element. // Process the custom es_systems.xml file after the bundled file, as any systems with identical
// If not, skip the system. // <path> tags will be overwritten by the last occurrence.
if (path.find("%ROMPATH%") != 0) { std::reverse(configPaths.begin(), configPaths.end());
LOG(LogWarning) << "The path element for system \"" << name
<< "\" does not " std::vector<std::pair<std::string, std::string>> systemsVector;
"utilize the %ROMPATH% variable, skipping entry";
for (auto configPath : configPaths) {
// If the loadExclusive tag is present.
if (onlyProcessCustomFile && configPath == configPaths.front())
continue; continue;
}
else {
systemDir = path.substr(9, path.size() - 9);
}
// Trim any leading directory separator characters. LOG(LogInfo) << "Parsing systems configuration file \"" << configPath << "\"...";
systemDir.erase(systemDir.begin(),
std::find_if(systemDir.begin(), systemDir.end(),
[](char c) { return c != '/' && c != '\\'; }));
if (!Utils::FileSystem::exists(rompath + systemDir)) {
if (!Utils::FileSystem::createDirectory(rompath + systemDir)) {
LOG(LogError) << "Couldn't create system directory \"" << systemDir
<< "\", permission problems or disk full?";
return true;
}
else {
LOG(LogInfo) << "Created system directory \"" << systemDir << "\"";
}
}
else {
LOG(LogInfo) << "System directory \"" << systemDir << "\" already exists";
}
if (Utils::FileSystem::exists(rompath + systemDir + systemInfoFileName))
replaceInfoFile = true;
else
replaceInfoFile = false;
if (replaceInfoFile) {
if (Utils::FileSystem::removeFile(rompath + systemDir + systemInfoFileName))
return true;
}
pugi::xml_document doc;
#if defined(_WIN64) #if defined(_WIN64)
systemInfoFile.open( pugi::xml_parse_result res =
Utils::String::stringToWideString(rompath + systemDir + systemInfoFileName).c_str()); doc.load_file(Utils::String::stringToWideString(configPath).c_str());
#else #else
systemInfoFile.open(rompath + systemDir + systemInfoFileName); pugi::xml_parse_result res = doc.load_file(configPath.c_str());
#endif #endif
if (systemInfoFile.fail()) { if (!res) {
LOG(LogError) << "Couldn't create system information file \"" LOG(LogError) << "Couldn't parse es_systems.xml";
<< rompath + systemDir + systemInfoFileName LOG(LogError) << res.description();
<< "\", permission problems or disk full?";
systemInfoFile.close();
return true; return true;
} }
systemInfoFile << "System name:" << std::endl; // Actually read the file.
systemInfoFile << name << std::endl << std::endl; pugi::xml_node systemList = doc.child("systemList");
systemInfoFile << "Full system name:" << std::endl;
systemInfoFile << fullname << std::endl << std::endl;
systemInfoFile << "Supported file extensions:" << std::endl;
systemInfoFile << extensions << std::endl << std::endl;
systemInfoFile << "Launch command:" << 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;
systemInfoFile << themeFolder << std::endl;
systemInfoFile.close();
systemsVector.push_back(systemDir + ": " + fullname); if (!systemList) {
LOG(LogError) << "es_systems.xml is missing the <systemList> tag";
if (replaceInfoFile) { return true;
LOG(LogInfo) << "Replaced existing system information file \""
<< rompath + systemDir + systemInfoFileName << "\"";
} }
else {
LOG(LogInfo) << "Created system information file \"" for (pugi::xml_node system = systemList.child("system"); system;
<< rompath + systemDir + systemInfoFileName << "\""; system = system.next_sibling("system")) {
std::string systemDir;
std::string name;
std::string fullname;
std::string path;
std::string extensions;
std::vector<std::string> commands;
std::string platform;
std::string themeFolder;
const std::string systemInfoFileName = "/systeminfo.txt";
bool replaceInfoFile = false;
std::ofstream systemInfoFile;
name = system.child("name").text().get();
fullname = system.child("fullname").text().get();
path = system.child("path").text().get();
extensions = system.child("extension").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());
// Check that the %ROMPATH% variable is actually used for the path element.
// If not, skip the system.
if (path.find("%ROMPATH%") != 0) {
LOG(LogWarning) << "The path element for system \"" << name
<< "\" does not "
"utilize the %ROMPATH% variable, skipping entry";
continue;
}
else {
systemDir = path.substr(9, path.size() - 9);
}
// Trim any leading directory separator characters.
systemDir.erase(systemDir.begin(),
std::find_if(systemDir.begin(), systemDir.end(),
[](char c) { return c != '/' && c != '\\'; }));
if (!Utils::FileSystem::exists(rompath + systemDir)) {
if (!Utils::FileSystem::createDirectory(rompath + systemDir)) {
LOG(LogError) << "Couldn't create system directory \"" << systemDir
<< "\", permission problems or disk full?";
return true;
}
else {
LOG(LogInfo) << "Created system directory \"" << systemDir << "\"";
}
}
else {
LOG(LogInfo) << "System directory \"" << systemDir << "\" already exists";
}
if (Utils::FileSystem::exists(rompath + systemDir + systemInfoFileName))
replaceInfoFile = true;
else
replaceInfoFile = false;
if (replaceInfoFile) {
if (Utils::FileSystem::removeFile(rompath + systemDir + systemInfoFileName))
return true;
}
#if defined(_WIN64)
systemInfoFile.open(
Utils::String::stringToWideString(rompath + systemDir + systemInfoFileName)
.c_str());
#else
systemInfoFile.open(rompath + systemDir + systemInfoFileName);
#endif
if (systemInfoFile.fail()) {
LOG(LogError) << "Couldn't create system information file \""
<< rompath + systemDir + systemInfoFileName
<< "\", permission problems or disk full?";
systemInfoFile.close();
return true;
}
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 << "Full system name:" << std::endl;
systemInfoFile << fullname << std::endl << std::endl;
systemInfoFile << "Supported file extensions:" << std::endl;
systemInfoFile << extensions << std::endl << std::endl;
systemInfoFile << "Launch command:" << 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;
systemInfoFile << themeFolder << std::endl;
systemInfoFile.close();
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) {
LOG(LogInfo) << "Replaced existing system information file \""
<< rompath + systemDir + systemInfoFileName << "\"";
}
else {
LOG(LogInfo) << "Created system information file \""
<< rompath + systemDir + systemInfoFileName << "\"";
}
} }
} }
@ -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();