mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-25 23:55:38 +00:00
Moved es_systems.cfg to use XML.
Updated README.md to reflect new format. "descname" has been renamed to "fullname".
This commit is contained in:
parent
c7a150046a
commit
dbcb9aed37
38
README.md
38
README.md
|
@ -110,21 +110,42 @@ You can use `--help` to view a list of command-line options. Briefly outlined he
|
|||
|
||||
Writing an es_systems.cfg
|
||||
=========================
|
||||
The file `~/.emulationstation/es_systems.cfg` contains the system configuration data for EmulationStation. A system is a NAME, DESCNAME, PATH, EXTENSION, and COMMAND. You can define any number of systems, just use every required variable again. You can switch between systems by pressing left and right. They will cycle in the order they are defined.
|
||||
The file `~/.emulationstation/es_systems.cfg` contains the system configuration data for EmulationStation, written in XML.
|
||||
|
||||
The NAME is what ES will use to internally identify the system. Theme.xml and gamelist.xml files will also be searched for in `~/.emulationstation/NAME/` if not found at the root of PATH. It is recommended that you abbreviate here if necessary, e.g. "nes".
|
||||
The order EmulationStation displays systems reflects the order you define them in.
|
||||
|
||||
The DESCNAME is a "pretty" name for the system - it show up in a header if one is displayed. It is optional; if not supplied, it will copy NAME (note: DESCNAME must also *not* be the last tag you define for a system! This is due to the nature of how optional tags are implemented.).
|
||||
**NOTE:** A system *must* have at least one game present in its "path" directory, or ES will ignore it! If no systems are found, ES won't even start!
|
||||
|
||||
The PATH is where ES will start the search for ROMs. All subdirectories (and links!) will be included.
|
||||
Here's an example es_systems.cfg:
|
||||
|
||||
**NOTE:** A system *must* have at least one game present in its PATH directory, or ES will ignore it.
|
||||
```
|
||||
<!-- This is the EmulationStation Systems configuration file.
|
||||
All systems must be contained within the <systemList> tag.-->
|
||||
|
||||
The EXTENSION is a list of extensions ES will consider valid and add to the list when searching. Each extension *must* start with a period. The list is delimited by a space.
|
||||
<systemList>
|
||||
<!-- Here's an example system to get you started. -->
|
||||
<system>
|
||||
<!-- A short name, used internally. -->
|
||||
<name>SNES</name>
|
||||
|
||||
The COMMAND is the shell command ES will execute to start your emulator. As it is evaluated by the shell (i.e. bash), you can do some clever tricks if need be.
|
||||
<!-- A "pretty" name, displayed in the menus and such. This one is optional. -->
|
||||
<fullname>Super Nintendo Entertainment System</fullname>
|
||||
|
||||
The following "tags" are replaced by ES in COMMANDs:
|
||||
<!-- The path to start searching for ROMs in. '~' will be expanded to $HOME or $HOMEPATH, depending on platform.
|
||||
All subdirectories (and non-recursive links) will be included. -->
|
||||
<path>~/roms/snes</path>
|
||||
|
||||
<!-- A list of extensions to search for, delimited by a space. You MUST include the period! It's also case sensitive. -->
|
||||
<extension>.smc .sfc .SMC .SFC</extension>
|
||||
|
||||
<!-- The shell command executed when a game is selected. A few special tags are replaced if found in a command, like %ROM%. -->
|
||||
<command>snesemulator %ROM%</command>
|
||||
<!-- This example would run the bash command "snesemulator /home/user/roms/snes/Super\ Mario\ World.sfc". -->
|
||||
</system>
|
||||
</systemList>
|
||||
```
|
||||
|
||||
The following "tags" are replaced by ES in launch commands:
|
||||
|
||||
`%ROM%` - Replaced with absolute path to the selected ROM, with most Bash special characters escaped with a backslash.
|
||||
|
||||
|
@ -132,6 +153,7 @@ The following "tags" are replaced by ES in COMMANDs:
|
|||
|
||||
`%ROM_RAW%` - Replaced with the unescaped absolute path to the selected ROM. If your emulator is picky about paths, you might want to use this instead of %ROM%, but enclosed in quotes.
|
||||
|
||||
|
||||
gamelist.xml
|
||||
============
|
||||
|
||||
|
|
|
@ -20,20 +20,19 @@ namespace fs = boost::filesystem;
|
|||
std::string SystemData::getStartPath() { return mStartPath; }
|
||||
std::string SystemData::getExtension() { return mSearchExtension; }
|
||||
|
||||
SystemData::SystemData(std::string name, std::string descName, std::string startPath, std::string extension, std::string command)
|
||||
SystemData::SystemData(const std::string& name, const std::string& fullName, const std::string& startPath, const std::string& extension, const std::string& command)
|
||||
{
|
||||
mName = name;
|
||||
mDescName = descName;
|
||||
mFullName = fullName;
|
||||
mStartPath = startPath;
|
||||
|
||||
//expand home symbol if the startpath contains ~
|
||||
if(startPath[0] == '~')
|
||||
if(mStartPath[0] == '~')
|
||||
{
|
||||
startPath.erase(0, 1);
|
||||
std::string home = getHomePath();
|
||||
startPath.insert(0, home);
|
||||
mStartPath.erase(0, 1);
|
||||
mStartPath.insert(0, getHomePath());
|
||||
}
|
||||
|
||||
mStartPath = startPath;
|
||||
mSearchExtension = extension;
|
||||
mLaunchCommand = command;
|
||||
|
||||
|
@ -177,9 +176,12 @@ std::string SystemData::getName()
|
|||
return mName;
|
||||
}
|
||||
|
||||
std::string SystemData::getDescName()
|
||||
std::string SystemData::getFullName()
|
||||
{
|
||||
return mDescName;
|
||||
if(mFullName.empty())
|
||||
return mName;
|
||||
else
|
||||
return mFullName;
|
||||
}
|
||||
|
||||
//creates systems from information located in a config file
|
||||
|
@ -187,132 +189,99 @@ bool SystemData::loadConfig(const std::string& path, bool writeExample)
|
|||
{
|
||||
deleteSystems();
|
||||
|
||||
LOG(LogInfo) << "Loading system config file...";
|
||||
LOG(LogInfo) << "Loading system config file " << path << "...";
|
||||
|
||||
if(!fs::exists(path))
|
||||
{
|
||||
LOG(LogInfo) << "System config file \"" << path << "\" doesn't exist!";
|
||||
LOG(LogError) << "File does not exist!";
|
||||
|
||||
if(writeExample)
|
||||
writeExampleConfig(path);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ifstream file(path.c_str());
|
||||
if(file.is_open())
|
||||
{
|
||||
size_t lineNr = 0;
|
||||
std::string line;
|
||||
std::string sysName, sysDescName, sysPath, sysExtension, sysCommand;
|
||||
while(file.good())
|
||||
{
|
||||
lineNr++;
|
||||
std::getline(file, line);
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result res = doc.load_file(path.c_str());
|
||||
|
||||
//remove whitespace from line through STL and lambda magic
|
||||
line.erase(std::remove_if(line.begin(), line.end(), [&](char c){ return std::string("\t\r\n\v\f").find(c) != std::string::npos; }), line.end());
|
||||
if(!res)
|
||||
{
|
||||
LOG(LogError) << "Could not parse config file!";
|
||||
LOG(LogError) << res.description();
|
||||
return false;
|
||||
}
|
||||
|
||||
//skip blank lines and comments
|
||||
if(line.empty() || line.at(0) == '#')
|
||||
//actually read the file
|
||||
pugi::xml_node systemList = doc.child("systemList");
|
||||
|
||||
for(pugi::xml_node system = systemList.child("system"); system; system = system.next_sibling("system"))
|
||||
{
|
||||
std::string name, fullname, path, ext, cmd;
|
||||
name = system.child("name").text().get();
|
||||
fullname = system.child("fullname").text().get();
|
||||
path = system.child("path").text().get();
|
||||
ext = system.child("extension").text().get();
|
||||
cmd = system.child("command").text().get();
|
||||
|
||||
//validate
|
||||
if(name.empty() || path.empty() || ext.empty() || cmd.empty())
|
||||
{
|
||||
LOG(LogError) << "System \"" << name << "\" is missing name, path, extension, or command!";
|
||||
continue;
|
||||
|
||||
//find the name (left of the equals sign) and the value (right of the equals sign)
|
||||
bool lineValid = false;
|
||||
std::string varName;
|
||||
std::string varValue;
|
||||
const std::string::size_type equalsPos = line.find('=', 1);
|
||||
if(equalsPos != std::string::npos)
|
||||
{
|
||||
lineValid = true;
|
||||
varName = line.substr(0, equalsPos);
|
||||
varValue = line.substr(equalsPos + 1, line.length() - 1);
|
||||
}
|
||||
|
||||
if(lineValid)
|
||||
{
|
||||
//map the value to the appropriate variable
|
||||
if(varName == "NAME")
|
||||
sysName = varValue;
|
||||
else if(varName == "DESCNAME")
|
||||
sysDescName = varValue;
|
||||
else if(varName == "PATH")
|
||||
{
|
||||
if(varValue[varValue.length() - 1] == '/')
|
||||
sysPath = varValue.substr(0, varValue.length() - 1);
|
||||
else
|
||||
sysPath = varValue;
|
||||
//convert path to generic directory seperators
|
||||
boost::filesystem::path genericPath(sysPath);
|
||||
sysPath = genericPath.generic_string();
|
||||
}
|
||||
else if(varName == "EXTENSION")
|
||||
sysExtension = varValue;
|
||||
else if(varName == "COMMAND")
|
||||
sysCommand = varValue;
|
||||
boost::filesystem::path genericPath(path);
|
||||
path = genericPath.generic_string();
|
||||
|
||||
//we have all our variables - create the system object
|
||||
if(!sysName.empty() && !sysPath.empty() &&!sysExtension.empty() && !sysCommand.empty())
|
||||
SystemData* newSys = new SystemData(name, fullname, path, ext, cmd);
|
||||
if(newSys->getRootFolder()->getFileCount() == 0)
|
||||
{
|
||||
if(sysDescName.empty())
|
||||
sysDescName = sysName;
|
||||
|
||||
SystemData* newSystem = new SystemData(sysName, sysDescName, sysPath, sysExtension, sysCommand);
|
||||
if(newSystem->getRootFolder()->getFileCount() == 0)
|
||||
{
|
||||
LOG(LogWarning) << "System \"" << sysName << "\" has no games! Ignoring it.";
|
||||
delete newSystem;
|
||||
LOG(LogWarning) << "System \"" << name << "\" has no games! Ignoring it.";
|
||||
delete newSys;
|
||||
}else{
|
||||
sSystemVector.push_back(newSystem);
|
||||
sSystemVector.push_back(newSys);
|
||||
}
|
||||
}
|
||||
|
||||
//reset the variables for the next block (should there be one)
|
||||
sysName = ""; sysDescName = ""; sysPath = ""; sysExtension = ""; sysCommand = "" ;
|
||||
}
|
||||
}else{
|
||||
LOG(LogError) << "Error reading config file \"" << path << "\" - no equals sign found on line " << lineNr << ": \"" << line << "\"!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
LOG(LogError) << "Error - could not load config file \"" << path << "\"!";
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(LogInfo) << "Finished loading config file - created " << sSystemVector.size() << " systems.";
|
||||
return true;
|
||||
}
|
||||
|
||||
void SystemData::writeExampleConfig(const std::string& path)
|
||||
{
|
||||
std::cerr << "Writing example config to \"" << path << "\"...";
|
||||
|
||||
std::ofstream file(path.c_str());
|
||||
|
||||
file << "# This is the EmulationStation Systems configuration file." << std::endl;
|
||||
file << "# Lines that begin with a hash (#) are ignored, as are empty lines." << std::endl;
|
||||
file << "# A sample system might look like this:" << std::endl;
|
||||
file << "#NAME=nes" << std::endl;
|
||||
file << "#DESCNAME=Nintendo Entertainment System" << std::endl;
|
||||
file << "#PATH=~/ROMs/nes/" << std::endl;
|
||||
file << "#EXTENSION=.nes .NES" << std::endl;
|
||||
file << "#COMMAND=retroarch -L ~/cores/libretro-fceumm.so %ROM%" << std::endl << std::endl;
|
||||
|
||||
file << "#NAME is a short name used internally (and in alternative paths)." << std::endl;
|
||||
file << "#DESCNAME is a descriptive name to identify the system. It may be displayed in a header." << std::endl;
|
||||
file << "#PATH is the path to start the recursive search for ROMs in. ~ will be expanded into the $HOME variable." << std::endl;
|
||||
file << "#EXTENSION is a list of extensions to search for, separated by spaces. You MUST include the period, and it must be exact - it's case sensitive, and no wildcards." << std::endl;
|
||||
file << "#COMMAND is the shell command to execute when a game is selected. %ROM% will be replaced with the (bash special-character escaped) path to the ROM." << std::endl << std::endl;
|
||||
|
||||
file << "#Now try your own!" << std::endl;
|
||||
file << "NAME=" << std::endl;
|
||||
file << "DESCNAME=" << std::endl;
|
||||
file << "PATH=" << std::endl;
|
||||
file << "EXTENSION=" << std::endl;
|
||||
file << "COMMAND=" << std::endl;
|
||||
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. -->\n"
|
||||
" <name>NES</name>\n"
|
||||
"\n"
|
||||
" <!-- A \"pretty\" name, displayed in the header and such. -->\n"
|
||||
" <fullname>Nintendo Entertainment System</fullname>\n"
|
||||
"\n"
|
||||
" <!-- The path to start searching for ROMs in. '~' will be expanded to $HOME or $HOMEPATH, depending on platform. -->\n"
|
||||
" <path>~/roms/nes</path>\n"
|
||||
"\n"
|
||||
" <!-- A list of extensions to search for, delimited by a space. You MUST include the period! 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"
|
||||
" </system>\n"
|
||||
"</systemList>\n";
|
||||
|
||||
file.close();
|
||||
|
||||
std::cerr << "done. Go read it!\n";
|
||||
LOG(LogError) << "Example config written! Go read it at \"" << path << "\"!";
|
||||
}
|
||||
|
||||
void SystemData::deleteSystems()
|
||||
|
@ -329,7 +298,7 @@ std::string SystemData::getConfigPath()
|
|||
std::string home = getHomePath();
|
||||
if(home.empty())
|
||||
{
|
||||
LOG(LogError) << "$HOME environment variable empty or nonexistant!";
|
||||
LOG(LogError) << "Home path environment variable empty or nonexistant!";
|
||||
exit(1);
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -11,12 +11,12 @@ class GameData;
|
|||
class SystemData
|
||||
{
|
||||
public:
|
||||
SystemData(std::string name, std::string descName, std::string startPath, std::string extension, std::string command);
|
||||
SystemData(const std::string& name, const std::string& fullName, const std::string& startPath, const std::string& extension, const std::string& command);
|
||||
~SystemData();
|
||||
|
||||
FolderData* getRootFolder();
|
||||
std::string getName();
|
||||
std::string getDescName();
|
||||
std::string getFullName();
|
||||
std::string getStartPath();
|
||||
std::string getExtension();
|
||||
std::string getGamelistPath();
|
||||
|
@ -32,7 +32,7 @@ public:
|
|||
static std::vector<SystemData*> sSystemVector;
|
||||
private:
|
||||
std::string mName;
|
||||
std::string mDescName;
|
||||
std::string mFullName;
|
||||
std::string mStartPath;
|
||||
std::string mSearchExtension;
|
||||
std::string mLaunchCommand;
|
||||
|
|
|
@ -316,7 +316,7 @@ void GuiGameList::updateTheme()
|
|||
|
||||
if(!mTheme->getBool("hideHeader"))
|
||||
{
|
||||
mHeaderText.setText(mSystem->getDescName());
|
||||
mHeaderText.setText(mSystem->getFullName());
|
||||
}else{
|
||||
mHeaderText.setText("");
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ int main(int argc, char* argv[])
|
|||
//make sure it wasn't empty
|
||||
if(SystemData::sSystemVector.size() == 0)
|
||||
{
|
||||
LOG(LogError) << "No systems found! Does at least one system have a game present? (check that extensions match!)";
|
||||
LOG(LogError) << "No systems found! Does at least one system have a game present? (check that extensions match!)\n(Also, make sure you've updated your es_systems.cfg for XML!)";
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue