Added the ability to automatically generate the game systems directory structure.

This commit is contained in:
Leon Styhre 2021-03-10 18:21:49 +01:00
parent 5ff003186c
commit 2432e118a7
5 changed files with 311 additions and 110 deletions

View file

@ -220,7 +220,6 @@ std::vector<std::string> readList(const std::string& str, const std::string& del
return ret;
}
// Creates systems from information located in a config file.
bool SystemData::loadConfig()
{
deleteSystems();
@ -231,7 +230,7 @@ bool SystemData::loadConfig()
if (!Utils::FileSystem::exists(path)) {
LOG(LogInfo) << "Systems configuration file does not exist";
if (copyConfigTemplate(getConfigPath(true)))
return false;
return true;
path = getConfigPath(false);
}
@ -245,9 +244,9 @@ bool SystemData::loadConfig()
#endif
if (!res) {
LOG(LogError) << "Could not parse es_systems.cfg";
LOG(LogError) << "Couldn't parse es_systems.cfg";
LOG(LogError) << res.description();
return false;
return true;
}
// Actually read the file.
@ -255,7 +254,7 @@ bool SystemData::loadConfig()
if (!systemList) {
LOG(LogError) << "es_systems.cfg is missing the <systemList> tag";
return false;
return true;
}
for (pugi::xml_node system = systemList.child("system"); system;
@ -282,12 +281,12 @@ bool SystemData::loadConfig()
// Check that the ROM directory for the system is valid or otherwise abort the processing.
if (!Utils::FileSystem::exists(path)) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping game system \"" <<
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" <<
name << "\" as the defined ROM directory \"" << path << "\" does not exist";
continue;
}
if (!Utils::FileSystem::isDirectory(path)) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping game system \"" <<
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" <<
name << "\" as the defined ROM directory \"" << path <<
"\" is not actually a directory";
continue;
@ -297,7 +296,7 @@ bool SystemData::loadConfig()
// 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 game system \"" << name <<
LOG(LogWarning) << "Skipping system \"" << name <<
"\" as the defined ROM directory \"" << path <<
"\" is an infinitely recursive symlink";
continue;
@ -387,11 +386,9 @@ bool SystemData::loadConfig()
}
if (newSys->getRootFolder()->getChildrenByFilename().size() == 0 || onlyHidden) {
LOG(LogWarning) << "No files were found for game system \"" << name <<
"\" which matched any of the defined file extensions \"" <<
Utils::String::vectorToDelimitedString(extensions, " ") << "\"";
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" <<
name << "\" as no files matched any of the defined file extensions";
delete newSys;
delete envData;
}
else {
sSystemVector.push_back(newSys);
@ -407,7 +404,7 @@ bool SystemData::loadConfig()
if (sSystemVector.size() > 0)
CollectionSystemsManager::get()->loadCollectionSystems();
return true;
return false;
}
bool SystemData::copyConfigTemplate(const std::string& path)
@ -459,9 +456,166 @@ std::string SystemData::getConfigPath(bool forWrite)
return "";
}
bool SystemData::createSystemDirectories()
{
std::string path = getConfigPath(false);
const std::string rompath = FileData::getROMDirectory();
if (!Utils::FileSystem::exists(path)) {
LOG(LogInfo) << "Systems configuration file does not exist, aborting";
return true;
}
LOG(LogInfo) << "Generating ROM directory structure...";
if (Utils::FileSystem::exists(rompath) && Utils::FileSystem::isRegularFile(rompath)) {
LOG(LogError) <<
"Requested ROM directory \"" << rompath << "\" is actually a file, aborting";
return true;
}
if (!Utils::FileSystem::exists(rompath)) {
LOG(LogInfo) << "Creating base ROM directory \"" << rompath << "\"...";
if (!Utils::FileSystem::createDirectory(rompath)) {
LOG(LogError) << "Couldn't create directory, permission problems or disk full?";
return true;
}
}
else {
LOG(LogInfo) << "Base ROM directory \"" << rompath << "\" already exists";
}
LOG(LogInfo) << "Parsing systems configuration file \"" << path << "\"...";
pugi::xml_document doc;
#if defined(_WIN64)
pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str());
#else
pugi::xml_parse_result res = doc.load_file(path.c_str());
#endif
if (!res) {
LOG(LogError) << "Couldn't parse es_systems.cfg";
LOG(LogError) << res.description();
return true;
}
// Actually read the file.
pugi::xml_node systemList = doc.child("systemList");
if (!systemList) {
LOG(LogError) << "es_systems.cfg is missing the <systemList> tag";
return true;
}
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::string command;
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();
command = system.child("command").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);
#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;
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 << command << std::endl << 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();
if (replaceInfoFile) {
LOG(LogInfo) << "Replaced existing system information file \"" <<
rompath + systemDir + systemInfoFileName << "\"";
}
else {
LOG(LogInfo) << "Created system information file \"" <<
rompath + systemDir + systemInfoFileName << "\"";
}
}
LOG(LogInfo) << "System directories successfully created";
return false;
}
bool SystemData::isVisible()
{
// This function doesn't make much sense at the moment; if a game system does not have any
// This function doesn't make much sense at the moment; if a system does not have any
// games available, it will not be processed during startup and will as such not exist.
// In the future this function may be used for an option to hide specific systems, but
// for the time being all systems will always be visible.

View file

@ -68,13 +68,15 @@ public:
void setScrapeFlag(bool scrapeflag) { mScrapeFlag = scrapeflag; }
static void deleteSystems();
// Load the system config file at getConfigPath().
// Returns true if no errors were encountered.
// An example will be written if the file doesn't exist.
// Loads the systems configuration file at getConfigPath() and creates the systems.
static bool loadConfig();
static bool copyConfigTemplate(const std::string& path);
static std::string getConfigPath(bool forWrite);
// Generates the game system directories and information files based on es_systems.cfg.
static bool createSystemDirectories();
static std::vector<SystemData*> sSystemVector;
inline std::vector<SystemData*>::const_iterator getIterator() const

View file

@ -51,7 +51,7 @@
bool forceInputConfig = false;
bool settingsNeedSaving = false;
enum returnCode {
enum loadSystemsReturnCode {
NO_LOADING_ERROR,
NO_SYSTEMS_FILE,
NO_ROMS
@ -354,37 +354,16 @@ bool verifyHomeFolderExists()
return true;
}
// Returns NO_LOADING_ERROR if everything is OK.
// Otherwise returns either NO_SYSTEMS_FILE or NO_ROMS.
returnCode loadSystemConfigFile(std::string& errorMsg)
loadSystemsReturnCode loadSystemConfigFile()
{
if (!SystemData::loadConfig()) {
LOG(LogError) << "Could not parse systems configuration file";
errorMsg = "COULDN'T FIND THE SYSTEMS CONFIGURATION FILE.\n"
"ATTEMPTED TO COPY A TEMPLATE ES_SYSTEMS.CFG FILE\n"
"FROM THE EMULATIONSTATION RESOURCES DIRECTORY,\n"
"BUT THIS FAILED. HAS EMULATIONSTATION BEEN PROPERLY\n"
"INSTALLED AND DO YOU HAVE WRITE PERMISSIONS TO \n"
"YOUR HOME DIRECTORY?";
if (SystemData::loadConfig()) {
LOG(LogError) << "Could not parse systems configuration file (es_systems.cfg)";
return NO_SYSTEMS_FILE;
}
if (SystemData::sSystemVector.size() == 0) {
LOG(LogError) << "No systems found, does at least one system have a game present? "
"(Check that the file extensions are supported)";
errorMsg = "THE SYSTEMS CONFIGURATION FILE EXISTS, BUT NO\n"
"GAME FILES WERE FOUND. EITHER PLACE YOUR GAMES\n"
"IN THE CURRENTLY CONFIGURED ROM DIRECTORY OR\n"
"CHANGE IT USING THE BUTTON BELOW. MAKE SURE\n"
"THAT YOUR FILE EXTENSIONS AND SYSTEMS DIRECTORY\n"
"NAMES ARE SUPPORTED BY EMULATIONSTATION-DE.\n"
"THIS IS THE CURRENTLY CONFIGURED ROM DIRECTORY:\n";
#if defined(_WIN64)
errorMsg += Utils::String::replace(FileData::getROMDirectory(), "/", "\\");
#else
errorMsg += FileData::getROMDirectory();
#endif
LOG(LogError) << "No game files were found, make sure that the system directories are "
"setup correctly and that the file extensions are supported";
return NO_ROMS;
}
@ -493,72 +472,19 @@ int main(int argc, char* argv[])
window.renderLoadingScreen(progressText);
}
std::string errorMsg;
returnCode returnCodeValue = loadSystemConfigFile(errorMsg);
if (returnCodeValue) {
// Something went terribly wrong.
if (errorMsg == "") {
LOG(LogError) << "Unknown error occured while parsing systems configuration file";
Renderer::deinit();
return 1;
}
loadSystemsReturnCode loadSystemsStatus = loadSystemConfigFile();
if (loadSystemsStatus) {
// If there was an issue with installing the es_systems.cfg file from the
// template directory, then display an error message and let the user quit.
// If there are no game files found, give the option to the user to quit or
// to configure a different ROM directory. The application will need to be
// restarted though, to activate any new ROM directory setting.
if (returnCodeValue == NO_SYSTEMS_FILE) {
window.pushGui(new GuiMsgBox(&window, HelpStyle(),
errorMsg.c_str(),
"QUIT", [] {
SDL_Event quit;
quit.type = SDL_QUIT;
SDL_PushEvent(&quit);
}, "", nullptr, "", nullptr, true));
// to configure a different ROM directory as well as to generate the systems
// directory structure.
if (loadSystemsStatus == NO_SYSTEMS_FILE) {
ViewController::get()->noSystemsFileDialog();
}
else if (returnCodeValue == NO_ROMS) {
auto updateVal = [](const std::string& newROMDirectory) {
Settings::getInstance()->setString("ROMDirectory", newROMDirectory);
Settings::getInstance()->saveFile();
SDL_Event quit;
quit.type = SDL_QUIT;
SDL_PushEvent(&quit);
};
window.pushGui(new GuiMsgBox(&window, HelpStyle(), errorMsg.c_str(),
"CHANGE ROM DIRECTORY", [&window, updateVal] {
std::string currentROMDirectory;
#if defined(_WIN64)
currentROMDirectory =
Utils::String::replace(FileData::getROMDirectory(), "/", "\\");
#else
currentROMDirectory = FileData::getROMDirectory();
#endif
window.pushGui(new GuiComplexTextEditPopup(
&window,
HelpStyle(),
"ENTER ROM DIRECTORY",
"Currently configured directory:",
currentROMDirectory,
currentROMDirectory,
updateVal,
false,
"SAVE AND QUIT",
"SAVE CHANGES?",
"LOAD CURRENT",
"LOAD CURRENTLY CONFIGURED VALUE",
"CLEAR",
"CLEAR (LEAVE BLANK TO RESET TO DEFAULT DIRECTORY)",
true));
},
"QUIT", [] {
SDL_Event quit;
quit.type = SDL_QUIT;
SDL_PushEvent(&quit);
}, "", nullptr, true));
else if (loadSystemsStatus == NO_ROMS) {
ViewController::get()->noGamesDialog();
}
}
@ -575,7 +501,7 @@ int main(int argc, char* argv[])
// Choose which GUI to open depending on if an input configuration already exists and
// whether the flag to force the input configuration was passed from the command line.
if (errorMsg == "") {
if (!loadSystemsStatus) {
if (!forceInputConfig && Utils::FileSystem::exists(InputManager::getConfigPath()) &&
InputManager::getInstance()->getNumConfiguredDevices() > 0) {
ViewController::get()->goToStart();

View file

@ -4,9 +4,10 @@
// ViewController.cpp
//
// Handles overall system navigation including animations and transitions.
// Also creates the gamelist views and handles refresh and reloads of these when needed
// Creates the gamelist views and handles refresh and reloads of these when needed
// (for example when metadata has been changed or when a list sorting has taken place).
// Initiates the launching of games, calling FileData to do the actual launch.
// Displays a dialog when there are no games found on startup.
//
#include "views/ViewController.h"
@ -16,7 +17,6 @@
#include "animations/MoveCameraAnimation.h"
#include "guis/GuiInfoPopup.h"
#include "guis/GuiMenu.h"
#include "guis/GuiMsgBox.h"
#include "views/gamelist/DetailedGameListView.h"
#include "views/gamelist/GridGameListView.h"
#include "views/gamelist/IGameListView.h"
@ -72,7 +72,8 @@ ViewController::ViewController(
mCancelledTransition(false),
mLockInput(false),
mNextSystem(false),
mGameToLaunch(nullptr)
mGameToLaunch(nullptr),
mNoGamesMessageBox(nullptr)
{
mState.viewing = NOTHING;
mState.viewstyle = AUTOMATIC;
@ -84,6 +85,113 @@ ViewController::~ViewController()
sInstance = nullptr;
}
void ViewController::noSystemsFileDialog()
{
std::string errorMessage =
"COULDN'T FIND THE SYSTEMS CONFIGURATION FILE.\n"
"ATTEMPTED TO COPY A TEMPLATE es_systems.cfg FILE\n"
"FROM THE EMULATIONSTATION RESOURCES DIRECTORY,\n"
"BUT THIS FAILED. HAS EMULATIONSTATION BEEN PROPERLY\n"
"INSTALLED AND DO YOU HAVE WRITE PERMISSIONS TO \n"
"YOUR HOME DIRECTORY?";
mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(),
errorMessage.c_str(),
"QUIT", [] {
SDL_Event quit;
quit.type = SDL_QUIT;
SDL_PushEvent(&quit);
}, "", nullptr, "", nullptr, true));
}
void ViewController::noGamesDialog()
{
mNoGamesErrorMessage =
"THE SYSTEMS CONFIGURATION FILE EXISTS, BUT NO\n"
"GAME FILES WERE FOUND. EITHER PLACE YOUR GAMES\n"
"IN THE CURRENTLY CONFIGURED ROM DIRECTORY OR\n"
"CHANGE IT USING THE BUTTON BELOW. OPTIONALLY THE\n"
"ROM DIRECTORY STRUCTURE CAN BE GENERATED WHICH\n"
"WILL CREATE A TEXT FILE IN EACH FOLDER PROVIDING\n"
"SOME INFO SUCH AS THE SUPPORTED FILE EXTENSIONS.\n"
"THIS IS THE CURRENTLY CONFIGURED ROM DIRECTORY:\n";
#if defined(_WIN64)
mRomDirectory = Utils::String::replace(FileData::getROMDirectory(), "/", "\\");
#else
mRomDirectory = FileData::getROMDirectory();
#endif
mNoGamesMessageBox = new GuiMsgBox(mWindow, HelpStyle(), mNoGamesErrorMessage + mRomDirectory,
"CHANGE ROM DIRECTORY", [this] {
std::string currentROMDirectory;
#if defined(_WIN64)
currentROMDirectory = Utils::String::replace(FileData::getROMDirectory(), "/", "\\");
#else
currentROMDirectory = FileData::getROMDirectory();
#endif
mWindow->pushGui(new GuiComplexTextEditPopup(
mWindow,
HelpStyle(),
"ENTER ROM DIRECTORY",
"Currently configured directory:",
currentROMDirectory,
currentROMDirectory,
[this](const std::string& newROMDirectory) {
Settings::getInstance()->setString("ROMDirectory", newROMDirectory);
Settings::getInstance()->saveFile();
#if defined(_WIN64)
mRomDirectory = Utils::String::replace(FileData::getROMDirectory(), "/", "\\");
#else
mRomDirectory = FileData::getROMDirectory();
#endif
mNoGamesMessageBox->changeText(mNoGamesErrorMessage + mRomDirectory);
mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(),
"ROM DIRECTORY SAVED, RESTART THE\n"
"APPLICATION TO RESCAN THE SYSTEMS",
"OK", nullptr, "", nullptr, "", nullptr, true));
},
false,
"SAVE",
"SAVE CHANGES?",
"LOAD CURRENT",
"LOAD CURRENTLY CONFIGURED VALUE",
"CLEAR",
"CLEAR (LEAVE BLANK TO RESET TO DEFAULT DIRECTORY)",
false));
},
"CREATE DIRECTORIES", [this] {
mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(),
"THIS WILL CREATE DIRECTORIES FOR ALL THE\n"
"GAME SYSTEMS DEFINED IN es_systems.cfg\n\n"
"THIS MAY CREATE A LOT OF FOLDERS SO IT'S\n"
"ADVICED TO DELETE THE ONES YOU DON'T NEED\n\n"
"PROCEED?",
"YES", [this] {
if (!SystemData::createSystemDirectories()) {
mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(),
"THE SYSTEM DIRECTORIES WERE SUCCESSFULLY CREATED ", "OK", nullptr,
"", nullptr, "", nullptr, true));
}
else {
mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(),
"ERROR CREATING THE SYSTEM DIRECTORIES,\n"
"PERMISSION PROBLEMS OR DISK FULL?\n\n"
"SEE THE LOG FILE FOR MORE DETAILS", "OK", nullptr,
"", nullptr, "", nullptr, true));
}
}, "NO", nullptr, "", nullptr, true));
},
"QUIT", [] {
SDL_Event quit;
quit.type = SDL_QUIT;
SDL_PushEvent(&quit);
}, true, false);
mWindow->pushGui(mNoGamesMessageBox);
}
void ViewController::goToStart()
{
// Check if the keyboard config is set as application default, meaning no user

View file

@ -4,14 +4,17 @@
// ViewController.h
//
// Handles overall system navigation including animations and transitions.
// Also creates the gamelist views and handles refresh and reloads of these when needed
// Creates the gamelist views and handles refresh and reloads of these when needed
// (for example when metadata has been changed or when a list sorting has taken place).
// Initiates the launching of games, calling FileData to do the actual launch.
// Displays a dialog when there are no games found on startup.
//
#ifndef ES_APP_VIEWS_VIEW_CONTROLLER_H
#define ES_APP_VIEWS_VIEW_CONTROLLER_H
#include "guis/GuiComplexTextEditPopup.h"
#include "guis/GuiMsgBox.h"
#include "renderers/Renderer.h"
#include "FileData.h"
#include "GuiComponent.h"
@ -32,6 +35,10 @@ public:
virtual ~ViewController();
// These functions are called from main().
void noSystemsFileDialog();
void noGamesDialog();
// Try to completely populate the GameListView map.
// Caches things so there's no pauses during transitions.
void preload();
@ -118,6 +125,10 @@ private:
void launch(FileData* game);
std::string mNoGamesErrorMessage;
std::string mRomDirectory;
GuiMsgBox* mNoGamesMessageBox;
void playViewTransition(bool instant = false);
int getSystemId(SystemData* system);
// Restore view position if it was moved during wrap around.