2020-09-17 20:00:07 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-05-24 08:29:29 +00:00
|
|
|
//
|
2020-09-17 20:00:07 +00:00
|
|
|
// EmulationStation Desktop Edition
|
2020-06-21 12:25:28 +00:00
|
|
|
// FileData.cpp
|
2020-05-24 08:29:29 +00:00
|
|
|
//
|
2020-06-21 12:25:28 +00:00
|
|
|
// Provides game file data structures and functions to access and sort this information.
|
|
|
|
// Also provides functions to look up paths to media files and for launching games
|
2020-08-19 20:17:32 +00:00
|
|
|
// (launching initiated in ViewController).
|
2020-05-24 08:29:29 +00:00
|
|
|
//
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
#include "FileData.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
|
2020-07-18 11:21:44 +00:00
|
|
|
#include "guis/GuiInfoPopup.h"
|
2018-01-09 22:55:09 +00:00
|
|
|
#include "utils/FileSystemUtil.h"
|
2017-11-29 19:57:43 +00:00
|
|
|
#include "utils/StringUtil.h"
|
2017-11-22 21:01:12 +00:00
|
|
|
#include "utils/TimeUtil.h"
|
2021-01-05 09:45:32 +00:00
|
|
|
#include "views/UIModeController.h"
|
2021-03-27 12:49:09 +00:00
|
|
|
#include "views/ViewController.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "AudioManager.h"
|
2020-12-23 17:06:30 +00:00
|
|
|
#include "CollectionSystemsManager.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "FileFilterIndex.h"
|
2017-06-12 16:38:59 +00:00
|
|
|
#include "FileSorts.h"
|
|
|
|
#include "Log.h"
|
2018-02-09 17:23:58 +00:00
|
|
|
#include "MameNames.h"
|
2020-06-21 10:26:21 +00:00
|
|
|
#include "Platform.h"
|
2018-01-30 00:49:08 +00:00
|
|
|
#include "Scripting.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "SystemData.h"
|
|
|
|
#include "Window.h"
|
2020-07-07 19:25:15 +00:00
|
|
|
|
2018-01-29 22:50:10 +00:00
|
|
|
#include <assert.h>
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-05-24 08:29:29 +00:00
|
|
|
FileData::FileData(
|
2020-06-21 12:25:28 +00:00
|
|
|
FileType type,
|
|
|
|
const std::string& path,
|
|
|
|
SystemEnvironmentData* envData,
|
|
|
|
SystemData* system)
|
|
|
|
: mType(type),
|
|
|
|
mPath(path),
|
|
|
|
mSystem(system),
|
|
|
|
mEnvData(envData),
|
|
|
|
mSourceFileData(nullptr),
|
|
|
|
mParent(nullptr),
|
2020-07-28 17:44:17 +00:00
|
|
|
mOnlyFolders(false),
|
2020-07-13 18:13:48 +00:00
|
|
|
mDeletionFlag(false),
|
2020-09-17 20:00:07 +00:00
|
|
|
// Metadata is set in the constructor.
|
2020-06-21 12:25:28 +00:00
|
|
|
metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA)
|
|
|
|
{
|
|
|
|
// Metadata needs at least a name field (since that's what getName() will return).
|
|
|
|
if (metadata.get("name").empty()) {
|
|
|
|
if ((system->hasPlatformId(PlatformIds::ARCADE) ||
|
2021-02-07 19:49:52 +00:00
|
|
|
system->hasPlatformId(PlatformIds::SNK_NEO_GEO)) &&
|
2020-06-21 12:25:28 +00:00
|
|
|
metadata.getType() != FOLDER_METADATA) {
|
|
|
|
// If it's a MAME or Neo Geo game, expand the game name accordingly.
|
|
|
|
metadata.set("name",
|
|
|
|
MameNames::getInstance()->getCleanName(getCleanName()));
|
|
|
|
}
|
|
|
|
else {
|
2020-07-26 13:21:41 +00:00
|
|
|
if (metadata.getType() == FOLDER_METADATA && Utils::FileSystem::isHidden(mPath)) {
|
|
|
|
metadata.set("name", Utils::FileSystem::getFileName(mPath));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
metadata.set("name", getDisplayName());
|
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
mSystemName = system->getName();
|
|
|
|
metadata.resetChangedFlag();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FileData::~FileData()
|
|
|
|
{
|
2020-10-17 15:35:50 +00:00
|
|
|
while (mChildren.size() > 0)
|
|
|
|
delete (mChildren.front());
|
2020-10-18 17:14:34 +00:00
|
|
|
|
|
|
|
if (mParent)
|
|
|
|
mParent->removeChild(this);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2016-03-29 15:33:19 +00:00
|
|
|
std::string FileData::getDisplayName() const
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
std::string stem = Utils::FileSystem::getStem(mPath);
|
|
|
|
return stem;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2016-03-29 15:33:19 +00:00
|
|
|
std::string FileData::getCleanName() const
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
return Utils::String::removeParenthesis(this->getDisplayName());
|
2016-03-29 15:33:19 +00:00
|
|
|
}
|
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
const std::string& FileData::getName()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
return metadata.get("name");
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 05:07:25 +00:00
|
|
|
const std::string& FileData::getSortName()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (metadata.get("sortname").empty())
|
|
|
|
return metadata.get("name");
|
|
|
|
else
|
|
|
|
return metadata.get("sortname");
|
2018-04-25 05:07:25 +00:00
|
|
|
}
|
|
|
|
|
2020-05-15 15:42:36 +00:00
|
|
|
const bool FileData::getFavorite()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (metadata.get("favorite") == "true")
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
2020-05-15 15:42:36 +00:00
|
|
|
}
|
|
|
|
|
2021-01-05 09:45:32 +00:00
|
|
|
const bool FileData::getKidgame()
|
|
|
|
{
|
|
|
|
if (metadata.get("kidgame") == "true")
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-26 20:19:29 +00:00
|
|
|
const bool FileData::getHidden()
|
|
|
|
{
|
|
|
|
if (metadata.get("hidden") == "true")
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-29 17:01:49 +00:00
|
|
|
const bool FileData::getCountAsGame()
|
|
|
|
{
|
2020-08-06 09:27:16 +00:00
|
|
|
if (metadata.get("nogamecount") == "true")
|
|
|
|
return false;
|
|
|
|
else
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const bool FileData::getExcludeFromScraper()
|
|
|
|
{
|
|
|
|
if (metadata.get("nomultiscrape") == "true")
|
2020-07-29 17:01:49 +00:00
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:19:54 +00:00
|
|
|
const std::vector<FileData*> FileData::getChildrenRecursive() const
|
2020-07-26 20:19:29 +00:00
|
|
|
{
|
|
|
|
std::vector<FileData*> childrenRecursive;
|
|
|
|
|
|
|
|
for (auto it = mChildrenByFilename.cbegin();
|
|
|
|
it != mChildrenByFilename.cend(); it++) {
|
|
|
|
childrenRecursive.push_back((*it).second);
|
|
|
|
// Recurse through any subdirectories.
|
|
|
|
if ((*it).second->getType() == FOLDER) {
|
2020-07-28 13:19:54 +00:00
|
|
|
std::vector<FileData*> childrenSubdirectory = (*it).second->getChildrenRecursive();
|
2020-07-26 20:19:29 +00:00
|
|
|
childrenRecursive.insert(childrenRecursive.end(),
|
|
|
|
childrenSubdirectory.begin(), childrenSubdirectory.end());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return childrenRecursive;
|
|
|
|
}
|
|
|
|
|
2020-06-18 15:09:32 +00:00
|
|
|
const std::string FileData::getROMDirectory()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
std::string romDirSetting = Settings::getInstance()->getString("ROMDirectory");
|
|
|
|
std::string romDirPath = "";
|
2020-06-18 15:09:32 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (romDirSetting == "") {
|
|
|
|
romDirPath = Utils::FileSystem::getHomePath() + "/ROMs/";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
romDirPath = romDirSetting;
|
2020-07-10 18:58:53 +00:00
|
|
|
// Expand home path if ~ is used.
|
|
|
|
romDirPath = Utils::FileSystem::expandHomePath(romDirPath);
|
2020-06-18 15:09:32 +00:00
|
|
|
|
2021-03-10 18:49:06 +00:00
|
|
|
#if defined(_WIN64)
|
|
|
|
if (romDirPath.back() != '\\')
|
|
|
|
romDirPath = romDirPath + "\\";
|
|
|
|
#else
|
2020-06-21 12:25:28 +00:00
|
|
|
if (romDirPath.back() != '/')
|
|
|
|
romDirPath = romDirPath + "/";
|
2021-03-10 18:49:06 +00:00
|
|
|
#endif
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2020-06-18 15:09:32 +00:00
|
|
|
|
2020-07-10 19:20:52 +00:00
|
|
|
// If %ESPATH% is used for the ROM path configuration, then expand it to the executable
|
|
|
|
// directory of ES. This is useful for a portable emulator installation, for instance on
|
|
|
|
// a USB memory stick.
|
|
|
|
romDirPath = Utils::String::replace(romDirPath, "%ESPATH%", Utils::FileSystem::getExePath());
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return romDirPath;
|
2020-06-18 15:09:32 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 11:10:33 +00:00
|
|
|
const std::string FileData::getMediaDirectory()
|
2020-05-18 17:00:43 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
std::string mediaDirSetting = Settings::getInstance()->getString("MediaDirectory");
|
|
|
|
std::string mediaDirPath = "";
|
2017-05-18 10:16:57 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mediaDirSetting == "") {
|
|
|
|
mediaDirPath = Utils::FileSystem::getHomePath() + "/.emulationstation/downloaded_media/";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mediaDirPath = mediaDirSetting;
|
2020-07-10 19:20:52 +00:00
|
|
|
// Expand home path if ~ is used.
|
|
|
|
mediaDirPath = Utils::FileSystem::expandHomePath(mediaDirPath);
|
2020-06-21 12:25:28 +00:00
|
|
|
// Expand home symbol if the path starts with ~
|
2020-07-10 19:20:52 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mediaDirPath.back() != '/')
|
|
|
|
mediaDirPath = mediaDirPath + "/";
|
|
|
|
}
|
2020-05-18 17:00:43 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return mediaDirPath;
|
2020-05-18 17:00:43 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 11:10:33 +00:00
|
|
|
const std::string FileData::getMediafilePath(std::string subdirectory, std::string mediatype) const
|
2020-05-18 17:00:43 +00:00
|
|
|
{
|
2021-01-31 18:53:55 +00:00
|
|
|
const std::vector<std::string> extList = { ".png", ".jpg" };
|
|
|
|
std::string subFolders;
|
|
|
|
|
|
|
|
// Extract possible subfolders from the path.
|
|
|
|
if (mEnvData->mStartPath != "")
|
|
|
|
subFolders = Utils::String::replace(
|
|
|
|
Utils::FileSystem::getParent(mPath), mEnvData->mStartPath, "");
|
|
|
|
|
|
|
|
const std::string tempPath = getMediaDirectory() + mSystemName + "/" + subdirectory +
|
|
|
|
subFolders + "/" + getDisplayName();
|
2020-05-18 17:00:43 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Look for an image file in the media directory.
|
2020-12-16 22:59:00 +00:00
|
|
|
for (int i = 0; i < extList.size(); i++) {
|
2020-06-21 12:25:28 +00:00
|
|
|
std::string mediaPath = tempPath + extList[i];
|
|
|
|
if (Utils::FileSystem::exists(mediaPath))
|
|
|
|
return mediaPath;
|
|
|
|
}
|
2020-05-18 17:00:43 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return "";
|
2017-03-18 17:54:39 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 11:10:33 +00:00
|
|
|
const std::string FileData::getImagePath() const
|
2017-10-22 23:04:17 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
// Look for a mix image (a combination of screenshot, 2D/3D box and marquee).
|
|
|
|
std::string image = getMediafilePath("miximages", "miximage");
|
|
|
|
if (image != "")
|
|
|
|
return image;
|
2020-06-06 11:10:33 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// If no mix image was found, try screenshot instead.
|
|
|
|
image = getMediafilePath("screenshots", "screenshot");
|
|
|
|
if (image != "")
|
|
|
|
return image;
|
2020-06-06 11:10:33 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// If no screenshot was found either, try cover.
|
|
|
|
return getMediafilePath("covers", "cover");
|
2020-06-06 11:10:33 +00:00
|
|
|
}
|
2017-10-22 23:04:17 +00:00
|
|
|
|
2020-06-06 11:10:33 +00:00
|
|
|
const std::string FileData::get3DBoxPath() const
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
return getMediafilePath("3dboxes", "3dbox");
|
2020-06-06 11:10:33 +00:00
|
|
|
}
|
2017-10-22 23:04:17 +00:00
|
|
|
|
2020-06-06 11:10:33 +00:00
|
|
|
const std::string FileData::getCoverPath() const
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
return getMediafilePath("covers", "cover");
|
2017-10-22 23:04:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const std::string FileData::getMarqueePath() const
|
2016-12-04 23:47:34 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
return getMediafilePath("marquees", "marquee");
|
2020-06-06 11:10:33 +00:00
|
|
|
}
|
2017-10-22 23:04:17 +00:00
|
|
|
|
2020-06-06 11:10:33 +00:00
|
|
|
const std::string FileData::getMiximagePath() const
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
return getMediafilePath("miximages", "miximage");
|
2016-12-04 23:47:34 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 11:10:33 +00:00
|
|
|
const std::string FileData::getScreenshotPath() const
|
2016-12-04 23:47:34 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
return getMediafilePath("screenshots", "screenshot");
|
2020-06-06 11:10:33 +00:00
|
|
|
}
|
2020-05-18 17:00:43 +00:00
|
|
|
|
2020-06-06 11:10:33 +00:00
|
|
|
const std::string FileData::getThumbnailPath() const
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
return getMediafilePath("thumbnails", "thumbnail");
|
2020-06-06 11:10:33 +00:00
|
|
|
}
|
2017-10-22 23:04:17 +00:00
|
|
|
|
2020-06-06 11:10:33 +00:00
|
|
|
const std::string FileData::getVideoPath() const
|
|
|
|
{
|
2021-01-31 18:53:55 +00:00
|
|
|
const std::vector<std::string> extList = { ".avi", ".mkv", ".mov", ".mp4", ".wmv" };
|
|
|
|
std::string subFolders;
|
|
|
|
|
|
|
|
// Extract possible subfolders from the path.
|
|
|
|
if (mEnvData->mStartPath != "")
|
|
|
|
subFolders = Utils::String::replace(
|
|
|
|
Utils::FileSystem::getParent(mPath), mEnvData->mStartPath, "");
|
|
|
|
|
|
|
|
const std::string tempPath =
|
|
|
|
getMediaDirectory() + mSystemName + "/videos" + subFolders + "/" + getDisplayName();
|
2020-05-18 17:00:43 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Look for media in the media directory.
|
2020-12-16 22:59:00 +00:00
|
|
|
for (int i = 0; i < extList.size(); i++) {
|
2020-06-21 12:25:28 +00:00
|
|
|
std::string mediaPath = tempPath + extList[i];
|
|
|
|
if (Utils::FileSystem::exists(mediaPath))
|
|
|
|
return mediaPath;
|
|
|
|
}
|
2020-05-18 17:00:43 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return "";
|
2020-05-18 17:00:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const std::vector<FileData*>& FileData::getChildrenListToDisplay()
|
|
|
|
{
|
2020-10-25 17:55:01 +00:00
|
|
|
FileFilterIndex* idx = mSystem->getIndex();
|
2021-01-05 09:45:32 +00:00
|
|
|
if (idx->isFiltered() || UIModeController::getInstance()->isUIModeKid()) {
|
2020-06-21 12:25:28 +00:00
|
|
|
mFilteredChildren.clear();
|
|
|
|
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
|
|
|
|
if (idx->showFile((*it))) {
|
|
|
|
mFilteredChildren.push_back(*it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mFilteredChildren;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return mChildren;
|
|
|
|
}
|
2016-12-04 23:47:34 +00:00
|
|
|
}
|
|
|
|
|
2020-07-29 17:01:49 +00:00
|
|
|
std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask,
|
|
|
|
bool displayedOnly, bool countAllGames) const
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
std::vector<FileData*> out;
|
|
|
|
FileFilterIndex* idx = mSystem->getIndex();
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
|
|
|
|
if ((*it)->getType() & typeMask) {
|
2020-07-29 17:01:49 +00:00
|
|
|
if (!displayedOnly || !idx->isFiltered() || idx->showFile(*it)) {
|
|
|
|
if (countAllGames)
|
|
|
|
out.push_back(*it);
|
|
|
|
else if ((*it)->getCountAsGame())
|
|
|
|
out.push_back(*it);
|
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
if ((*it)->getChildren().size() > 0) {
|
2020-07-29 17:01:49 +00:00
|
|
|
std::vector<FileData*> subChildren = (*it)->getFilesRecursive(typeMask, displayedOnly);
|
|
|
|
if (countAllGames) {
|
|
|
|
out.insert(out.cend(), subChildren.cbegin(), subChildren.cend());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
for (auto it = subChildren.cbegin(); it != subChildren.cend(); it++) {
|
|
|
|
if ((*it)->getCountAsGame())
|
|
|
|
out.push_back(*it);
|
|
|
|
}
|
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return out;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-08-06 13:12:04 +00:00
|
|
|
std::vector<FileData*> FileData::getScrapeFilesRecursive(bool includeFolders,
|
|
|
|
bool excludeRecursively, bool respectExclusions) const
|
|
|
|
{
|
|
|
|
std::vector<FileData*> out;
|
|
|
|
|
|
|
|
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
|
|
|
|
if (includeFolders && (*it)->getType() == FOLDER) {
|
|
|
|
if (!(respectExclusions && (*it)->getExcludeFromScraper()))
|
|
|
|
out.push_back(*it);
|
|
|
|
}
|
|
|
|
else if ((*it)->getType() == GAME) {
|
|
|
|
if (!(respectExclusions && (*it)->getExcludeFromScraper()))
|
|
|
|
out.push_back(*it);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the flag has been passed to exclude directories recursively, then skip the entire
|
|
|
|
// folder at this point if the folder is marked for scrape exclusion.
|
|
|
|
if (excludeRecursively && (*it)->getType() == FOLDER && (*it)->getExcludeFromScraper())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ((*it)->getChildren().size() > 0) {
|
|
|
|
std::vector<FileData*> subChildren = (*it)->getScrapeFilesRecursive(
|
|
|
|
includeFolders, excludeRecursively, respectExclusions);
|
|
|
|
out.insert(out.cend(), subChildren.cbegin(), subChildren.cend());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
std::string FileData::getKey() {
|
2020-06-21 12:25:28 +00:00
|
|
|
return getFileName();
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
|
2018-05-10 01:29:46 +00:00
|
|
|
const bool FileData::isArcadeAsset()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
const std::string stem = Utils::FileSystem::getStem(mPath);
|
2020-09-17 20:00:07 +00:00
|
|
|
return ((mSystem && (mSystem->hasPlatformId(PlatformIds::ARCADE) ||
|
2021-02-07 19:49:52 +00:00
|
|
|
mSystem->hasPlatformId(PlatformIds::SNK_NEO_GEO))) &&
|
2020-09-17 20:00:07 +00:00
|
|
|
(MameNames::getInstance()->isBios(stem) ||
|
|
|
|
MameNames::getInstance()->isDevice(stem)));
|
2018-05-10 01:29:46 +00:00
|
|
|
}
|
|
|
|
|
2020-11-14 14:30:49 +00:00
|
|
|
const bool FileData::isArcadeGame()
|
|
|
|
{
|
|
|
|
const std::string stem = Utils::FileSystem::getStem(mPath);
|
|
|
|
return ((mSystem && (mSystem->hasPlatformId(PlatformIds::ARCADE) ||
|
2021-02-07 19:49:52 +00:00
|
|
|
mSystem->hasPlatformId(PlatformIds::SNK_NEO_GEO))) &&
|
2020-11-14 14:30:49 +00:00
|
|
|
(!MameNames::getInstance()->isBios(stem) &&
|
|
|
|
!MameNames::getInstance()->isDevice(stem)));
|
|
|
|
}
|
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
FileData* FileData::getSourceFileData()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
return this;
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
void FileData::addChild(FileData* file)
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
assert(mType == FOLDER);
|
2020-06-23 18:07:00 +00:00
|
|
|
assert(file->getParent() == nullptr);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
const std::string key = file->getKey();
|
|
|
|
if (mChildrenByFilename.find(key) == mChildrenByFilename.cend()) {
|
|
|
|
mChildrenByFilename[key] = file;
|
|
|
|
mChildren.push_back(file);
|
|
|
|
file->mParent = this;
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FileData::removeChild(FileData* file)
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
assert(mType == FOLDER);
|
|
|
|
assert(file->getParent() == this);
|
|
|
|
mChildrenByFilename.erase(file->getKey());
|
|
|
|
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
|
|
|
|
if (*it == file) {
|
2020-06-23 18:07:00 +00:00
|
|
|
file->mParent = nullptr;
|
2020-06-21 12:25:28 +00:00
|
|
|
mChildren.erase(it);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// File somehow wasn't in our children.
|
|
|
|
assert(false);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2021-01-08 19:30:21 +00:00
|
|
|
void FileData::sort(ComparisonFunction& comparator,
|
2020-09-21 16:13:27 +00:00
|
|
|
std::pair<unsigned int, unsigned int>& gameCount)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-08-07 21:33:05 +00:00
|
|
|
mOnlyFolders = true;
|
2020-10-31 10:32:18 +00:00
|
|
|
mHasFolders = false;
|
2020-07-28 17:44:17 +00:00
|
|
|
bool foldersOnTop = Settings::getInstance()->getBool("FoldersOnTop");
|
2020-11-08 17:33:28 +00:00
|
|
|
bool showHiddenGames = Settings::getInstance()->getBool("ShowHiddenGames");
|
2021-01-05 09:45:32 +00:00
|
|
|
bool isKidMode = UIModeController::getInstance()->isUIModeKid();
|
2020-07-28 17:44:17 +00:00
|
|
|
std::vector<FileData*> mChildrenFolders;
|
|
|
|
std::vector<FileData*> mChildrenOthers;
|
2020-07-26 20:19:29 +00:00
|
|
|
|
2020-10-30 10:49:51 +00:00
|
|
|
if (mSystem->isGroupedCustomCollection())
|
|
|
|
gameCount = {};
|
|
|
|
|
2020-11-08 17:33:28 +00:00
|
|
|
if (!showHiddenGames) {
|
|
|
|
for (auto it = mChildren.begin(); it != mChildren.end();) {
|
|
|
|
// If the option to hide hidden games has been set and the game is hidden,
|
|
|
|
// then skip it. Normally games are hidden during loading of the gamelists in
|
|
|
|
// Gamelist::parseGamelist() and this code should only run when a user has marked
|
|
|
|
// an entry manually as hidden. So upon the next application startup, this game
|
|
|
|
// should be filtered already at that earlier point.
|
|
|
|
if ((*it)->getHidden())
|
|
|
|
it = mChildren.erase(it);
|
2021-02-04 18:57:13 +00:00
|
|
|
// Also hide folders where all its entries have been hidden, unless it's a
|
|
|
|
// grouped custom collection.
|
|
|
|
else if ((*it)->getType() == FOLDER && (*it)->getChildren().size() == 0 &&
|
|
|
|
!(*it)->getSystem()->isGroupedCustomCollection())
|
2020-11-08 17:33:28 +00:00
|
|
|
it = mChildren.erase(it);
|
|
|
|
else
|
|
|
|
it++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-23 17:06:30 +00:00
|
|
|
// The main custom collections view is sorted during startup in CollectionSystemsManager.
|
2020-10-21 19:56:31 +00:00
|
|
|
// The individual collections are however sorted as any normal systems/folders.
|
|
|
|
if (mSystem->isCollection() && mSystem->getFullName() == "collections") {
|
2020-10-30 10:49:51 +00:00
|
|
|
std::pair<unsigned int, unsigned int> tempGameCount = {};
|
2020-10-21 19:56:31 +00:00
|
|
|
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
|
|
|
|
if ((*it)->getChildren().size() > 0)
|
2021-01-08 19:30:21 +00:00
|
|
|
(*it)->sort(comparator, gameCount);
|
2020-10-30 10:49:51 +00:00
|
|
|
tempGameCount.first += gameCount.first;
|
|
|
|
tempGameCount.second += gameCount.second;
|
|
|
|
gameCount = {};
|
2020-10-21 19:56:31 +00:00
|
|
|
}
|
2020-10-30 10:49:51 +00:00
|
|
|
gameCount = tempGameCount;
|
2020-10-21 19:56:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-28 17:44:17 +00:00
|
|
|
if (foldersOnTop) {
|
|
|
|
for (unsigned int i = 0; i < mChildren.size(); i++) {
|
2020-09-20 10:17:38 +00:00
|
|
|
if (mChildren[i]->getType() == FOLDER) {
|
2020-07-28 17:44:17 +00:00
|
|
|
mChildrenFolders.push_back(mChildren[i]);
|
2020-09-20 10:17:38 +00:00
|
|
|
}
|
|
|
|
else {
|
2020-07-28 17:44:17 +00:00
|
|
|
mChildrenOthers.push_back(mChildren[i]);
|
2020-09-20 10:17:38 +00:00
|
|
|
mOnlyFolders = false;
|
|
|
|
}
|
2020-07-28 17:44:17 +00:00
|
|
|
}
|
|
|
|
|
2021-01-08 19:30:21 +00:00
|
|
|
// If the requested sorting is not by filename, then sort in ascending filename order
|
|
|
|
// as a first step, in order to get a correct secondary sorting.
|
|
|
|
if (getSortTypeFromString("filename, ascending").comparisonFunction != comparator &&
|
|
|
|
getSortTypeFromString("filename, descending").comparisonFunction != comparator) {
|
|
|
|
std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(),
|
|
|
|
getSortTypeFromString("filename, ascending").comparisonFunction);
|
2020-09-20 18:25:32 +00:00
|
|
|
std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(),
|
|
|
|
getSortTypeFromString("filename, ascending").comparisonFunction);
|
2021-01-08 19:30:21 +00:00
|
|
|
}
|
2020-09-20 18:25:32 +00:00
|
|
|
|
2020-09-20 10:17:38 +00:00
|
|
|
if (foldersOnTop && mOnlyFolders)
|
|
|
|
std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), comparator);
|
2020-10-30 17:34:05 +00:00
|
|
|
|
2020-07-28 17:44:17 +00:00
|
|
|
std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator);
|
|
|
|
|
|
|
|
mChildren.erase(mChildren.begin(), mChildren.end());
|
|
|
|
mChildren.reserve(mChildrenFolders.size() + mChildrenOthers.size());
|
|
|
|
mChildren.insert(mChildren.end(), mChildrenFolders.begin(), mChildrenFolders.end());
|
|
|
|
mChildren.insert(mChildren.end(), mChildrenOthers.begin(), mChildrenOthers.end());
|
|
|
|
}
|
|
|
|
else {
|
2021-01-08 19:30:21 +00:00
|
|
|
// If the requested sorting is not by filename, then sort in ascending filename order
|
|
|
|
// as a first step, in order to get a correct secondary sorting.
|
|
|
|
if (getSortTypeFromString("filename, ascending").comparisonFunction != comparator &&
|
|
|
|
getSortTypeFromString("filename, descending").comparisonFunction != comparator)
|
2020-09-20 18:25:32 +00:00
|
|
|
std::stable_sort(mChildren.begin(), mChildren.end(),
|
|
|
|
getSortTypeFromString("filename, ascending").comparisonFunction);
|
|
|
|
|
2020-07-28 17:44:17 +00:00
|
|
|
std::stable_sort(mChildren.begin(), mChildren.end(), comparator);
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
|
2020-09-21 16:13:27 +00:00
|
|
|
// Game count, which will be displayed in the system view.
|
|
|
|
if ((*it)->getType() == GAME && (*it)->getCountAsGame()) {
|
2021-01-05 09:45:32 +00:00
|
|
|
if (!isKidMode || (isKidMode && (*it)->getKidgame())) {
|
|
|
|
gameCount.first++;
|
|
|
|
if ((*it)->getFavorite())
|
|
|
|
gameCount.second++;
|
|
|
|
}
|
2020-09-21 16:13:27 +00:00
|
|
|
}
|
|
|
|
|
2020-08-07 21:33:05 +00:00
|
|
|
if ((*it)->getType() != FOLDER)
|
|
|
|
mOnlyFolders = false;
|
2020-10-31 10:32:18 +00:00
|
|
|
else
|
|
|
|
mHasFolders = true;
|
2020-08-07 21:33:05 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Iterate through any child folders.
|
|
|
|
if ((*it)->getChildren().size() > 0)
|
2021-01-08 19:30:21 +00:00
|
|
|
(*it)->sort(comparator, gameCount);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-10-30 10:49:51 +00:00
|
|
|
if (mSystem->isGroupedCustomCollection())
|
|
|
|
mGameCount = gameCount;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2021-01-08 19:30:21 +00:00
|
|
|
void FileData::sortFavoritesOnTop(ComparisonFunction& comparator,
|
2020-09-21 16:13:27 +00:00
|
|
|
std::pair<unsigned int, unsigned int>& gameCount)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-08-07 21:33:05 +00:00
|
|
|
mOnlyFolders = true;
|
2020-10-31 10:32:18 +00:00
|
|
|
mHasFolders = false;
|
2020-11-08 17:33:28 +00:00
|
|
|
bool foldersOnTop = Settings::getInstance()->getBool("FoldersOnTop");
|
|
|
|
bool showHiddenGames = Settings::getInstance()->getBool("ShowHiddenGames");
|
2021-01-05 09:45:32 +00:00
|
|
|
bool isKidMode = UIModeController::getInstance()->isUIModeKid();
|
2020-07-28 17:44:17 +00:00
|
|
|
std::vector<FileData*> mChildrenFolders;
|
2020-09-20 18:25:32 +00:00
|
|
|
std::vector<FileData*> mChildrenFavoritesFolders;
|
2020-06-21 12:25:28 +00:00
|
|
|
std::vector<FileData*> mChildrenFavorites;
|
|
|
|
std::vector<FileData*> mChildrenOthers;
|
|
|
|
|
2020-10-30 10:49:51 +00:00
|
|
|
if (mSystem->isGroupedCustomCollection())
|
|
|
|
gameCount = {};
|
|
|
|
|
2020-12-23 17:06:30 +00:00
|
|
|
// The main custom collections view is sorted during startup in CollectionSystemsManager.
|
2020-10-21 19:56:31 +00:00
|
|
|
// The individual collections are however sorted as any normal systems/folders.
|
|
|
|
if (mSystem->isCollection() && mSystem->getFullName() == "collections") {
|
2020-10-30 10:49:51 +00:00
|
|
|
std::pair<unsigned int, unsigned int> tempGameCount = {};
|
2020-10-21 19:56:31 +00:00
|
|
|
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
|
|
|
|
if ((*it)->getChildren().size() > 0)
|
2021-01-08 19:30:21 +00:00
|
|
|
(*it)->sortFavoritesOnTop(comparator, gameCount);
|
2020-10-30 10:49:51 +00:00
|
|
|
tempGameCount.first += gameCount.first;
|
|
|
|
tempGameCount.second += gameCount.second;
|
|
|
|
gameCount = {};
|
2020-10-21 19:56:31 +00:00
|
|
|
}
|
2020-10-30 10:49:51 +00:00
|
|
|
gameCount = tempGameCount;
|
2020-10-21 19:56:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
for (unsigned int i = 0; i < mChildren.size(); i++) {
|
2020-11-08 17:33:28 +00:00
|
|
|
// If the option to hide hidden games has been set and the game is hidden,
|
|
|
|
// then skip it. Normally games are hidden during loading of the gamelists in
|
|
|
|
// Gamelist::parseGamelist() and this code should only run when a user has marked
|
|
|
|
// an entry manually as hidden. So upon the next application startup, this game
|
|
|
|
// should be filtered already at that earlier point.
|
|
|
|
if (!showHiddenGames && mChildren[i]->getHidden())
|
|
|
|
continue;
|
|
|
|
// Also hide folders where all its entries have been hidden.
|
|
|
|
else if (mChildren[i]->getType() == FOLDER && mChildren[i]->getChildren().size() == 0)
|
|
|
|
continue;
|
|
|
|
|
2020-09-21 16:13:27 +00:00
|
|
|
// Game count, which will be displayed in the system view.
|
|
|
|
if (mChildren[i]->getType() == GAME && mChildren[i]->getCountAsGame()) {
|
2021-01-05 09:45:32 +00:00
|
|
|
if (!isKidMode || (isKidMode && mChildren[i]->getKidgame())) {
|
|
|
|
gameCount.first++;
|
|
|
|
if (mChildren[i]->getFavorite())
|
|
|
|
gameCount.second++;
|
|
|
|
}
|
2020-09-21 16:13:27 +00:00
|
|
|
}
|
|
|
|
|
2020-07-28 17:44:17 +00:00
|
|
|
if (foldersOnTop && mChildren[i]->getType() == FOLDER) {
|
2020-09-20 18:25:32 +00:00
|
|
|
if (!mChildren[i]->getFavorite())
|
|
|
|
mChildrenFolders.push_back(mChildren[i]);
|
|
|
|
else
|
|
|
|
mChildrenFavoritesFolders.push_back(mChildren[i]);
|
2020-07-28 17:44:17 +00:00
|
|
|
}
|
|
|
|
else if (mChildren[i]->getFavorite()) {
|
2020-06-21 12:25:28 +00:00
|
|
|
mChildrenFavorites.push_back(mChildren[i]);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mChildrenOthers.push_back(mChildren[i]);
|
|
|
|
}
|
2020-08-07 21:33:05 +00:00
|
|
|
|
|
|
|
if (mChildren[i]->getType() != FOLDER)
|
|
|
|
mOnlyFolders = false;
|
2020-10-31 10:32:18 +00:00
|
|
|
else
|
|
|
|
mHasFolders = true;
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
2020-10-30 10:49:51 +00:00
|
|
|
if (mSystem->isGroupedCustomCollection())
|
|
|
|
mGameCount = gameCount;
|
|
|
|
|
2020-09-20 18:25:32 +00:00
|
|
|
// If there are favorite folders and this is a mixed list, then don't handle these
|
|
|
|
// separately but instead merge them into the same vector. This is a quite wasteful
|
|
|
|
// approach but the scenario where a user has a mixed folder and files list and marks
|
|
|
|
// some folders as favorites is probably a rare situation.
|
|
|
|
if (!mOnlyFolders && mChildrenFavoritesFolders.size() > 0) {
|
|
|
|
mChildrenFolders.insert(mChildrenFolders.end(), mChildrenFavoritesFolders.begin(),
|
|
|
|
mChildrenFavoritesFolders.end());
|
|
|
|
mChildrenFavoritesFolders.erase(mChildrenFavoritesFolders.begin(),
|
|
|
|
mChildrenFavoritesFolders.end());
|
|
|
|
std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(),
|
|
|
|
getSortTypeFromString("filename, ascending").comparisonFunction);
|
|
|
|
}
|
|
|
|
|
2021-01-08 19:30:21 +00:00
|
|
|
// If the requested sorting is not by filename, then sort in ascending filename order
|
|
|
|
// as a first step, in order to get a correct secondary sorting.
|
|
|
|
if (getSortTypeFromString("filename, ascending").comparisonFunction != comparator &&
|
|
|
|
getSortTypeFromString("filename, descending").comparisonFunction != comparator) {
|
2020-09-20 18:25:32 +00:00
|
|
|
std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(),
|
|
|
|
getSortTypeFromString("filename, ascending").comparisonFunction);
|
|
|
|
std::stable_sort(mChildrenFavoritesFolders.begin(), mChildrenFavoritesFolders.end(),
|
|
|
|
getSortTypeFromString("filename, ascending").comparisonFunction);
|
|
|
|
std::stable_sort(mChildrenFavorites.begin(), mChildrenFavorites.end(),
|
|
|
|
getSortTypeFromString("filename, ascending").comparisonFunction);
|
|
|
|
std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(),
|
|
|
|
getSortTypeFromString("filename, ascending").comparisonFunction);
|
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Sort favorite games and the other games separately.
|
2020-09-20 18:25:32 +00:00
|
|
|
if (foldersOnTop && mOnlyFolders) {
|
|
|
|
std::stable_sort(mChildrenFavoritesFolders.begin(),
|
|
|
|
mChildrenFavoritesFolders.end(), comparator);
|
2020-09-20 10:17:38 +00:00
|
|
|
std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), comparator);
|
2020-09-20 18:25:32 +00:00
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
std::stable_sort(mChildrenFavorites.begin(), mChildrenFavorites.end(), comparator);
|
|
|
|
std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator);
|
|
|
|
|
2020-09-20 18:25:32 +00:00
|
|
|
// Iterate through any child favorite folders.
|
|
|
|
for (auto it = mChildrenFavoritesFolders.cbegin(); it !=
|
|
|
|
mChildrenFavoritesFolders.cend(); it++) {
|
2020-06-21 12:25:28 +00:00
|
|
|
if ((*it)->getChildren().size() > 0)
|
2021-01-08 19:30:21 +00:00
|
|
|
(*it)->sortFavoritesOnTop(comparator, gameCount);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Iterate through any child folders.
|
2020-09-20 18:25:32 +00:00
|
|
|
for (auto it = mChildrenFolders.cbegin(); it != mChildrenFolders.cend(); it++) {
|
2020-06-21 12:25:28 +00:00
|
|
|
if ((*it)->getChildren().size() > 0)
|
2021-01-08 19:30:21 +00:00
|
|
|
(*it)->sortFavoritesOnTop(comparator, gameCount);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
2020-10-30 17:34:05 +00:00
|
|
|
// If folders are not sorted on top, mChildrenFavoritesFolders and mChildrenFolders
|
|
|
|
// could be empty. So due to this, step through all mChildren and see if there are
|
|
|
|
// any folders that we need to iterate.
|
|
|
|
if (mChildrenFavoritesFolders.size() == 0 && mChildrenFolders.size() == 0) {
|
|
|
|
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
|
|
|
|
if ((*it)->getChildren().size() > 0)
|
2021-01-08 19:30:21 +00:00
|
|
|
(*it)->sortFavoritesOnTop(comparator, gameCount);
|
2020-09-20 18:25:32 +00:00
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Combine the individually sorted favorite games and other games vectors.
|
|
|
|
mChildren.erase(mChildren.begin(), mChildren.end());
|
2020-09-20 18:25:32 +00:00
|
|
|
mChildren.reserve(mChildrenFavoritesFolders.size() + mChildrenFolders.size() +
|
|
|
|
mChildrenFavorites.size() + mChildrenOthers.size());
|
|
|
|
mChildren.insert(mChildren.end(), mChildrenFavoritesFolders.begin(),
|
|
|
|
mChildrenFavoritesFolders.end());
|
2020-07-28 17:44:17 +00:00
|
|
|
mChildren.insert(mChildren.end(), mChildrenFolders.begin(), mChildrenFolders.end());
|
2020-06-21 12:25:28 +00:00
|
|
|
mChildren.insert(mChildren.end(), mChildrenFavorites.begin(), mChildrenFavorites.end());
|
|
|
|
mChildren.insert(mChildren.end(), mChildrenOthers.begin(), mChildrenOthers.end());
|
2020-05-24 08:29:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FileData::sort(const SortType& type, bool mFavoritesOnTop)
|
|
|
|
{
|
2020-09-21 16:13:27 +00:00
|
|
|
mGameCount = std::make_pair(0, 0);
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mFavoritesOnTop)
|
2021-01-08 19:30:21 +00:00
|
|
|
sortFavoritesOnTop(*type.comparisonFunction, mGameCount);
|
2020-06-21 12:25:28 +00:00
|
|
|
else
|
2021-01-08 19:30:21 +00:00
|
|
|
sort(*type.comparisonFunction, mGameCount);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
2017-06-12 16:38:59 +00:00
|
|
|
|
2020-10-30 11:53:35 +00:00
|
|
|
void FileData::countGames(std::pair<unsigned int, unsigned int>& gameCount)
|
|
|
|
{
|
2021-01-05 09:45:32 +00:00
|
|
|
bool isKidMode = (Settings::getInstance()->getString("UIMode") == "kid" ||
|
|
|
|
Settings::getInstance()->getBool("ForceKid"));
|
|
|
|
|
|
|
|
(Settings::getInstance()->getString("UIMode") == "kid" ||
|
|
|
|
Settings::getInstance()->getBool("ForceKid"));
|
|
|
|
|
2020-10-30 11:53:35 +00:00
|
|
|
for (unsigned int i = 0; i < mChildren.size(); i++) {
|
|
|
|
if (mChildren[i]->getType() == GAME && mChildren[i]->getCountAsGame()) {
|
2021-01-05 09:45:32 +00:00
|
|
|
if (!isKidMode || (isKidMode && mChildren[i]->getKidgame())) {
|
|
|
|
gameCount.first++;
|
|
|
|
if (mChildren[i]->getFavorite())
|
|
|
|
gameCount.second++;
|
|
|
|
}
|
2020-10-30 11:53:35 +00:00
|
|
|
}
|
|
|
|
// Iterate through any folders.
|
|
|
|
else if (mChildren[i]->getType() == FOLDER)
|
|
|
|
mChildren[i]->countGames(gameCount);
|
|
|
|
}
|
|
|
|
mGameCount = gameCount;
|
|
|
|
}
|
|
|
|
|
2020-09-17 20:18:13 +00:00
|
|
|
FileData::SortType FileData::getSortTypeFromString(std::string desc) {
|
|
|
|
std::vector<FileData::SortType> SortTypes = FileSorts::SortTypes;
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < FileSorts::SortTypes.size(); i++) {
|
|
|
|
const FileData::SortType& sort = FileSorts::SortTypes.at(i);
|
|
|
|
if (sort.description == desc)
|
|
|
|
return sort;
|
|
|
|
}
|
|
|
|
// If no type was found then default to "filename, ascending".
|
|
|
|
return FileSorts::SortTypes.at(0);
|
|
|
|
}
|
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
void FileData::launchGame(Window* window)
|
|
|
|
{
|
2021-01-11 17:21:14 +00:00
|
|
|
LOG(LogInfo) << "Launching game \"" << this->metadata.get("name") << "\"...";
|
2017-06-12 16:38:59 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
std::string command = "";
|
2020-05-19 15:52:11 +00:00
|
|
|
|
2020-07-10 18:58:53 +00:00
|
|
|
// Check if there is a launch command override for the game
|
2020-06-21 12:25:28 +00:00
|
|
|
// and the corresponding option to use it has been set.
|
2020-07-08 15:06:34 +00:00
|
|
|
if (Settings::getInstance()->getBool("LaunchCommandOverride") &&
|
|
|
|
!metadata.get("launchcommand").empty())
|
|
|
|
command = metadata.get("launchcommand");
|
2020-06-21 12:25:28 +00:00
|
|
|
else
|
|
|
|
command = mEnvData->mLaunchCommand;
|
2017-06-12 16:38:59 +00:00
|
|
|
|
2020-07-07 19:25:15 +00:00
|
|
|
std::string commandRaw = command;
|
|
|
|
|
2021-01-10 21:55:17 +00:00
|
|
|
const std::string romPath = Utils::FileSystem::getEscapedPath(getPath());
|
|
|
|
const std::string baseName = Utils::FileSystem::getStem(getPath());
|
|
|
|
const std::string romRaw = Utils::FileSystem::getPreferredPath(getPath());
|
|
|
|
const std::string esPath = Utils::FileSystem::getExePath();
|
2021-06-19 12:09:14 +00:00
|
|
|
|
|
|
|
std::string coreEntry;
|
|
|
|
std::string coreName;
|
|
|
|
size_t coreEntryPos = 0;
|
|
|
|
size_t coreFilePos = 0;
|
|
|
|
bool foundCoreFile = false;
|
|
|
|
std::vector<std::string> emulatorCorePaths;
|
|
|
|
|
|
|
|
// If there's a quotation mark before the %CORE_ variable, then remove it.
|
|
|
|
// The closing quotation mark will be removed later below.
|
|
|
|
command = Utils::String::replace(command, "\"%CORE_", "%CORE_");
|
|
|
|
|
|
|
|
coreEntryPos = command.find("%CORE_");
|
|
|
|
if (coreEntryPos != std::string::npos) {
|
|
|
|
coreFilePos = command.find("%", coreEntryPos + 6);
|
|
|
|
if (coreFilePos != std::string::npos)
|
|
|
|
coreEntry = command.substr(coreEntryPos + 6, coreFilePos - (coreEntryPos + 6));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (coreEntry != "")
|
|
|
|
emulatorCorePaths = SystemData::sFindRules.get()->mCores[coreEntry].corePaths;
|
2017-06-12 16:38:59 +00:00
|
|
|
|
2020-07-10 18:58:53 +00:00
|
|
|
// Expand home path if ~ is used.
|
|
|
|
command = Utils::FileSystem::expandHomePath(command);
|
2017-06-12 16:38:59 +00:00
|
|
|
|
2021-06-19 12:09:14 +00:00
|
|
|
// Check that the emulator binary actually exists, and if so, get its path.
|
2021-06-23 18:37:18 +00:00
|
|
|
std::string binaryPath = findEmulatorPath(command);
|
2021-06-19 12:09:14 +00:00
|
|
|
|
|
|
|
// Hack to show an error message if there was no emulator entry in es_find_rules.xml.
|
2021-06-23 18:37:18 +00:00
|
|
|
if (binaryPath.substr(0, 18) == "NO EMULATOR RULE: ") {
|
|
|
|
std::string emulatorEntry = binaryPath.substr(18, binaryPath.size() - 18);
|
2021-06-19 12:09:14 +00:00
|
|
|
LOG(LogError) << "Couldn't launch game, either there is no emulator entry for \"" <<
|
|
|
|
emulatorEntry << "\" in es_find_rules.xml or there are no systempath or staticpath "
|
|
|
|
"rules defined";
|
|
|
|
LOG(LogError) << "Raw emulator launch command:";
|
|
|
|
LOG(LogError) << commandRaw;
|
|
|
|
|
|
|
|
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: MISSING EMULATOR CONFIGURATION FOR '" +
|
|
|
|
emulatorEntry + "'", 6000);
|
|
|
|
window->setInfoPopup(s);
|
|
|
|
return;
|
|
|
|
}
|
2021-06-23 18:37:18 +00:00
|
|
|
else if (binaryPath.empty()) {
|
2021-01-10 21:55:17 +00:00
|
|
|
LOG(LogError) << "Couldn't launch game, emulator binary not found";
|
|
|
|
LOG(LogError) << "Raw emulator launch command:";
|
|
|
|
LOG(LogError) << commandRaw;
|
|
|
|
|
|
|
|
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: COULDN'T FIND EMULATOR, HAS IT " \
|
|
|
|
"BEEN PROPERLY INSTALLED?", 6000);
|
|
|
|
window->setInfoPopup(s);
|
|
|
|
return;
|
|
|
|
}
|
2020-07-07 19:25:15 +00:00
|
|
|
|
2021-06-19 12:09:14 +00:00
|
|
|
// If %EMUPATH% is used in es_systems.xml for this system, then check that the core
|
|
|
|
// file actually exists.
|
|
|
|
size_t emuPathPos = command.find("%EMUPATH%");
|
2021-01-11 17:21:14 +00:00
|
|
|
if (emuPathPos != std::string::npos) {
|
|
|
|
bool hasQuotationMark = false;
|
|
|
|
unsigned int quotationMarkPos = 0;
|
|
|
|
if (command.find("\"%EMUPATH%", emuPathPos - 1) != std::string::npos) {
|
|
|
|
hasQuotationMark = true;
|
|
|
|
quotationMarkPos = static_cast<unsigned int>(
|
|
|
|
command.find("\"", emuPathPos + 9) - emuPathPos);
|
|
|
|
}
|
2021-06-19 12:09:14 +00:00
|
|
|
size_t spacePos = command.find(" ", emuPathPos + quotationMarkPos);
|
2021-01-11 17:21:14 +00:00
|
|
|
std::string coreRaw;
|
|
|
|
std::string coreFile;
|
|
|
|
if (spacePos != std::string::npos) {
|
|
|
|
coreRaw = command.substr(emuPathPos, spacePos - emuPathPos);
|
2021-06-23 18:37:18 +00:00
|
|
|
coreFile = Utils::FileSystem::getParent(binaryPath) +
|
2021-06-19 12:09:14 +00:00
|
|
|
command.substr(emuPathPos + 9, spacePos - emuPathPos - 9);
|
2021-01-11 17:21:14 +00:00
|
|
|
if (hasQuotationMark) {
|
|
|
|
coreRaw.pop_back();
|
|
|
|
coreFile.pop_back();
|
|
|
|
}
|
|
|
|
if (!Utils::FileSystem::isRegularFile(coreFile) &&
|
|
|
|
!Utils::FileSystem::isSymlink(coreFile)) {
|
|
|
|
LOG(LogError) << "Couldn't launch game, emulator core file \"" <<
|
2021-06-19 12:09:14 +00:00
|
|
|
Utils::FileSystem::getFileName(coreFile) << "\" not found";
|
2021-01-11 17:21:14 +00:00
|
|
|
LOG(LogError) << "Raw emulator launch command:";
|
|
|
|
LOG(LogError) << commandRaw;
|
|
|
|
|
|
|
|
GuiInfoPopup* s = new GuiInfoPopup(window,
|
|
|
|
"ERROR: COULDN'T FIND EMULATOR CORE FILE '" +
|
|
|
|
Utils::String::toUpper(Utils::FileSystem::getFileName(coreFile)) +
|
|
|
|
"'", 6000);
|
|
|
|
window->setInfoPopup(s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (hasQuotationMark) {
|
|
|
|
command = command.replace(emuPathPos + quotationMarkPos, 1, "");
|
|
|
|
emuPathPos--;
|
|
|
|
command = command.replace(emuPathPos, 1, "");
|
|
|
|
}
|
|
|
|
coreFile = Utils::FileSystem::getEscapedPath(coreFile);
|
|
|
|
command = command.replace(emuPathPos, coreRaw.size(), coreFile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2021-06-16 16:54:04 +00:00
|
|
|
LOG(LogError) << "Invalid entry in systems configuration file es_systems.xml";
|
2021-01-11 17:21:14 +00:00
|
|
|
LOG(LogError) << "Raw emulator launch command:";
|
|
|
|
LOG(LogError) << commandRaw;
|
|
|
|
|
|
|
|
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: INVALID ENTRY IN SYSTEMS " \
|
|
|
|
"CONFIGURATION FILE", 6000);
|
|
|
|
window->setInfoPopup(s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-19 12:09:14 +00:00
|
|
|
// Error handling in case of no core find rule.
|
|
|
|
if (coreEntry != "" && emulatorCorePaths.empty()) {
|
|
|
|
LOG(LogError) << "Couldn't launch game, either there is no core entry for \"" <<
|
|
|
|
coreEntry << "\" in es_find_rules.xml or there are no corepath rules defined";
|
|
|
|
LOG(LogError) << "Raw emulator launch command:";
|
|
|
|
LOG(LogError) << commandRaw;
|
2021-01-11 17:21:14 +00:00
|
|
|
|
2021-06-19 12:09:14 +00:00
|
|
|
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: MISSING CORE CONFIGURATION FOR '" +
|
|
|
|
coreEntry + "'", 6000);
|
|
|
|
window->setInfoPopup(s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If a %CORE_ find rule entry is used in es_systems.xml for this system, then try to find
|
|
|
|
// the emulator core using the rules defined in es_find_rules.xml.
|
|
|
|
for (std::string path : emulatorCorePaths) {
|
2021-06-20 10:30:14 +00:00
|
|
|
|
|
|
|
// The position of the %CORE_ variable could have changed as there may have been an
|
|
|
|
// %EMULATOR_ variable that was substituted for the actual emulator binary.
|
|
|
|
coreEntryPos = command.find("%CORE_");
|
|
|
|
coreFilePos = command.find("%", coreEntryPos + 6);
|
|
|
|
|
2021-06-19 12:09:14 +00:00
|
|
|
size_t separatorPos;
|
|
|
|
size_t quotePos = command.find("\"", coreFilePos);
|
|
|
|
if (quotePos == std::string::npos)
|
|
|
|
separatorPos = command.find(" ", coreFilePos);
|
|
|
|
else
|
|
|
|
separatorPos = quotePos;
|
|
|
|
|
|
|
|
if (separatorPos != std::string::npos) {
|
|
|
|
coreName = command.substr(coreFilePos + 1, separatorPos - (coreFilePos + 1));
|
|
|
|
|
|
|
|
std::string coreFile = Utils::FileSystem::expandHomePath(path + coreName);
|
|
|
|
|
|
|
|
// Expand %EMUPATH% if it has been used in the %CORE_ variable.
|
|
|
|
size_t stringPos = coreFile.find("%EMUPATH%");
|
2021-06-20 10:30:14 +00:00
|
|
|
if (stringPos != std::string::npos) {
|
|
|
|
#if defined (_WIN64)
|
|
|
|
coreFile = Utils::String::replace(coreFile.replace(stringPos, 9,
|
2021-06-23 18:37:18 +00:00
|
|
|
Utils::FileSystem::getParent(binaryPath)), "/", "\\");
|
2021-06-20 10:30:14 +00:00
|
|
|
#else
|
2021-06-23 18:37:18 +00:00
|
|
|
coreFile = coreFile.replace(stringPos, 9, Utils::FileSystem::getParent(binaryPath));
|
2021-06-20 10:30:14 +00:00
|
|
|
#endif
|
|
|
|
}
|
2021-06-19 12:09:14 +00:00
|
|
|
|
|
|
|
// Expand %ESPATH% if it has been used in the %CORE_ variable.
|
|
|
|
stringPos = coreFile.find("%ESPATH%");
|
|
|
|
if (stringPos != std::string::npos) {
|
|
|
|
coreFile = coreFile.replace(stringPos, 8, esPath);
|
|
|
|
#if defined(_WIN64)
|
|
|
|
coreFile = Utils::String::replace(coreFile, "/", "\\");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Utils::FileSystem::isRegularFile(coreFile) ||
|
|
|
|
Utils::FileSystem::isSymlink(coreFile)) {
|
|
|
|
foundCoreFile = true;
|
|
|
|
// Escape any blankspaces.
|
|
|
|
if (coreFile.find(" ") != std::string::npos)
|
|
|
|
coreFile = Utils::FileSystem::getEscapedPath(coreFile);
|
2021-06-20 10:30:14 +00:00
|
|
|
command.replace(coreEntryPos, separatorPos - coreEntryPos, coreFile);
|
2021-06-19 12:09:14 +00:00
|
|
|
// Remove any quotation marks as it would make the launch function fail.
|
|
|
|
if (command.find("\"") != std::string::npos)
|
|
|
|
command = Utils::String::replace(command, "\"", "");
|
|
|
|
break;
|
2020-12-15 17:42:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2021-06-16 16:54:04 +00:00
|
|
|
LOG(LogError) << "Invalid entry in systems configuration file es_systems.xml";
|
2020-12-15 17:42:38 +00:00
|
|
|
LOG(LogError) << "Raw emulator launch command:";
|
|
|
|
LOG(LogError) << commandRaw;
|
|
|
|
|
|
|
|
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: INVALID ENTRY IN SYSTEMS " \
|
|
|
|
"CONFIGURATION FILE", 6000);
|
|
|
|
window->setInfoPopup(s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2021-06-19 12:09:14 +00:00
|
|
|
if (!foundCoreFile && coreName.size() > 0) {
|
|
|
|
LOG(LogError) << "Couldn't launch game, emulator core file \"" <<
|
|
|
|
coreName.substr(0, coreName.size()) << "\" not found";
|
|
|
|
LOG(LogError) << "Raw emulator launch command:";
|
|
|
|
LOG(LogError) << commandRaw;
|
|
|
|
LOG(LogError) <<
|
|
|
|
"Tried to find the core file using these paths as defined by es_find_rules.xml:";
|
|
|
|
LOG(LogError) << Utils::String::vectorToDelimitedString(emulatorCorePaths, ", ");
|
|
|
|
|
|
|
|
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: COULDN'T FIND EMULATOR CORE FILE '" +
|
|
|
|
Utils::String::toUpper(coreName.substr(0, coreName.size()) + "'"), 6000);
|
2020-12-15 17:42:38 +00:00
|
|
|
window->setInfoPopup(s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-20 10:30:14 +00:00
|
|
|
// Replace the remaining variables with their actual values.
|
|
|
|
command = Utils::String::replace(command, "%ROM%", romPath);
|
|
|
|
command = Utils::String::replace(command, "%BASENAME%", baseName);
|
|
|
|
command = Utils::String::replace(command, "%ROMRAW%", romRaw);
|
|
|
|
command = Utils::String::replace(command, "%ESPATH%", esPath);
|
|
|
|
|
2021-03-27 12:51:50 +00:00
|
|
|
// swapBuffers() is called here to turn the screen black to eliminate some potential
|
|
|
|
// flickering and to avoid showing the game launch message briefly when returning
|
|
|
|
// from the game.
|
|
|
|
if (!ViewController::get()->runInBackground(mSystem))
|
|
|
|
Renderer::swapBuffers();
|
|
|
|
|
2021-01-10 21:55:17 +00:00
|
|
|
Scripting::fireEvent("game-start", romPath, getSourceFileData()->metadata.get("name"));
|
2020-07-18 21:07:02 +00:00
|
|
|
int returnValue = 0;
|
2020-07-07 19:25:15 +00:00
|
|
|
|
2020-08-05 13:07:17 +00:00
|
|
|
LOG(LogDebug) << "Raw emulator launch command:";
|
|
|
|
LOG(LogDebug) << commandRaw;
|
2020-07-07 19:25:15 +00:00
|
|
|
LOG(LogInfo) << "Expanded emulator launch command:";
|
|
|
|
|
2021-01-11 17:21:14 +00:00
|
|
|
LOG(LogInfo) << command;
|
2021-03-27 12:49:09 +00:00
|
|
|
// Possibly keep ES-DE running in the background while the game is launched.
|
2020-08-19 20:17:32 +00:00
|
|
|
#if defined(_WIN64)
|
2021-03-27 12:49:09 +00:00
|
|
|
returnValue = launchGameWindows(Utils::String::stringToWideString(command),
|
|
|
|
ViewController::get()->runInBackground(mSystem));
|
2020-07-07 19:25:15 +00:00
|
|
|
#else
|
2021-03-27 12:49:09 +00:00
|
|
|
returnValue = launchGameUnix(command, ViewController::get()->runInBackground(mSystem));
|
2020-07-07 19:25:15 +00:00
|
|
|
#endif
|
2020-07-18 11:21:44 +00:00
|
|
|
// Notify the user in case of a failed game launch using a popup window.
|
2020-07-18 21:07:02 +00:00
|
|
|
if (returnValue != 0) {
|
2021-01-10 21:55:17 +00:00
|
|
|
LOG(LogWarning) << "...launch terminated with nonzero return value " << returnValue;
|
2020-07-18 11:21:44 +00:00
|
|
|
|
|
|
|
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR LAUNCHING GAME '" +
|
2020-07-18 21:07:02 +00:00
|
|
|
Utils::String::toUpper(metadata.get("name")) + "' (ERROR CODE " +
|
|
|
|
Utils::String::toUpper(std::to_string(returnValue) + ")"), 6000);
|
2020-07-18 11:21:44 +00:00
|
|
|
window->setInfoPopup(s);
|
|
|
|
}
|
|
|
|
else {
|
2020-08-15 13:41:11 +00:00
|
|
|
// Stop showing the game launch notification.
|
|
|
|
window->stopInfoPopup();
|
2021-03-27 12:49:09 +00:00
|
|
|
#if defined(_WIN64)
|
|
|
|
// For some game systems or if the "RunInBackground" setting has been enabled, keep
|
|
|
|
// ES-DE running while the game is launched. This pauses any video and keeps the
|
|
|
|
// screensaver from getting activated.
|
|
|
|
if (ViewController::get()->runInBackground(mSystem))
|
2020-07-19 20:08:14 +00:00
|
|
|
window->setLaunchedGame();
|
2020-07-20 07:54:38 +00:00
|
|
|
else
|
|
|
|
// Normalize deltaTime so that the screensaver does not start immediately
|
|
|
|
// when returning from the game.
|
|
|
|
window->normalizeNextUpdate();
|
|
|
|
#else
|
2021-03-27 12:49:09 +00:00
|
|
|
// For some game systems we need to keep ES-DE running while the game is launched.
|
2021-03-24 19:13:33 +00:00
|
|
|
// This pauses any video and keeps the screensaver from getting activated.
|
2021-03-27 12:49:09 +00:00
|
|
|
if (ViewController::get()->runInBackground(mSystem))
|
2021-03-24 19:13:33 +00:00
|
|
|
window->setLaunchedGame();
|
2020-07-20 07:54:38 +00:00
|
|
|
// Normalize deltaTime so that the screensaver does not start immediately
|
|
|
|
// when returning from the game.
|
|
|
|
window->normalizeNextUpdate();
|
|
|
|
#endif
|
2020-06-25 17:52:38 +00:00
|
|
|
}
|
2017-06-12 16:38:59 +00:00
|
|
|
|
2021-01-10 21:55:17 +00:00
|
|
|
Scripting::fireEvent("game-end", romPath, getSourceFileData()->metadata.get("name"));
|
2018-01-30 00:49:08 +00:00
|
|
|
|
2020-09-17 20:00:07 +00:00
|
|
|
// Re-enable the text scrolling that was disabled in ViewController on game launch.
|
|
|
|
window->setAllowTextScrolling(true);
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Update number of times the game has been launched.
|
|
|
|
FileData* gameToUpdate = getSourceFileData();
|
2017-06-12 16:38:59 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
int timesPlayed = gameToUpdate->metadata.getInt("playcount") + 1;
|
|
|
|
gameToUpdate->metadata.set("playcount", std::to_string(static_cast<long long>(timesPlayed)));
|
2017-06-12 16:38:59 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Update last played time.
|
|
|
|
gameToUpdate->metadata.set("lastplayed", Utils::Time::DateTime(Utils::Time::now()));
|
2020-07-30 12:21:20 +00:00
|
|
|
|
|
|
|
// If the parent is a folder and it's not the root of the system, then update its lastplayed
|
|
|
|
// timestamp to the same time as the game that was just launched.
|
|
|
|
if (gameToUpdate->getParent()->getType() == FOLDER && gameToUpdate->getParent()->getName() !=
|
|
|
|
gameToUpdate->getSystem()->getFullName()) {
|
|
|
|
gameToUpdate->getParent()->metadata.set("lastplayed",
|
|
|
|
gameToUpdate->metadata.get("lastplayed"));
|
|
|
|
}
|
|
|
|
|
2020-12-23 17:06:30 +00:00
|
|
|
CollectionSystemsManager::get()->refreshCollectionSystems(gameToUpdate);
|
2019-08-24 14:22:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
gameToUpdate->mSystem->onMetaDataSavePoint();
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
|
2021-06-19 12:09:14 +00:00
|
|
|
std::string FileData::findEmulatorPath(std::string& command)
|
2021-01-10 21:55:17 +00:00
|
|
|
{
|
2021-06-19 12:09:14 +00:00
|
|
|
// Extract the emulator executable from the launch command string. There are two ways
|
|
|
|
// that the emulator can be defined in es_systems.xml, either using the find rules in
|
|
|
|
// es_find_rules.xml or via the exact emulator binary name. In the former case, we
|
|
|
|
// need to process any configured systempath and staticpath rules, and in the latter
|
|
|
|
// we simply search for the emulator binary in the system path.
|
|
|
|
|
2021-01-11 17:21:14 +00:00
|
|
|
std::string emuExecutable;
|
|
|
|
std::string exePath;
|
2021-01-10 21:55:17 +00:00
|
|
|
|
2021-06-19 12:09:14 +00:00
|
|
|
// Method 1, emulator binary is defined using find rules:
|
|
|
|
|
|
|
|
std::vector<std::string> emulatorSystemPaths;
|
|
|
|
std::vector<std::string> emulatorStaticPaths;
|
|
|
|
std::string emulatorEntry;
|
|
|
|
size_t endPos = 0;
|
|
|
|
|
|
|
|
if (command.find("%EMULATOR_", 0) == 0) {
|
|
|
|
endPos = command.find("%", 1);
|
|
|
|
if (endPos != std::string::npos)
|
|
|
|
emulatorEntry = command.substr(10, endPos - 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (emulatorEntry != "") {
|
|
|
|
emulatorSystemPaths = SystemData::sFindRules.get()->mEmulators[emulatorEntry].systemPaths;
|
|
|
|
emulatorStaticPaths = SystemData::sFindRules.get()->mEmulators[emulatorEntry].staticPaths;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error handling in case of no emulator find rule.
|
|
|
|
if (emulatorEntry != "" && emulatorSystemPaths.empty() && emulatorStaticPaths.empty())
|
|
|
|
return "NO EMULATOR RULE: " + emulatorEntry;
|
|
|
|
|
|
|
|
for (std::string path : emulatorSystemPaths) {
|
|
|
|
#if defined(_WIN64)
|
|
|
|
std::wstring pathWide = Utils::String::stringToWideString(path);
|
|
|
|
// Search for the emulator using the PATH environmental variable.
|
|
|
|
DWORD size = SearchPathW(nullptr, pathWide.c_str(), L".exe", 0, nullptr, nullptr);
|
|
|
|
|
|
|
|
if (size) {
|
|
|
|
std::vector<wchar_t> pathBuffer(static_cast<size_t>(size) + 1 );
|
|
|
|
wchar_t* fileName = nullptr;
|
|
|
|
|
|
|
|
SearchPathW(nullptr, pathWide.c_str(), L".exe", size + 1 ,
|
|
|
|
pathBuffer.data(), &fileName);
|
|
|
|
std::wstring pathString = pathBuffer.data();
|
|
|
|
|
|
|
|
if (pathString.length()) {
|
|
|
|
exePath = Utils::String::wideStringToString(pathString.substr(0,
|
|
|
|
pathString.size() - std::wstring(fileName).size()));
|
|
|
|
exePath.pop_back();
|
|
|
|
}
|
|
|
|
}
|
2021-06-20 10:30:14 +00:00
|
|
|
if (exePath != "") {
|
|
|
|
exePath += "\\" + path;
|
|
|
|
command.replace(0, endPos + 1, exePath);
|
|
|
|
return exePath;
|
|
|
|
}
|
2021-06-19 12:09:14 +00:00
|
|
|
#else
|
|
|
|
exePath = Utils::FileSystem::getPathToBinary(path);
|
|
|
|
if (exePath != "") {
|
|
|
|
exePath += "/" + path;
|
|
|
|
command.replace(0, endPos + 1, exePath);
|
|
|
|
return exePath;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
for (std::string path : emulatorStaticPaths) {
|
2021-06-23 15:49:47 +00:00
|
|
|
path = Utils::FileSystem::expandHomePath(path);
|
|
|
|
#if defined(_WIN64)
|
|
|
|
path = Utils::String::replace(path, "/", "\\");
|
|
|
|
#endif
|
2021-06-19 12:09:14 +00:00
|
|
|
if (Utils::FileSystem::isRegularFile(path) ||
|
|
|
|
Utils::FileSystem::isSymlink(path)) {
|
|
|
|
command.replace(0, endPos + 1, path);
|
2021-06-20 10:30:14 +00:00
|
|
|
return path;
|
2021-06-19 12:09:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Method 2, exact emulator binary name:
|
|
|
|
|
2021-01-10 21:55:17 +00:00
|
|
|
// If the first character is a quotation mark, then we need to extract up to the
|
|
|
|
// next quotation mark, otherwise we'll only extract up to the first space character.
|
|
|
|
if (command.front() == '\"') {
|
2021-01-11 17:21:14 +00:00
|
|
|
std::string emuTemp = command.substr(1, std::string::npos);
|
2021-01-10 21:55:17 +00:00
|
|
|
emuExecutable = emuTemp.substr(0, emuTemp.find('"'));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
emuExecutable = command.substr(0, command.find(' '));
|
|
|
|
}
|
|
|
|
|
2021-01-11 17:21:14 +00:00
|
|
|
#if defined(_WIN64)
|
|
|
|
std::wstring emuExecutableWide = Utils::String::stringToWideString(emuExecutable);
|
2021-01-10 21:55:17 +00:00
|
|
|
// Search for the emulator using the PATH environmental variable.
|
2021-01-11 17:21:14 +00:00
|
|
|
DWORD size = SearchPathW(nullptr, emuExecutableWide.c_str(), L".exe", 0, nullptr, nullptr);
|
2021-01-10 21:55:17 +00:00
|
|
|
|
|
|
|
if (size) {
|
|
|
|
std::vector<wchar_t> pathBuffer(static_cast<size_t>(size) + 1 );
|
|
|
|
wchar_t* fileName = nullptr;
|
|
|
|
|
2021-01-11 17:21:14 +00:00
|
|
|
SearchPathW(nullptr, emuExecutableWide.c_str(), L".exe", size + 1 ,
|
2021-01-10 21:55:17 +00:00
|
|
|
pathBuffer.data(), &fileName);
|
|
|
|
std::wstring pathString = pathBuffer.data();
|
|
|
|
|
|
|
|
if (pathString.length()) {
|
2021-01-11 17:21:14 +00:00
|
|
|
exePath = Utils::String::wideStringToString(pathString.substr(0, pathString.size() -
|
|
|
|
std::wstring(fileName).size()));
|
2021-01-10 21:55:17 +00:00
|
|
|
exePath.pop_back();
|
|
|
|
}
|
|
|
|
}
|
2021-01-11 17:21:14 +00:00
|
|
|
#else
|
2021-01-10 21:55:17 +00:00
|
|
|
if (Utils::FileSystem::isRegularFile(emuExecutable) ||
|
|
|
|
Utils::FileSystem::isSymlink(emuExecutable))
|
|
|
|
exePath = Utils::FileSystem::getParent(emuExecutable);
|
|
|
|
else
|
|
|
|
exePath = Utils::FileSystem::getPathToBinary(emuExecutable);
|
2021-01-11 17:21:14 +00:00
|
|
|
#endif
|
|
|
|
|
2021-01-10 21:55:17 +00:00
|
|
|
return exePath;
|
|
|
|
}
|
|
|
|
|
2017-06-12 16:38:59 +00:00
|
|
|
CollectionFileData::CollectionFileData(FileData* file, SystemData* system)
|
2020-06-21 12:25:28 +00:00
|
|
|
: FileData(file->getSourceFileData()->getType(), file->getSourceFileData()->getPath(),
|
|
|
|
file->getSourceFileData()->getSystemEnvData(), system)
|
2017-06-12 16:38:59 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
// We use this constructor to create a clone of the filedata, and change its system.
|
|
|
|
mSourceFileData = file->getSourceFileData();
|
|
|
|
refreshMetadata();
|
2020-06-23 18:07:00 +00:00
|
|
|
mParent = nullptr;
|
2020-06-21 12:25:28 +00:00
|
|
|
metadata = mSourceFileData->metadata;
|
|
|
|
mSystemName = mSourceFileData->getSystem()->getName();
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CollectionFileData::~CollectionFileData()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
// Need to remove collection file data at the collection object destructor.
|
|
|
|
if (mParent)
|
|
|
|
mParent->removeChild(this);
|
2020-06-23 18:07:00 +00:00
|
|
|
mParent = nullptr;
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string CollectionFileData::getKey() {
|
2020-06-21 12:25:28 +00:00
|
|
|
return getFullPath();
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FileData* CollectionFileData::getSourceFileData()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
return mSourceFileData;
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CollectionFileData::refreshMetadata()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
metadata = mSourceFileData->metadata;
|
|
|
|
mDirty = true;
|
2017-06-12 16:38:59 +00:00
|
|
|
}
|
|
|
|
|
2019-08-25 15:23:02 +00:00
|
|
|
const std::string& CollectionFileData::getName()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mDirty) {
|
|
|
|
mCollectionFileName =
|
|
|
|
Utils::String::removeParenthesis(mSourceFileData->metadata.get("name"));
|
|
|
|
mCollectionFileName +=
|
|
|
|
" [" + Utils::String::toUpper(mSourceFileData->getSystem()->getName()) + "]";
|
|
|
|
mDirty = false;
|
|
|
|
}
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (Settings::getInstance()->getBool("CollectionShowSystemInfo"))
|
|
|
|
return mCollectionFileName;
|
2020-05-24 08:29:29 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return mSourceFileData->metadata.get("name");
|
2017-07-18 09:45:50 +00:00
|
|
|
}
|