2012-07-20 16:14:09 +00:00
|
|
|
#include "SystemData.h"
|
|
|
|
#include "GameData.h"
|
2012-08-08 00:50:45 +00:00
|
|
|
#include "XMLReader.h"
|
2012-07-20 16:14:09 +00:00
|
|
|
#include <boost/filesystem.hpp>
|
2012-07-21 19:06:24 +00:00
|
|
|
#include <fstream>
|
|
|
|
#include <stdlib.h>
|
2013-05-13 19:53:28 +00:00
|
|
|
#include <SDL_joystick.h>
|
2012-08-29 18:53:53 +00:00
|
|
|
#include "Renderer.h"
|
2012-10-13 18:29:53 +00:00
|
|
|
#include "AudioManager.h"
|
2013-05-22 17:11:10 +00:00
|
|
|
#include "VolumeControl.h"
|
2013-01-04 23:31:51 +00:00
|
|
|
#include "Log.h"
|
2013-01-08 02:24:59 +00:00
|
|
|
#include "InputManager.h"
|
2013-04-13 18:52:32 +00:00
|
|
|
#include <iostream>
|
2013-06-17 19:01:03 +00:00
|
|
|
#include "Settings.h"
|
2012-07-20 16:14:09 +00:00
|
|
|
|
2012-07-21 20:57:53 +00:00
|
|
|
std::vector<SystemData*> SystemData::sSystemVector;
|
|
|
|
|
2012-07-20 16:14:09 +00:00
|
|
|
namespace fs = boost::filesystem;
|
|
|
|
|
2012-08-08 00:50:45 +00:00
|
|
|
std::string SystemData::getStartPath() { return mStartPath; }
|
|
|
|
std::string SystemData::getExtension() { return mSearchExtension; }
|
|
|
|
|
2013-08-13 06:56:10 +00:00
|
|
|
SystemData::SystemData(const std::string& name, const std::string& fullName, const std::string& startPath, const std::string& extension, const std::string& command)
|
2012-07-20 16:14:09 +00:00
|
|
|
{
|
|
|
|
mName = name;
|
2013-08-13 06:56:10 +00:00
|
|
|
mFullName = fullName;
|
|
|
|
mStartPath = startPath;
|
2012-07-27 16:58:27 +00:00
|
|
|
|
2013-01-04 23:31:51 +00:00
|
|
|
//expand home symbol if the startpath contains ~
|
2013-08-13 06:56:10 +00:00
|
|
|
if(mStartPath[0] == '~')
|
2013-01-04 23:31:51 +00:00
|
|
|
{
|
2013-08-13 06:56:10 +00:00
|
|
|
mStartPath.erase(0, 1);
|
|
|
|
mStartPath.insert(0, getHomePath());
|
|
|
|
}
|
2012-07-27 16:58:27 +00:00
|
|
|
|
2012-07-20 16:14:09 +00:00
|
|
|
mSearchExtension = extension;
|
2012-07-21 19:06:24 +00:00
|
|
|
mLaunchCommand = command;
|
2012-07-27 16:58:27 +00:00
|
|
|
|
|
|
|
mRootFolder = new FolderData(this, mStartPath, "Search Root");
|
2012-08-08 00:50:45 +00:00
|
|
|
|
2013-06-17 19:01:03 +00:00
|
|
|
if(!Settings::getInstance()->getBool("PARSEGAMELISTONLY"))
|
2012-08-08 00:50:45 +00:00
|
|
|
populateFolder(mRootFolder);
|
|
|
|
|
2013-06-17 19:01:03 +00:00
|
|
|
if(!Settings::getInstance()->getBool("IGNOREGAMELIST"))
|
2012-08-11 04:17:52 +00:00
|
|
|
parseGamelist(this);
|
2012-08-08 00:50:45 +00:00
|
|
|
|
|
|
|
mRootFolder->sort();
|
2012-07-20 16:14:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SystemData::~SystemData()
|
2012-07-21 19:06:24 +00:00
|
|
|
{
|
2013-06-28 12:54:14 +00:00
|
|
|
//save changed game data back to xml
|
|
|
|
if(!Settings::getInstance()->getBool("IGNOREGAMELIST")) {
|
|
|
|
updateGamelist(this);
|
|
|
|
}
|
2012-07-27 16:58:27 +00:00
|
|
|
delete mRootFolder;
|
2012-07-21 19:06:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string strreplace(std::string& str, std::string replace, std::string with)
|
|
|
|
{
|
|
|
|
size_t pos = str.find(replace);
|
|
|
|
|
2012-09-23 21:01:56 +00:00
|
|
|
if(pos != std::string::npos)
|
|
|
|
return str.replace(pos, replace.length(), with.c_str(), with.length());
|
|
|
|
else
|
|
|
|
return str;
|
2012-07-21 19:06:24 +00:00
|
|
|
}
|
|
|
|
|
2013-04-08 16:52:40 +00:00
|
|
|
void SystemData::launchGame(Window* window, GameData* game)
|
2012-07-21 19:06:24 +00:00
|
|
|
{
|
2013-01-04 23:31:51 +00:00
|
|
|
LOG(LogInfo) << "Attempting to launch game...";
|
2012-07-21 19:06:24 +00:00
|
|
|
|
2013-05-21 08:40:01 +00:00
|
|
|
AudioManager::getInstance()->deinit();
|
2013-05-22 17:11:10 +00:00
|
|
|
VolumeControl::getInstance()->deinit();
|
2013-04-10 17:29:07 +00:00
|
|
|
window->deinit();
|
2012-08-29 18:53:53 +00:00
|
|
|
|
2012-07-21 19:06:24 +00:00
|
|
|
std::string command = mLaunchCommand;
|
|
|
|
|
2012-08-02 01:43:55 +00:00
|
|
|
command = strreplace(command, "%ROM%", game->getBashPath());
|
2012-12-14 20:42:11 +00:00
|
|
|
command = strreplace(command, "%BASENAME%", game->getBaseName());
|
2013-05-27 17:13:38 +00:00
|
|
|
command = strreplace(command, "%ROM_RAW%", game->getPath());
|
2012-07-21 19:06:24 +00:00
|
|
|
|
2013-01-04 23:31:51 +00:00
|
|
|
LOG(LogInfo) << " " << command;
|
2013-04-13 18:52:32 +00:00
|
|
|
std::cout << "==============================================\n";
|
2012-10-28 23:07:05 +00:00
|
|
|
int exitCode = system(command.c_str());
|
2013-04-13 18:52:32 +00:00
|
|
|
std::cout << "==============================================\n";
|
2012-07-21 19:06:24 +00:00
|
|
|
|
2012-10-28 23:07:05 +00:00
|
|
|
if(exitCode != 0)
|
2013-01-04 23:31:51 +00:00
|
|
|
{
|
|
|
|
LOG(LogWarning) << "...launch terminated with nonzero exit code " << exitCode << "!";
|
|
|
|
}
|
2012-07-23 23:53:33 +00:00
|
|
|
|
2013-04-10 17:29:07 +00:00
|
|
|
window->init();
|
2013-05-22 17:11:10 +00:00
|
|
|
VolumeControl::getInstance()->init();
|
2013-05-21 08:40:01 +00:00
|
|
|
AudioManager::getInstance()->init();
|
2013-07-17 06:47:02 +00:00
|
|
|
window->normalizeNextUpdate();
|
2013-06-28 12:54:14 +00:00
|
|
|
|
2013-06-28 15:25:18 +00:00
|
|
|
//update number of times the game has been launched and the time
|
2013-08-14 12:16:49 +00:00
|
|
|
game->incTimesPlayed();
|
|
|
|
game->lastPlayedNow();
|
2012-07-21 19:06:24 +00:00
|
|
|
}
|
|
|
|
|
2012-07-27 16:58:27 +00:00
|
|
|
void SystemData::populateFolder(FolderData* folder)
|
2012-07-20 16:14:09 +00:00
|
|
|
{
|
2012-07-27 16:58:27 +00:00
|
|
|
std::string folderPath = folder->getPath();
|
|
|
|
if(!fs::is_directory(folderPath))
|
2012-07-20 16:14:09 +00:00
|
|
|
{
|
2013-04-13 18:52:32 +00:00
|
|
|
LOG(LogWarning) << "Error - folder with path \"" << folderPath << "\" is not a directory!";
|
2012-07-20 16:14:09 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-04-13 21:33:18 +00:00
|
|
|
//make sure that this isn't a symlink to a thing we already have
|
|
|
|
if(fs::is_symlink(folderPath))
|
|
|
|
{
|
2013-04-13 22:30:57 +00:00
|
|
|
//if this symlink resolves to somewhere that's at the beginning of our path, it's gonna recurse
|
|
|
|
if(folderPath.find(fs::canonical(folderPath).string()) == 0)
|
2013-04-13 21:33:18 +00:00
|
|
|
{
|
2013-04-13 22:30:57 +00:00
|
|
|
LOG(LogWarning) << "Skipping infinitely recursive symlink \"" << folderPath << "\"";
|
2013-04-13 21:33:18 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-27 16:58:27 +00:00
|
|
|
for(fs::directory_iterator end, dir(folderPath); dir != end; ++dir)
|
2012-07-20 16:14:09 +00:00
|
|
|
{
|
2012-07-27 16:58:27 +00:00
|
|
|
fs::path filePath = (*dir).path();
|
2012-07-20 16:14:09 +00:00
|
|
|
|
2012-08-11 01:29:51 +00:00
|
|
|
if(filePath.stem().string().empty())
|
|
|
|
continue;
|
|
|
|
|
2013-05-27 17:13:38 +00:00
|
|
|
//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:
|
|
|
|
std::string extension = filePath.extension().string();
|
|
|
|
std::string chkExt;
|
|
|
|
size_t extPos = 0;
|
|
|
|
|
|
|
|
//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
|
|
|
|
bool isGame = false;
|
|
|
|
do {
|
|
|
|
//now we loop through every extension in the list
|
|
|
|
size_t cpos = extPos;
|
|
|
|
extPos = mSearchExtension.find(" ", extPos);
|
|
|
|
chkExt = mSearchExtension.substr(cpos, ((extPos == std::string::npos) ? mSearchExtension.length() - cpos: extPos - cpos));
|
|
|
|
|
|
|
|
//if it matches, add it
|
|
|
|
if(chkExt == extension)
|
|
|
|
{
|
2013-08-14 12:16:49 +00:00
|
|
|
GameData* newGame = new GameData(this, filePath.generic_string());
|
2013-05-27 17:13:38 +00:00
|
|
|
folder->pushFileData(newGame);
|
|
|
|
isGame = true;
|
|
|
|
break;
|
|
|
|
}else if(extPos != std::string::npos) //if not, add one to the "next position" marker to skip the space when reading the next extension
|
|
|
|
{
|
|
|
|
extPos++;
|
|
|
|
}
|
|
|
|
|
|
|
|
} while(extPos != std::string::npos && chkExt != "" && chkExt.find(".") != std::string::npos);
|
|
|
|
|
|
|
|
//add directories that also do not match an extension as folders
|
|
|
|
if(!isGame && fs::is_directory(filePath))
|
2012-07-20 16:14:09 +00:00
|
|
|
{
|
2013-07-03 12:27:06 +00:00
|
|
|
FolderData* newFolder = new FolderData(this, filePath.generic_string(), filePath.stem().string());
|
2012-07-27 16:58:27 +00:00
|
|
|
populateFolder(newFolder);
|
2012-08-11 04:17:52 +00:00
|
|
|
|
|
|
|
//ignore folders that do not contain games
|
|
|
|
if(newFolder->getFileCount() == 0)
|
|
|
|
delete newFolder;
|
|
|
|
else
|
|
|
|
folder->pushFileData(newFolder);
|
2012-07-20 16:14:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string SystemData::getName()
|
|
|
|
{
|
|
|
|
return mName;
|
|
|
|
}
|
2012-07-21 19:06:24 +00:00
|
|
|
|
2013-08-13 06:56:10 +00:00
|
|
|
std::string SystemData::getFullName()
|
2012-12-17 19:29:43 +00:00
|
|
|
{
|
2013-08-13 06:56:10 +00:00
|
|
|
if(mFullName.empty())
|
|
|
|
return mName;
|
|
|
|
else
|
|
|
|
return mFullName;
|
2012-12-17 19:29:43 +00:00
|
|
|
}
|
2012-07-21 20:57:53 +00:00
|
|
|
|
|
|
|
//creates systems from information located in a config file
|
2013-07-09 10:37:37 +00:00
|
|
|
bool SystemData::loadConfig(const std::string& path, bool writeExample)
|
2012-07-21 19:06:24 +00:00
|
|
|
{
|
2012-07-21 20:57:53 +00:00
|
|
|
deleteSystems();
|
2012-07-21 19:06:24 +00:00
|
|
|
|
2013-08-13 06:56:10 +00:00
|
|
|
LOG(LogInfo) << "Loading system config file " << path << "...";
|
2012-07-21 19:06:24 +00:00
|
|
|
|
2013-07-09 10:37:37 +00:00
|
|
|
if(!fs::exists(path))
|
|
|
|
{
|
2013-08-13 06:56:10 +00:00
|
|
|
LOG(LogError) << "File does not exist!";
|
|
|
|
|
2013-07-09 10:37:37 +00:00
|
|
|
if(writeExample)
|
|
|
|
writeExampleConfig(path);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-08-13 06:56:10 +00:00
|
|
|
pugi::xml_document doc;
|
|
|
|
pugi::xml_parse_result res = doc.load_file(path.c_str());
|
|
|
|
|
|
|
|
if(!res)
|
|
|
|
{
|
|
|
|
LOG(LogError) << "Could not parse config file!";
|
|
|
|
LOG(LogError) << res.description();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//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"))
|
2012-07-21 19:06:24 +00:00
|
|
|
{
|
2013-08-13 06:56:10 +00:00
|
|
|
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())
|
2012-07-21 19:06:24 +00:00
|
|
|
{
|
2013-08-13 06:56:10 +00:00
|
|
|
LOG(LogError) << "System \"" << name << "\" is missing name, path, extension, or command!";
|
|
|
|
continue;
|
|
|
|
}
|
2012-07-21 19:06:24 +00:00
|
|
|
|
2013-08-13 06:56:10 +00:00
|
|
|
//convert path to generic directory seperators
|
|
|
|
boost::filesystem::path genericPath(path);
|
|
|
|
path = genericPath.generic_string();
|
|
|
|
|
|
|
|
SystemData* newSys = new SystemData(name, fullname, path, ext, cmd);
|
|
|
|
if(newSys->getRootFolder()->getFileCount() == 0)
|
|
|
|
{
|
|
|
|
LOG(LogWarning) << "System \"" << name << "\" has no games! Ignoring it.";
|
|
|
|
delete newSys;
|
|
|
|
}else{
|
|
|
|
sSystemVector.push_back(newSys);
|
2012-07-21 19:06:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-09 10:37:37 +00:00
|
|
|
return true;
|
2012-07-21 20:57:53 +00:00
|
|
|
}
|
|
|
|
|
2013-07-09 10:37:37 +00:00
|
|
|
void SystemData::writeExampleConfig(const std::string& path)
|
2012-07-23 23:53:33 +00:00
|
|
|
{
|
|
|
|
std::ofstream file(path.c_str());
|
|
|
|
|
2013-08-13 06:56:10 +00:00
|
|
|
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"
|
2013-08-28 19:39:29 +00:00
|
|
|
" <!-- A short name, used internally. Traditionally lower-case. -->\n"
|
|
|
|
" <name>nes</name>\n"
|
2013-08-13 06:56:10 +00:00
|
|
|
"\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";
|
2012-07-23 23:53:33 +00:00
|
|
|
|
|
|
|
file.close();
|
2013-07-09 10:37:37 +00:00
|
|
|
|
2013-08-13 06:56:10 +00:00
|
|
|
LOG(LogError) << "Example config written! Go read it at \"" << path << "\"!";
|
2012-07-23 23:53:33 +00:00
|
|
|
}
|
|
|
|
|
2012-07-21 20:57:53 +00:00
|
|
|
void SystemData::deleteSystems()
|
|
|
|
{
|
|
|
|
for(unsigned int i = 0; i < sSystemVector.size(); i++)
|
|
|
|
{
|
|
|
|
delete sSystemVector.at(i);
|
|
|
|
}
|
|
|
|
sSystemVector.clear();
|
2012-07-21 19:06:24 +00:00
|
|
|
}
|
2012-07-23 23:53:33 +00:00
|
|
|
|
|
|
|
std::string SystemData::getConfigPath()
|
|
|
|
{
|
2013-05-13 19:53:28 +00:00
|
|
|
std::string home = getHomePath();
|
2012-07-23 23:53:33 +00:00
|
|
|
if(home.empty())
|
|
|
|
{
|
2013-08-13 06:56:10 +00:00
|
|
|
LOG(LogError) << "Home path environment variable empty or nonexistant!";
|
2012-07-23 23:53:33 +00:00
|
|
|
exit(1);
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2012-08-02 02:37:07 +00:00
|
|
|
return(home + "/.emulationstation/es_systems.cfg");
|
2012-07-23 23:53:33 +00:00
|
|
|
}
|
2012-07-27 16:58:27 +00:00
|
|
|
|
|
|
|
FolderData* SystemData::getRootFolder()
|
|
|
|
{
|
|
|
|
return mRootFolder;
|
|
|
|
}
|
2012-08-08 00:50:45 +00:00
|
|
|
|
2013-06-17 19:01:03 +00:00
|
|
|
std::string SystemData::getGamelistPath()
|
|
|
|
{
|
2012-12-17 19:29:43 +00:00
|
|
|
std::string filePath;
|
|
|
|
|
|
|
|
filePath = mRootFolder->getPath() + "/gamelist.xml";
|
|
|
|
if(fs::exists(filePath))
|
|
|
|
return filePath;
|
|
|
|
|
2013-05-13 19:53:28 +00:00
|
|
|
filePath = getHomePath();
|
2012-12-17 19:29:43 +00:00
|
|
|
filePath += "/.emulationstation/"+ getName() + "/gamelist.xml";
|
|
|
|
if(fs::exists(filePath))
|
|
|
|
return filePath;
|
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2012-08-08 00:50:45 +00:00
|
|
|
bool SystemData::hasGamelist()
|
|
|
|
{
|
2012-12-17 19:29:43 +00:00
|
|
|
if(getGamelistPath().empty())
|
|
|
|
return false;
|
|
|
|
else
|
|
|
|
return true;
|
2012-08-08 00:50:45 +00:00
|
|
|
}
|