diff --git a/Source/Core/DolphinQt/GameList/GameFile.cpp b/Source/Core/DolphinQt/GameList/GameFile.cpp index 03c3ddd037..f10dd0f201 100644 --- a/Source/Core/DolphinQt/GameList/GameFile.cpp +++ b/Source/Core/DolphinQt/GameList/GameFile.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include #include #include #include #include #include +#include #include "Common/Common.h" #include "Common/CommonPaths.h" @@ -24,7 +26,7 @@ #include "DolphinQt/GameList/GameFile.h" #include "DolphinQt/Utils/Utils.h" -static const u32 CACHE_REVISION = 0x00B; // Last changed in PR 2598 +static const u32 CACHE_REVISION = 0x00C; // Last changed in PR 2993 static const u32 DATASTREAM_REVISION = 15; // Introduced in Qt 5.2 static QMap ConvertLocalizedStrings(std::map strings) @@ -71,6 +73,10 @@ static QString GetLanguageString(DiscIO::IVolume::ELanguage language, QMapGetDiscNumber(); m_revision = volume->GetRevision(); - QFileInfo info(m_file_name); - m_folder_name = info.absoluteDir().dirName(); - ReadBanner(*volume); m_valid = true; @@ -132,6 +135,27 @@ GameFile::GameFile(const QString& fileName) ini.GetIfExists("EmuState", "EmulationIssues", &issues_temp); m_issues = QString::fromStdString(issues_temp); } + + if (!IsValid() && IsElfOrDol()) + { + m_valid = true; + m_file_size = info.size(); + m_platform = DiscIO::IVolume::ELF_DOL; + } + + // Metadata can optionally be stored in XML and PNG files. Typical for DOLs and ELFs, but also works + // with volumes. icon.png and meta.xml are the file names used by Homebrew Channel. The ability to use + // files with the same name as the main file is provided as an alternative for those who want to have + // multiple files in one folder instead of having a Homebrew Channel-style folder structure. + + if (!ReadXML(directory.filePath(info.baseName() + SL(".xml")))) + ReadXML(directory.filePath(SL("meta.xml"))); + + QImage banner(directory.filePath(info.baseName() + SL(".png"))); + if (banner.isNull()) + banner.load(directory.filePath(SL("icon.png"))); + if (!banner.isNull()) + m_banner = QPixmap::fromImage(banner); } bool GameFile::LoadFromCache() @@ -160,8 +184,7 @@ bool GameFile::LoadFromCache() QMap short_names; QMap long_names; QMap descriptions; - stream >> m_folder_name - >> short_names + stream >> short_names >> long_names >> descriptions >> m_company @@ -203,8 +226,7 @@ void GameFile::SaveToCache() stream.setVersion(DATASTREAM_REVISION); stream << CACHE_REVISION; - stream << m_folder_name - << CastLocalizedStrings(m_short_names) + stream << CastLocalizedStrings(m_short_names) << CastLocalizedStrings(m_long_names) << CastLocalizedStrings(m_descriptions) << m_company @@ -219,7 +241,22 @@ void GameFile::SaveToCache() << m_revision; } -QString GameFile::CreateCacheFilename() +bool GameFile::IsElfOrDol() const +{ + const std::string name = m_file_name.toStdString(); + const size_t pos = name.rfind('.'); + + if (pos != std::string::npos) + { + std::string ext = name.substr(pos); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + + return ext == ".elf" || ext == ".dol"; + } + return false; +} + +QString GameFile::CreateCacheFilename() const { std::string filename, pathname, extension; SplitPath(m_file_name.toStdString(), &pathname, &filename, &extension); @@ -238,6 +275,7 @@ QString GameFile::CreateCacheFilename() return fullname; } +// Outputs to m_banner void GameFile::ReadBanner(const DiscIO::IVolume& volume) { int width, height; @@ -255,6 +293,50 @@ void GameFile::ReadBanner(const DiscIO::IVolume& volume) m_banner = QPixmap::fromImage(banner); } +// Outputs to m_short_names, m_long_names, m_descriptions, m_company. +// Returns whether a file was found, not whether it contained useful data. +bool GameFile::ReadXML(const QString& file_path) +{ + // The format of Homebrew Channel XML metadata is described at: + // http://wiibrew.org/wiki/Homebrew_Channel#Adding_Text + + QFile file(file_path); + if (!file.open(QIODevice::ReadOnly)) + return false; + + QXmlStreamReader reader(&file); + if (reader.readNextStartElement() && reader.name() == SL("app")) + { + while (reader.readNextStartElement()) + { + QStringRef name = reader.name(); + if (name == SL("name")) + { + m_short_names = { { DiscIO::IVolume::LANGUAGE_UNKNOWN, reader.readElementText() } }; + m_long_names = m_short_names; + } + else if (name == SL("short_description")) + { + m_descriptions = { { DiscIO::IVolume::LANGUAGE_UNKNOWN, reader.readElementText() } }; + } + else if (name == SL("coder")) + { + m_company = reader.readElementText(); + } + else + { + reader.skipCurrentElement(); + } + // Elements that we aren't using: + // version (can be written in any format) + // release_date (YYYYmmddHHMMSS format) + // long_description (can be several screens long!) + } + } + + return true; +} + QString GameFile::GetDescription(DiscIO::IVolume::ELanguage language) const { return GetLanguageString(language, m_descriptions); @@ -278,9 +360,9 @@ QString GameFile::GetName(bool prefer_long) const if (name.isEmpty()) { // No usable name, return filename (better than nothing) - std::string nametemp; - SplitPath(m_file_name.toStdString(), nullptr, &nametemp, nullptr); - name = QString::fromStdString(nametemp); + std::string name_temp, extension; + SplitPath(m_file_name.toStdString(), nullptr, &name_temp, &extension); + name = QString::fromStdString(name_temp + extension); } return name; } diff --git a/Source/Core/DolphinQt/GameList/GameFile.h b/Source/Core/DolphinQt/GameList/GameFile.h index e62fcd1a79..c9e2da0c37 100644 --- a/Source/Core/DolphinQt/GameList/GameFile.h +++ b/Source/Core/DolphinQt/GameList/GameFile.h @@ -66,7 +66,7 @@ private: quint64 m_file_size = 0; quint64 m_volume_size = 0; - DiscIO::IVolume::ECountry m_country; + DiscIO::IVolume::ECountry m_country = DiscIO::IVolume::COUNTRY_UNKNOWN; DiscIO::IVolume::EPlatform m_platform; u16 m_revision = 0; @@ -78,7 +78,12 @@ private: bool LoadFromCache(); void SaveToCache(); - QString CreateCacheFilename(); + bool IsElfOrDol() const; + QString CreateCacheFilename() const; + // Outputs to m_banner void ReadBanner(const DiscIO::IVolume& volume); + // Outputs to m_short_names, m_long_names, m_descriptions, m_company. + // Returns whether a file was found, not whether it contained useful data. + bool ReadXML(const QString& file_path); }; diff --git a/Source/Core/DolphinQt/GameList/GameTracker.cpp b/Source/Core/DolphinQt/GameList/GameTracker.cpp index 80bceacf0b..cee3e94a62 100644 --- a/Source/Core/DolphinQt/GameList/GameTracker.cpp +++ b/Source/Core/DolphinQt/GameList/GameTracker.cpp @@ -102,6 +102,11 @@ void DGameTracker::ScanForGames() } if (SConfig::GetInstance().m_ListWad) exts.push_back("*.wad"); + if (SConfig::GetInstance().m_ListElfDol) + { + exts.push_back("*.dol"); + exts.push_back("*.elf"); + } auto rFilenames = DoFileSearch(exts, SConfig::GetInstance().m_ISOFolder, SConfig::GetInstance().m_RecursiveISOFolder); QList newItems; diff --git a/Source/Core/DolphinQt/Utils/Resources.cpp b/Source/Core/DolphinQt/Utils/Resources.cpp index 4450e72dfb..09abbf5291 100644 --- a/Source/Core/DolphinQt/Utils/Resources.cpp +++ b/Source/Core/DolphinQt/Utils/Resources.cpp @@ -40,7 +40,7 @@ void Resources::Init() m_regions[DiscIO::IVolume::COUNTRY_WORLD].load(GIFN("Flag_Europe")); // Uses European flag as a placeholder m_regions[DiscIO::IVolume::COUNTRY_UNKNOWN].load(GIFN("Flag_Unknown")); - m_platforms.resize(3); + m_platforms.resize(4); m_platforms[0].load(GIFN("Platform_Gamecube")); m_platforms[1].load(GIFN("Platform_Wii")); m_platforms[2].load(GIFN("Platform_Wad")); @@ -77,6 +77,8 @@ void Resources::UpdatePixmaps() // TODO: toolbar[MEMCARD]; // TODO: toolbar[HOTKEYS]; m_pixmaps[BANNER_MISSING].load(GIFN("nobanner")); + // TODO: Make this consistent with the other files + m_platforms[3].load(GIFN("fileplatform")); } QString Resources::GetImageFilename(QString name, QString dir)