2014-06-25 16:29:58 +00:00
|
|
|
#include "SystemData.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
|
2018-01-09 22:55:09 +00:00
|
|
|
#include "utils/FileSystemUtil.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "CollectionSystemManager.h"
|
|
|
|
#include "FileFilterIndex.h"
|
|
|
|
#include "FileSorts.h"
|
2014-06-25 16:29:58 +00:00
|
|
|
#include "Gamelist.h"
|
|
|
|
#include "Log.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "platform.h"
|
2014-06-25 16:29:58 +00:00
|
|
|
#include "Settings.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "ThemeData.h"
|
2017-11-10 19:16:42 +00:00
|
|
|
#include <pugixml/src/pugixml.hpp>
|
2017-11-01 22:21:10 +00:00
|
|
|
#include <fstream>
|
|
|
|
#ifdef WIN32
|
|
|
|
#include <Windows.h>
|
|
|
|
#endif
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
std::vector<SystemData*> SystemData::sSystemVector;
|
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
SystemData::SystemData(const std::string& name, const std::string& fullName, SystemEnvironmentData* envData, const std::string& themeFolder, bool CollectionSystem) :
|
|
|
|
mName(name), mFullName(fullName), mEnvData(envData), mThemeFolder(themeFolder), mIsCollectionSystem(CollectionSystem), mIsGameSystem(true)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2017-03-18 17:54:39 +00:00
|
|
|
mFilterIndex = new FileFilterIndex();
|
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
// if it's an actual system, initialize it, if not, just create the data structure
|
|
|
|
if(!CollectionSystem)
|
|
|
|
{
|
|
|
|
mRootFolder = new FileData(FOLDER, mEnvData->mStartPath, mEnvData, this);
|
|
|
|
mRootFolder->metadata.set("name", mFullName);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
if(!Settings::getInstance()->getBool("ParseGamelistOnly"))
|
|
|
|
populateFolder(mRootFolder);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
if(!Settings::getInstance()->getBool("IgnoreGamelist"))
|
|
|
|
parseGamelist(this);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
mRootFolder->sort(FileSorts::SortTypes.at(0));
|
2017-11-04 16:03:24 +00:00
|
|
|
|
|
|
|
indexAllGameFilters(mRootFolder);
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// virtual systems are updated afterwards, we're just creating the data structure
|
|
|
|
mRootFolder = new FileData(FOLDER, "" + name, mEnvData, this);
|
|
|
|
}
|
|
|
|
setIsGameSystemStatus();
|
2014-06-25 16:29:58 +00:00
|
|
|
loadTheme();
|
|
|
|
}
|
|
|
|
|
|
|
|
SystemData::~SystemData()
|
|
|
|
{
|
|
|
|
//save changed game data back to xml
|
2017-06-12 16:38:59 +00:00
|
|
|
if(!Settings::getInstance()->getBool("IgnoreGamelist") && Settings::getInstance()->getBool("SaveGamelistsOnExit") && !mIsCollectionSystem)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
|
|
|
updateGamelist(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
delete mRootFolder;
|
2017-03-18 17:54:39 +00:00
|
|
|
delete mFilterIndex;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
void SystemData::setIsGameSystemStatus()
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2017-06-12 16:38:59 +00:00
|
|
|
// we exclude non-game systems from specific operations (i.e. the "RetroPie" system, at least)
|
|
|
|
// if/when there are more in the future, maybe this can be a more complex method, with a proper list
|
|
|
|
// but for now a simple string comparison is more performant
|
|
|
|
mIsGameSystem = (mName != "retropie");
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2017-10-21 15:13:40 +00:00
|
|
|
// test to see if a file is hidden
|
2017-12-04 23:30:25 +00:00
|
|
|
bool isHidden(const boost::filesystem::path &filePath)
|
2017-07-24 23:12:40 +00:00
|
|
|
{
|
2017-10-21 15:13:40 +00:00
|
|
|
#ifdef WIN32
|
|
|
|
const DWORD Attributes = GetFileAttributes(filePath.generic_string().c_str());
|
|
|
|
return (Attributes != INVALID_FILE_ATTRIBUTES) && (Attributes & FILE_ATTRIBUTE_HIDDEN);
|
|
|
|
#else
|
2017-12-04 23:30:25 +00:00
|
|
|
boost::filesystem::path::string_type fileName = filePath.filename().string();
|
2017-10-21 15:13:40 +00:00
|
|
|
return fileName[0] == '.';
|
2017-07-24 23:12:40 +00:00
|
|
|
#endif
|
2017-10-21 15:13:40 +00:00
|
|
|
}
|
2017-07-24 23:12:40 +00:00
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
void SystemData::populateFolder(FileData* folder)
|
|
|
|
{
|
2017-12-04 23:30:25 +00:00
|
|
|
const boost::filesystem::path& folderPath = folder->getPath();
|
2018-01-09 22:55:09 +00:00
|
|
|
if(!Utils::FileSystem::isDirectory(folderPath.generic_string()))
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
|
|
|
LOG(LogWarning) << "Error - folder with path \"" << folderPath << "\" is not a directory!";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string folderStr = folderPath.generic_string();
|
|
|
|
|
|
|
|
//make sure that this isn't a symlink to a thing we already have
|
2018-01-09 22:55:09 +00:00
|
|
|
if(Utils::FileSystem::isSymlink(folderPath.generic_string()))
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
|
|
|
//if this symlink resolves to somewhere that's at the beginning of our path, it's gonna recurse
|
2018-01-27 20:04:08 +00:00
|
|
|
if(folderStr.find(Utils::FileSystem::getCanonicalPath(folderPath.generic_string())) == 0)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
|
|
|
LOG(LogWarning) << "Skipping infinitely recursive symlink \"" << folderPath << "\"";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-04 23:30:25 +00:00
|
|
|
boost::filesystem::path filePath;
|
2014-06-25 16:29:58 +00:00
|
|
|
std::string extension;
|
|
|
|
bool isGame;
|
2017-07-24 23:12:40 +00:00
|
|
|
bool showHidden = Settings::getInstance()->getBool("ShowHiddenFiles");
|
2018-01-27 20:04:08 +00:00
|
|
|
Utils::FileSystem::stringList dirContent = Utils::FileSystem::getDirContent(folderPath.generic_string());
|
|
|
|
for(Utils::FileSystem::stringList::const_iterator it = dirContent.cbegin(); it != dirContent.cend(); ++it)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2018-01-27 20:04:08 +00:00
|
|
|
filePath = *it;
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
if(filePath.stem().empty())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
//this is a little complicated because we allow a list of extensions to be defined (delimited with a space)
|
|
|
|
//we first get the extension of the file itself:
|
|
|
|
extension = filePath.extension().string();
|
2017-05-18 10:16:57 +00:00
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
//fyi, folders *can* also match the extension and be added as games - this is mostly just to support higan
|
|
|
|
//see issue #75: https://github.com/Aloshi/EmulationStation/issues/75
|
|
|
|
|
|
|
|
isGame = false;
|
2017-11-11 14:56:22 +00:00
|
|
|
if(std::find(mEnvData->mSearchExtensions.cbegin(), mEnvData->mSearchExtensions.cend(), extension) != mEnvData->mSearchExtensions.cend())
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2017-07-24 23:12:40 +00:00
|
|
|
// skip hidden files
|
|
|
|
if(!showHidden && isHidden(filePath))
|
|
|
|
continue;
|
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
FileData* newGame = new FileData(GAME, filePath.generic_string(), mEnvData, this);
|
2014-06-25 16:29:58 +00:00
|
|
|
folder->addChild(newGame);
|
|
|
|
isGame = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//add directories that also do not match an extension as folders
|
2018-01-09 22:55:09 +00:00
|
|
|
if(!isGame && Utils::FileSystem::isDirectory(filePath.generic_string()))
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2017-06-12 16:38:59 +00:00
|
|
|
FileData* newFolder = new FileData(FOLDER, filePath.generic_string(), mEnvData, this);
|
2014-06-25 16:29:58 +00:00
|
|
|
populateFolder(newFolder);
|
|
|
|
|
|
|
|
//ignore folders that do not contain games
|
2016-08-09 20:26:30 +00:00
|
|
|
if(newFolder->getChildrenByFilename().size() == 0)
|
2014-06-25 16:29:58 +00:00
|
|
|
delete newFolder;
|
|
|
|
else
|
|
|
|
folder->addChild(newFolder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-04 16:03:24 +00:00
|
|
|
void SystemData::indexAllGameFilters(const FileData* folder)
|
|
|
|
{
|
|
|
|
const std::vector<FileData*>& children = folder->getChildren();
|
|
|
|
|
2017-11-11 14:56:22 +00:00
|
|
|
for(std::vector<FileData*>::const_iterator it = children.cbegin(); it != children.cend(); ++it)
|
2017-11-04 16:03:24 +00:00
|
|
|
{
|
|
|
|
switch((*it)->getType())
|
|
|
|
{
|
|
|
|
case GAME: { mFilterIndex->addToIndex(*it); } break;
|
|
|
|
case FOLDER: { indexAllGameFilters(*it); } break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
std::vector<std::string> readList(const std::string& str, const char* delims = " \t\r\n,")
|
|
|
|
{
|
|
|
|
std::vector<std::string> ret;
|
|
|
|
|
|
|
|
size_t prevOff = str.find_first_not_of(delims, 0);
|
|
|
|
size_t off = str.find_first_of(delims, prevOff);
|
|
|
|
while(off != std::string::npos || prevOff != std::string::npos)
|
|
|
|
{
|
|
|
|
ret.push_back(str.substr(prevOff, off - prevOff));
|
|
|
|
|
|
|
|
prevOff = str.find_first_not_of(delims, off);
|
|
|
|
off = str.find_first_of(delims, prevOff);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
//creates systems from information located in a config file
|
|
|
|
bool SystemData::loadConfig()
|
|
|
|
{
|
|
|
|
deleteSystems();
|
|
|
|
|
|
|
|
std::string path = getConfigPath(false);
|
|
|
|
|
|
|
|
LOG(LogInfo) << "Loading system config file " << path << "...";
|
|
|
|
|
2018-01-09 22:55:09 +00:00
|
|
|
if(!Utils::FileSystem::exists(path))
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
|
|
|
LOG(LogError) << "es_systems.cfg file does not exist!";
|
|
|
|
writeExampleConfig(getConfigPath(true));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
pugi::xml_document doc;
|
|
|
|
pugi::xml_parse_result res = doc.load_file(path.c_str());
|
|
|
|
|
|
|
|
if(!res)
|
|
|
|
{
|
|
|
|
LOG(LogError) << "Could not parse es_systems.cfg file!";
|
|
|
|
LOG(LogError) << res.description();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//actually read the file
|
|
|
|
pugi::xml_node systemList = doc.child("systemList");
|
|
|
|
|
|
|
|
if(!systemList)
|
|
|
|
{
|
|
|
|
LOG(LogError) << "es_systems.cfg is missing the <systemList> tag!";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(pugi::xml_node system = systemList.child("system"); system; system = system.next_sibling("system"))
|
|
|
|
{
|
|
|
|
std::string name, fullname, path, cmd, themeFolder;
|
|
|
|
|
|
|
|
name = system.child("name").text().get();
|
|
|
|
fullname = system.child("fullname").text().get();
|
|
|
|
path = system.child("path").text().get();
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
|
|
|
// platform id list
|
|
|
|
const char* platformList = system.child("platform").text().get();
|
|
|
|
std::vector<std::string> platformStrs = readList(platformList);
|
|
|
|
std::vector<PlatformIds::PlatformId> platformIds;
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = platformStrs.cbegin(); it != platformStrs.cend(); it++)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
|
|
|
const char* str = it->c_str();
|
|
|
|
PlatformIds::PlatformId platformId = PlatformIds::getPlatformId(str);
|
2017-05-18 10:16:57 +00:00
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
if(platformId == PlatformIds::PLATFORM_IGNORE)
|
|
|
|
{
|
|
|
|
// when platform is ignore, do not allow other platforms
|
|
|
|
platformIds.clear();
|
|
|
|
platformIds.push_back(platformId);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there appears to be an actual platform ID supplied but it didn't match the list, warn
|
|
|
|
if(str != NULL && str[0] != '\0' && platformId == PlatformIds::PLATFORM_UNKNOWN)
|
|
|
|
LOG(LogWarning) << " Unknown platform for system \"" << name << "\" (platform \"" << str << "\" from list \"" << platformList << "\")";
|
|
|
|
else if(platformId != PlatformIds::PLATFORM_UNKNOWN)
|
|
|
|
platformIds.push_back(platformId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// theme folder
|
|
|
|
themeFolder = system.child("theme").text().as_string(name.c_str());
|
|
|
|
|
2016-03-29 04:03:39 +00:00
|
|
|
//validate
|
|
|
|
if(name.empty() || path.empty() || extensions.empty() || cmd.empty())
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
|
|
|
LOG(LogError) << "System \"" << name << "\" is missing name, path, extension, or command!";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-03-29 04:03:39 +00:00
|
|
|
//convert path to generic directory seperators
|
|
|
|
boost::filesystem::path genericPath(path);
|
|
|
|
path = genericPath.generic_string();
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
//expand home symbol if the startpath contains ~
|
|
|
|
if(path[0] == '~')
|
|
|
|
{
|
|
|
|
path.erase(0, 1);
|
2018-01-27 20:04:08 +00:00
|
|
|
path.insert(0, Utils::FileSystem::getHomePath());
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//create the system runtime environment data
|
|
|
|
SystemEnvironmentData* envData = new SystemEnvironmentData;
|
|
|
|
envData->mStartPath = path;
|
|
|
|
envData->mSearchExtensions = extensions;
|
|
|
|
envData->mLaunchCommand = cmd;
|
|
|
|
envData->mPlatformIds = platformIds;
|
|
|
|
|
|
|
|
SystemData* newSys = new SystemData(name, fullname, envData, themeFolder);
|
2016-08-09 20:26:30 +00:00
|
|
|
if(newSys->getRootFolder()->getChildrenByFilename().size() == 0)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
|
|
|
LOG(LogWarning) << "System \"" << name << "\" has no games! Ignoring it.";
|
|
|
|
delete newSys;
|
|
|
|
}else{
|
|
|
|
sSystemVector.push_back(newSys);
|
|
|
|
}
|
|
|
|
}
|
2017-06-12 16:38:59 +00:00
|
|
|
CollectionSystemManager::get()->loadCollectionSystems();
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SystemData::writeExampleConfig(const std::string& path)
|
|
|
|
{
|
|
|
|
std::ofstream file(path.c_str());
|
|
|
|
|
|
|
|
file << "<!-- This is the EmulationStation Systems configuration file.\n"
|
|
|
|
"All systems must be contained within the <systemList> tag.-->\n"
|
|
|
|
"\n"
|
|
|
|
"<systemList>\n"
|
|
|
|
" <!-- Here's an example system to get you started. -->\n"
|
|
|
|
" <system>\n"
|
|
|
|
"\n"
|
|
|
|
" <!-- A short name, used internally. Traditionally lower-case. -->\n"
|
|
|
|
" <name>nes</name>\n"
|
|
|
|
"\n"
|
|
|
|
" <!-- A \"pretty\" name, displayed in menus and such. -->\n"
|
|
|
|
" <fullname>Nintendo Entertainment System</fullname>\n"
|
|
|
|
"\n"
|
|
|
|
" <!-- The path to start searching for ROMs in. '~' will be expanded to $HOME on Linux or %HOMEPATH% on Windows. -->\n"
|
|
|
|
" <path>~/roms/nes</path>\n"
|
|
|
|
"\n"
|
|
|
|
" <!-- A list of extensions to search for, delimited by any of the whitespace characters (\", \\r\\n\\t\").\n"
|
|
|
|
" You MUST include the period at the start of the extension! It's also case sensitive. -->\n"
|
|
|
|
" <extension>.nes .NES</extension>\n"
|
|
|
|
"\n"
|
|
|
|
" <!-- The shell command executed when a game is selected. A few special tags are replaced if found in a command:\n"
|
|
|
|
" %ROM% is replaced by a bash-special-character-escaped absolute path to the ROM.\n"
|
|
|
|
" %BASENAME% is replaced by the \"base\" name of the ROM. For example, \"/foo/bar.rom\" would have a basename of \"bar\". Useful for MAME.\n"
|
|
|
|
" %ROM_RAW% is the raw, unescaped path to the ROM. -->\n"
|
|
|
|
" <command>retroarch -L ~/cores/libretro-fceumm.so %ROM%</command>\n"
|
|
|
|
"\n"
|
|
|
|
" <!-- The platform to use when scraping. You can see the full list of accepted platforms in src/PlatformIds.cpp.\n"
|
|
|
|
" It's case sensitive, but everything is lowercase. This tag is optional.\n"
|
|
|
|
" You can use multiple platforms too, delimited with any of the whitespace characters (\", \\r\\n\\t\"), eg: \"genesis, megadrive\" -->\n"
|
|
|
|
" <platform>nes</platform>\n"
|
|
|
|
"\n"
|
|
|
|
" <!-- The theme to load from the current theme set. See THEMES.md for more information.\n"
|
|
|
|
" This tag is optional. If not set, it will default to the value of <name>. -->\n"
|
|
|
|
" <theme>nes</theme>\n"
|
|
|
|
" </system>\n"
|
|
|
|
"</systemList>\n";
|
|
|
|
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
LOG(LogError) << "Example config written! Go read it at \"" << path << "\"!";
|
|
|
|
}
|
|
|
|
|
|
|
|
void SystemData::deleteSystems()
|
|
|
|
{
|
|
|
|
for(unsigned int i = 0; i < sSystemVector.size(); i++)
|
|
|
|
{
|
|
|
|
delete sSystemVector.at(i);
|
|
|
|
}
|
|
|
|
sSystemVector.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string SystemData::getConfigPath(bool forWrite)
|
|
|
|
{
|
2018-01-27 20:04:08 +00:00
|
|
|
boost::filesystem::path path = Utils::FileSystem::getHomePath() + "/.emulationstation/es_systems.cfg";
|
2018-01-09 22:55:09 +00:00
|
|
|
if(forWrite || Utils::FileSystem::exists(path.generic_string()))
|
2014-06-25 16:29:58 +00:00
|
|
|
return path.generic_string();
|
|
|
|
|
|
|
|
return "/etc/emulationstation/es_systems.cfg";
|
|
|
|
}
|
|
|
|
|
2017-09-08 13:20:07 +00:00
|
|
|
SystemData* SystemData::getNext() const
|
|
|
|
{
|
|
|
|
std::vector<SystemData*>::const_iterator it = getIterator();
|
|
|
|
|
|
|
|
do {
|
|
|
|
it++;
|
2017-11-11 14:56:22 +00:00
|
|
|
if (it == sSystemVector.cend())
|
|
|
|
it = sSystemVector.cbegin();
|
2017-09-08 13:20:07 +00:00
|
|
|
} while ((*it)->getDisplayedGameCount() == 0);
|
|
|
|
// as we are starting in a valid gamelistview, this will always succeed, even if we have to come full circle.
|
|
|
|
|
|
|
|
return *it;
|
|
|
|
}
|
|
|
|
|
|
|
|
SystemData* SystemData::getPrev() const
|
|
|
|
{
|
2017-11-11 14:56:22 +00:00
|
|
|
std::vector<SystemData*>::const_reverse_iterator it = getRevIterator();
|
|
|
|
|
2017-09-08 13:20:07 +00:00
|
|
|
do {
|
|
|
|
it++;
|
2017-11-11 14:56:22 +00:00
|
|
|
if (it == sSystemVector.crend())
|
|
|
|
it = sSystemVector.crbegin();
|
2017-09-08 13:20:07 +00:00
|
|
|
} while ((*it)->getDisplayedGameCount() == 0);
|
|
|
|
// as we are starting in a valid gamelistview, this will always succeed, even if we have to come full circle.
|
|
|
|
|
|
|
|
return *it;
|
|
|
|
}
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
std::string SystemData::getGamelistPath(bool forWrite) const
|
|
|
|
{
|
2017-12-04 23:30:25 +00:00
|
|
|
boost::filesystem::path filePath;
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
filePath = mRootFolder->getPath() / "gamelist.xml";
|
2018-01-09 22:55:09 +00:00
|
|
|
if(Utils::FileSystem::exists(filePath.generic_string()))
|
2014-06-25 16:29:58 +00:00
|
|
|
return filePath.generic_string();
|
|
|
|
|
2018-01-27 20:04:08 +00:00
|
|
|
filePath = Utils::FileSystem::getHomePath() + "/.emulationstation/gamelists/" + mName + "/gamelist.xml";
|
2014-06-25 16:29:58 +00:00
|
|
|
if(forWrite) // make sure the directory exists if we're going to write to it, or crashes will happen
|
2018-01-09 22:55:09 +00:00
|
|
|
Utils::FileSystem::createDirectory(filePath.parent_path().generic_string());
|
|
|
|
if(forWrite || Utils::FileSystem::exists(filePath.generic_string()))
|
2014-06-25 16:29:58 +00:00
|
|
|
return filePath.generic_string();
|
|
|
|
|
|
|
|
return "/etc/emulationstation/gamelists/" + mName + "/gamelist.xml";
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string SystemData::getThemePath() const
|
|
|
|
{
|
|
|
|
// where we check for themes, in order:
|
|
|
|
// 1. [SYSTEM_PATH]/theme.xml
|
2017-05-14 04:07:28 +00:00
|
|
|
// 2. system theme from currently selected theme set [CURRENT_THEME_PATH]/[SYSTEM]/theme.xml
|
|
|
|
// 3. default system theme from currently selected theme set [CURRENT_THEME_PATH]/theme.xml
|
2014-06-25 16:29:58 +00:00
|
|
|
|
|
|
|
// first, check game folder
|
2017-12-04 23:30:25 +00:00
|
|
|
boost::filesystem::path localThemePath = mRootFolder->getPath() / "theme.xml";
|
2018-01-09 22:55:09 +00:00
|
|
|
if(Utils::FileSystem::exists(localThemePath.generic_string()))
|
2014-06-25 16:29:58 +00:00
|
|
|
return localThemePath.generic_string();
|
|
|
|
|
2017-05-14 04:07:28 +00:00
|
|
|
// not in game folder, try system theme in theme sets
|
|
|
|
localThemePath = ThemeData::getThemeFromCurrentSet(mThemeFolder);
|
|
|
|
|
2018-01-09 22:55:09 +00:00
|
|
|
if (Utils::FileSystem::exists(localThemePath.generic_string()))
|
2017-05-14 04:07:28 +00:00
|
|
|
return localThemePath.generic_string();
|
|
|
|
|
|
|
|
// not system theme, try default system theme in theme set
|
|
|
|
localThemePath = localThemePath.parent_path().parent_path() / "theme.xml";
|
|
|
|
|
|
|
|
return localThemePath.generic_string();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool SystemData::hasGamelist() const
|
|
|
|
{
|
2018-01-09 22:55:09 +00:00
|
|
|
return (Utils::FileSystem::exists(getGamelistPath(false)));
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int SystemData::getGameCount() const
|
|
|
|
{
|
2017-11-17 14:58:52 +00:00
|
|
|
return (unsigned int)mRootFolder->getFilesRecursive(GAME).size();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
SystemData* SystemData::getRandomSystem()
|
|
|
|
{
|
|
|
|
// this is a bit brute force. It might be more efficient to just to a while (!gameSystem) do random again...
|
|
|
|
unsigned int total = 0;
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = sSystemVector.cbegin(); it != sSystemVector.cend(); it++)
|
2017-06-12 16:38:59 +00:00
|
|
|
{
|
|
|
|
if ((*it)->isGameSystem())
|
|
|
|
total ++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get random number in range
|
2017-11-17 14:58:52 +00:00
|
|
|
int target = (int)Math::round((std::rand() / (float)RAND_MAX) * (total - 1));
|
2017-11-11 14:56:22 +00:00
|
|
|
for (auto it = sSystemVector.cbegin(); it != sSystemVector.cend(); it++)
|
2017-06-12 16:38:59 +00:00
|
|
|
{
|
|
|
|
if ((*it)->isGameSystem())
|
|
|
|
{
|
2017-07-11 09:07:44 +00:00
|
|
|
if (target > 0)
|
2017-06-12 16:38:59 +00:00
|
|
|
{
|
|
|
|
target--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return (*it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-18 14:19:32 +00:00
|
|
|
|
|
|
|
// if we end up here, there is no valid system
|
|
|
|
return NULL;
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FileData* SystemData::getRandomGame()
|
|
|
|
{
|
|
|
|
std::vector<FileData*> list = mRootFolder->getFilesRecursive(GAME, true);
|
2017-11-17 14:58:52 +00:00
|
|
|
unsigned int total = (int)list.size();
|
2017-07-18 09:45:50 +00:00
|
|
|
int target = 0;
|
2017-06-12 16:38:59 +00:00
|
|
|
// get random number in range
|
2017-07-18 09:45:50 +00:00
|
|
|
if (total == 0)
|
|
|
|
return NULL;
|
2017-11-17 14:58:52 +00:00
|
|
|
target = (int)Math::round((std::rand() / (float)RAND_MAX) * (total - 1));
|
2017-06-12 16:38:59 +00:00
|
|
|
return list.at(target);
|
|
|
|
}
|
|
|
|
|
2017-03-18 17:54:39 +00:00
|
|
|
unsigned int SystemData::getDisplayedGameCount() const
|
|
|
|
{
|
2017-11-17 14:58:52 +00:00
|
|
|
return (unsigned int)mRootFolder->getFilesRecursive(GAME, true).size();
|
2017-03-18 17:54:39 +00:00
|
|
|
}
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
void SystemData::loadTheme()
|
|
|
|
{
|
|
|
|
mTheme = std::make_shared<ThemeData>();
|
|
|
|
|
|
|
|
std::string path = getThemePath();
|
|
|
|
|
2018-01-09 22:55:09 +00:00
|
|
|
if(!Utils::FileSystem::exists(path)) // no theme available for this platform
|
2014-06-25 16:29:58 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2017-05-14 04:07:28 +00:00
|
|
|
// build map with system variables for theme to use,
|
|
|
|
std::map<std::string, std::string> sysData;
|
|
|
|
sysData.insert(std::pair<std::string, std::string>("system.name", getName()));
|
|
|
|
sysData.insert(std::pair<std::string, std::string>("system.theme", getThemeFolder()));
|
|
|
|
sysData.insert(std::pair<std::string, std::string>("system.fullName", getFullName()));
|
|
|
|
|
|
|
|
mTheme->loadFile(sysData, path);
|
2014-06-25 16:29:58 +00:00
|
|
|
} catch(ThemeException& e)
|
|
|
|
{
|
|
|
|
LOG(LogError) << e.what();
|
|
|
|
mTheme = std::make_shared<ThemeData>(); // reset to empty
|
|
|
|
}
|
|
|
|
}
|