Unify ISOFile (wx) with GameFile (Qt) and put it in UICommon
The original reason I wanted to do this was so that we can replace the Android-specific code with this in the future, but of course, just deduplicating between DolphinWX and DolphinQt2 is nice too. Fixes: - DolphinQt2 showing the wrong size for split WBFS disc images. - DolphinQt2 being case sensitive when checking if a file is a DOL/ELF. - DolphinQt2 not detecting when a Wii banner has become available after the game list cache was created. Removes: - DolphinWX's ability to load PNGs as custom banners. But it was already rather broken (see https://bugs.dolphin-emu.org/issues/10365 and https://bugs.dolphin-emu.org/issues/10366). The reason I removed this was because PNG decoding relied on wx code and we don't have any good non-wx/Qt code for loading PNG files right now (let's not use SOIL), but we should be able to use libpng directly to implement PNG loading in the future. - DolphinQt2's ability to ignore a cached game if the last modified time differs. We currently don't have a non-wx/Qt way to get the time.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
set(SRCS
|
||||
CommandLineParse.cpp
|
||||
Disassembler.cpp
|
||||
GameFile.cpp
|
||||
GameFileCache.cpp
|
||||
UICommon.cpp
|
||||
USBUtils.cpp
|
||||
VideoUtils.cpp
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
// Copyright 2008 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Hash.h"
|
||||
#include "Common/IniFile.h"
|
||||
#include "Common/NandPaths.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "Core/Boot/Boot.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
#include "Core/TitleDatabase.h"
|
||||
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
#include "DiscIO/WiiSaveBanner.h"
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
static const std::string EMPTY_STRING;
|
||||
|
||||
const std::string& GameFile::Lookup(DiscIO::Language language,
|
||||
const std::map<DiscIO::Language, std::string>& strings)
|
||||
{
|
||||
auto end = strings.end();
|
||||
auto it = strings.find(language);
|
||||
if (it != end)
|
||||
return it->second;
|
||||
|
||||
// English tends to be a good fallback when the requested language isn't available
|
||||
if (language != DiscIO::Language::LANGUAGE_ENGLISH)
|
||||
{
|
||||
it = strings.find(DiscIO::Language::LANGUAGE_ENGLISH);
|
||||
if (it != end)
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// If English isn't available either, just pick something
|
||||
if (!strings.empty())
|
||||
return strings.cbegin()->second;
|
||||
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
|
||||
const std::string&
|
||||
GameFile::LookupUsingConfigLanguage(const std::map<DiscIO::Language, std::string>& strings) const
|
||||
{
|
||||
const bool wii = DiscIO::IsWii(m_platform);
|
||||
return Lookup(SConfig::GetInstance().GetCurrentLanguage(wii), strings);
|
||||
}
|
||||
|
||||
GameFile::GameFile(const std::string& path)
|
||||
: m_file_path(path), m_region(DiscIO::Region::UNKNOWN_REGION),
|
||||
m_country(DiscIO::Country::COUNTRY_UNKNOWN)
|
||||
{
|
||||
{
|
||||
std::string name, extension;
|
||||
SplitPath(m_file_path, nullptr, &name, &extension);
|
||||
m_file_name = name + extension;
|
||||
|
||||
std::unique_ptr<DiscIO::Volume> volume(DiscIO::CreateVolumeFromFilename(m_file_path));
|
||||
if (volume != nullptr)
|
||||
{
|
||||
m_platform = volume->GetVolumeType();
|
||||
|
||||
m_short_names = volume->GetShortNames();
|
||||
m_long_names = volume->GetLongNames();
|
||||
m_short_makers = volume->GetShortMakers();
|
||||
m_long_makers = volume->GetLongMakers();
|
||||
m_descriptions = volume->GetDescriptions();
|
||||
|
||||
m_region = volume->GetRegion();
|
||||
m_country = volume->GetCountry();
|
||||
m_blob_type = volume->GetBlobType();
|
||||
m_file_size = volume->GetRawSize();
|
||||
m_volume_size = volume->GetSize();
|
||||
|
||||
m_internal_name = volume->GetInternalName();
|
||||
m_game_id = volume->GetGameID();
|
||||
m_title_id = volume->GetTitleID().value_or(0);
|
||||
m_maker_id = volume->GetMakerID();
|
||||
m_revision = volume->GetRevision().value_or(0);
|
||||
m_disc_number = volume->GetDiscNumber().value_or(0);
|
||||
m_apploader_date = volume->GetApploaderDate();
|
||||
|
||||
m_volume_banner.buffer = volume->GetBanner(&m_volume_banner.width, &m_volume_banner.height);
|
||||
|
||||
m_valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsValid() && IsElfOrDol())
|
||||
{
|
||||
m_valid = true;
|
||||
m_file_size = File::GetSize(m_file_path);
|
||||
m_platform = DiscIO::Platform::ELF_DOL;
|
||||
m_blob_type = DiscIO::BlobType::DIRECTORY;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameFile::IsValid() const
|
||||
{
|
||||
if (!m_valid)
|
||||
return false;
|
||||
|
||||
if (m_platform == DiscIO::Platform::WII_WAD && !IOS::ES::IsChannel(m_title_id))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameFile::CustomNameChanged(const Core::TitleDatabase& title_database)
|
||||
{
|
||||
const auto type = m_platform == DiscIO::Platform::WII_WAD ?
|
||||
Core::TitleDatabase::TitleType::Channel :
|
||||
Core::TitleDatabase::TitleType::Other;
|
||||
m_pending.custom_name = title_database.GetTitleName(m_game_id, type);
|
||||
return m_custom_name != m_pending.custom_name;
|
||||
}
|
||||
|
||||
void GameFile::CustomNameCommit()
|
||||
{
|
||||
m_custom_name = std::move(m_pending.custom_name);
|
||||
}
|
||||
|
||||
bool GameFile::EmuStateChanged()
|
||||
{
|
||||
IniFile ini = SConfig::LoadGameIni(m_game_id, m_revision);
|
||||
ini.GetIfExists("EmuState", "EmulationStateId", &m_pending.emu_state.rating, 0);
|
||||
ini.GetIfExists("EmuState", "EmulationIssues", &m_pending.emu_state.issues, std::string());
|
||||
return m_emu_state != m_pending.emu_state;
|
||||
}
|
||||
|
||||
void GameFile::EmuStateCommit()
|
||||
{
|
||||
m_emu_state = std::move(m_pending.emu_state);
|
||||
}
|
||||
|
||||
void GameFile::EmuState::DoState(PointerWrap& p)
|
||||
{
|
||||
p.Do(rating);
|
||||
p.Do(issues);
|
||||
}
|
||||
|
||||
void GameBanner::DoState(PointerWrap& p)
|
||||
{
|
||||
p.Do(buffer);
|
||||
p.Do(width);
|
||||
p.Do(height);
|
||||
}
|
||||
|
||||
void GameFile::DoState(PointerWrap& p)
|
||||
{
|
||||
p.Do(m_valid);
|
||||
p.Do(m_file_path);
|
||||
p.Do(m_file_name);
|
||||
|
||||
p.Do(m_file_size);
|
||||
p.Do(m_volume_size);
|
||||
|
||||
p.Do(m_short_names);
|
||||
p.Do(m_long_names);
|
||||
p.Do(m_short_makers);
|
||||
p.Do(m_long_makers);
|
||||
p.Do(m_descriptions);
|
||||
p.Do(m_internal_name);
|
||||
p.Do(m_game_id);
|
||||
p.Do(m_title_id);
|
||||
p.Do(m_maker_id);
|
||||
|
||||
p.Do(m_region);
|
||||
p.Do(m_country);
|
||||
p.Do(m_platform);
|
||||
p.Do(m_blob_type);
|
||||
p.Do(m_revision);
|
||||
p.Do(m_disc_number);
|
||||
p.Do(m_apploader_date);
|
||||
|
||||
m_volume_banner.DoState(p);
|
||||
m_emu_state.DoState(p);
|
||||
p.Do(m_custom_name);
|
||||
}
|
||||
|
||||
bool GameFile::IsElfOrDol() const
|
||||
{
|
||||
if (m_file_path.size() < 4)
|
||||
return false;
|
||||
|
||||
std::string name_end = m_file_path.substr(m_file_path.size() - 4);
|
||||
std::transform(name_end.begin(), name_end.end(), name_end.begin(), ::tolower);
|
||||
return name_end == ".elf" || name_end == ".dol";
|
||||
}
|
||||
|
||||
bool GameFile::BannerChanged()
|
||||
{
|
||||
// Wii banners can only be read if there is a save file.
|
||||
// In case the cache was created without a save file existing,
|
||||
// let's try reading the save file again, because it might exist now.
|
||||
|
||||
if (!m_volume_banner.empty())
|
||||
return false;
|
||||
if (!DiscIO::IsWii(m_platform))
|
||||
return false;
|
||||
|
||||
m_volume_banner.buffer =
|
||||
DiscIO::WiiSaveBanner(m_title_id).GetBanner(&m_volume_banner.width, &m_volume_banner.height);
|
||||
if (m_volume_banner.buffer.empty())
|
||||
return false;
|
||||
|
||||
// We only reach here if m_volume_banner was empty, so we can always return true
|
||||
// without needing any extra check to know whether the banners are different
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameFile::BannerCommit()
|
||||
{
|
||||
m_volume_banner = std::move(m_pending.volume_banner);
|
||||
}
|
||||
|
||||
const std::string& GameFile::GetName(bool long_name) const
|
||||
{
|
||||
if (!m_custom_name.empty())
|
||||
return m_custom_name;
|
||||
|
||||
const std::string& name = long_name ? GetLongName() : GetShortName();
|
||||
if (!name.empty())
|
||||
return name;
|
||||
|
||||
// No usable name, return filename (better than nothing)
|
||||
return m_file_name;
|
||||
}
|
||||
|
||||
const std::string& GameFile::GetMaker(bool long_maker) const
|
||||
{
|
||||
const std::string& maker = long_maker ? GetLongMaker() : GetShortMaker();
|
||||
if (!maker.empty())
|
||||
return maker;
|
||||
|
||||
if (m_game_id.size() >= 6)
|
||||
return DiscIO::GetCompanyFromID(m_maker_id);
|
||||
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
|
||||
std::vector<DiscIO::Language> GameFile::GetLanguages() const
|
||||
{
|
||||
std::vector<DiscIO::Language> languages;
|
||||
// TODO: What if some languages don't have long names but have other strings?
|
||||
for (std::pair<DiscIO::Language, std::string> name : m_long_names)
|
||||
languages.push_back(name.first);
|
||||
return languages;
|
||||
}
|
||||
|
||||
std::string GameFile::GetUniqueIdentifier() const
|
||||
{
|
||||
const DiscIO::Language lang = DiscIO::Language::LANGUAGE_ENGLISH;
|
||||
std::vector<std::string> info;
|
||||
if (!GetGameID().empty())
|
||||
info.push_back(GetGameID());
|
||||
if (GetRevision() != 0)
|
||||
info.push_back("Revision " + std::to_string(GetRevision()));
|
||||
|
||||
std::string name(GetLongName(lang));
|
||||
if (name.empty())
|
||||
name = GetName();
|
||||
|
||||
int disc_number = GetDiscNumber() + 1;
|
||||
|
||||
std::string lower_name = name;
|
||||
std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(), ::tolower);
|
||||
if (disc_number > 1 &&
|
||||
lower_name.find(StringFromFormat("disc %i", disc_number)) == std::string::npos &&
|
||||
lower_name.find(StringFromFormat("disc%i", disc_number)) == std::string::npos)
|
||||
{
|
||||
std::string disc_text = "Disc ";
|
||||
info.push_back(disc_text + std::to_string(disc_number));
|
||||
}
|
||||
if (info.empty())
|
||||
return name;
|
||||
std::ostringstream ss;
|
||||
std::copy(info.begin(), info.end() - 1, std::ostream_iterator<std::string>(ss, ", "));
|
||||
ss << info.back();
|
||||
return name + " (" + ss.str() + ")";
|
||||
}
|
||||
|
||||
std::string GameFile::GetWiiFSPath() const
|
||||
{
|
||||
_assert_(DiscIO::IsWii(m_platform));
|
||||
return Common::GetTitleDataPath(m_title_id, Common::FROM_CONFIGURED_ROOT);
|
||||
}
|
||||
|
||||
} // namespace UICommon
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright 2008 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class TitleDatabase;
|
||||
}
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
enum class BlobType;
|
||||
enum class Country;
|
||||
enum class Language;
|
||||
enum class Region;
|
||||
enum class Platform;
|
||||
}
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
struct GameBanner
|
||||
{
|
||||
std::vector<u32> buffer{};
|
||||
int width{};
|
||||
int height{};
|
||||
bool empty() const { return buffer.empty(); }
|
||||
void DoState(PointerWrap& p);
|
||||
};
|
||||
|
||||
// This class caches the metadata of a DiscIO::Volume (or a DOL/ELF file).
|
||||
class GameFile final
|
||||
{
|
||||
public:
|
||||
GameFile() = default;
|
||||
explicit GameFile(const std::string& path);
|
||||
~GameFile() = default;
|
||||
|
||||
bool IsValid() const;
|
||||
const std::string& GetFilePath() const { return m_file_path; }
|
||||
const std::string& GetFileName() const { return m_file_name; }
|
||||
const std::string& GetName(bool long_name = true) const;
|
||||
const std::string& GetMaker(bool long_maker = true) const;
|
||||
const std::string& GetShortName(DiscIO::Language l) const { return Lookup(l, m_short_names); }
|
||||
const std::string& GetShortName() const { return LookupUsingConfigLanguage(m_short_names); }
|
||||
const std::string& GetLongName(DiscIO::Language l) const { return Lookup(l, m_long_names); }
|
||||
const std::string& GetLongName() const { return LookupUsingConfigLanguage(m_long_names); }
|
||||
const std::string& GetShortMaker(DiscIO::Language l) const { return Lookup(l, m_short_makers); }
|
||||
const std::string& GetShortMaker() const { return LookupUsingConfigLanguage(m_short_makers); }
|
||||
const std::string& GetLongMaker(DiscIO::Language l) const { return Lookup(l, m_long_makers); }
|
||||
const std::string& GetLongMaker() const { return LookupUsingConfigLanguage(m_long_makers); }
|
||||
const std::string& GetDescription(DiscIO::Language l) const { return Lookup(l, m_descriptions); }
|
||||
const std::string& GetDescription() const { return LookupUsingConfigLanguage(m_descriptions); }
|
||||
std::vector<DiscIO::Language> GetLanguages() const;
|
||||
const std::string& GetInternalName() const { return m_internal_name; }
|
||||
const std::string& GetGameID() const { return m_game_id; }
|
||||
u64 GetTitleID() const { return m_title_id; }
|
||||
const std::string& GetMakerID() const { return m_maker_id; }
|
||||
u16 GetRevision() const { return m_revision; }
|
||||
// 0 is the first disc, 1 is the second disc
|
||||
u8 GetDiscNumber() const { return m_disc_number; }
|
||||
std::string GetUniqueIdentifier() const;
|
||||
std::string GetWiiFSPath() const;
|
||||
DiscIO::Region GetRegion() const { return m_region; }
|
||||
DiscIO::Country GetCountry() const { return m_country; }
|
||||
DiscIO::Platform GetPlatform() const { return m_platform; }
|
||||
DiscIO::BlobType GetBlobType() const { return m_blob_type; }
|
||||
const std::string& GetApploaderDate() const { return m_apploader_date; }
|
||||
const std::string& GetIssues() const { return m_emu_state.issues; }
|
||||
int GetEmuState() const { return m_emu_state.rating; }
|
||||
u64 GetFileSize() const { return m_file_size; }
|
||||
u64 GetVolumeSize() const { return m_volume_size; }
|
||||
const GameBanner& GetBannerImage() const { return m_volume_banner; }
|
||||
void DoState(PointerWrap& p);
|
||||
bool BannerChanged();
|
||||
void BannerCommit();
|
||||
bool EmuStateChanged();
|
||||
void EmuStateCommit();
|
||||
bool CustomNameChanged(const Core::TitleDatabase& title_database);
|
||||
void CustomNameCommit();
|
||||
|
||||
private:
|
||||
struct EmuState
|
||||
{
|
||||
int rating{};
|
||||
std::string issues{};
|
||||
bool operator!=(const EmuState& rhs) const
|
||||
{
|
||||
return rating != rhs.rating || issues != rhs.issues;
|
||||
}
|
||||
void DoState(PointerWrap& p);
|
||||
};
|
||||
|
||||
static const std::string& Lookup(DiscIO::Language language,
|
||||
const std::map<DiscIO::Language, std::string>& strings);
|
||||
const std::string&
|
||||
LookupUsingConfigLanguage(const std::map<DiscIO::Language, std::string>& strings) const;
|
||||
bool IsElfOrDol() const;
|
||||
|
||||
// IMPORTANT: Nearly all data members must be save/restored in DoState.
|
||||
// If anything is changed, make sure DoState handles it properly and
|
||||
// CACHE_REVISION in GameFileCache.cpp is incremented.
|
||||
|
||||
bool m_valid{};
|
||||
std::string m_file_path{};
|
||||
std::string m_file_name{};
|
||||
|
||||
u64 m_file_size{};
|
||||
u64 m_volume_size{};
|
||||
|
||||
std::map<DiscIO::Language, std::string> m_short_names{};
|
||||
std::map<DiscIO::Language, std::string> m_long_names{};
|
||||
std::map<DiscIO::Language, std::string> m_short_makers{};
|
||||
std::map<DiscIO::Language, std::string> m_long_makers{};
|
||||
std::map<DiscIO::Language, std::string> m_descriptions{};
|
||||
std::string m_internal_name{};
|
||||
std::string m_game_id{};
|
||||
u64 m_title_id{};
|
||||
std::string m_maker_id{};
|
||||
|
||||
DiscIO::Region m_region{};
|
||||
DiscIO::Country m_country{};
|
||||
DiscIO::Platform m_platform{};
|
||||
DiscIO::BlobType m_blob_type{};
|
||||
u16 m_revision{};
|
||||
u8 m_disc_number{};
|
||||
std::string m_apploader_date{};
|
||||
|
||||
GameBanner m_volume_banner{};
|
||||
EmuState m_emu_state{};
|
||||
// Overridden name from TitleDatabase
|
||||
std::string m_custom_name{};
|
||||
|
||||
// The following data members allow GameFileCache to construct updated versions
|
||||
// of GameFiles in a threadsafe way. They should not be handled in DoState.
|
||||
struct
|
||||
{
|
||||
EmuState emu_state;
|
||||
GameBanner volume_banner;
|
||||
std::string custom_name;
|
||||
} m_pending{};
|
||||
};
|
||||
|
||||
} // namespace UICommon
|
||||
@@ -0,0 +1,235 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "UICommon/GameFileCache.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/File.h"
|
||||
#include "Common/FileSearch.h"
|
||||
#include "Common/FileUtil.h"
|
||||
|
||||
#include "Core/TitleDatabase.h"
|
||||
|
||||
#include "DiscIO/DirectoryBlob.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
static constexpr u32 CACHE_REVISION = 7; // Last changed in PR 6281
|
||||
|
||||
std::vector<std::string> FindAllGamePaths(const std::vector<std::string>& directories_to_scan,
|
||||
bool recursive_scan)
|
||||
{
|
||||
static const std::vector<std::string> search_extensions = {
|
||||
".gcm", ".tgc", ".iso", ".ciso", ".gcz", ".wbfs", ".wad", ".dol", ".elf"};
|
||||
|
||||
// TODO: We could process paths iteratively as they are found
|
||||
return Common::DoFileSearch(directories_to_scan, search_extensions, recursive_scan);
|
||||
}
|
||||
|
||||
void GameFileCache::ForEach(std::function<void(const std::shared_ptr<const GameFile>&)> f) const
|
||||
{
|
||||
for (const std::shared_ptr<const GameFile>& item : m_cached_files)
|
||||
f(item);
|
||||
}
|
||||
|
||||
void GameFileCache::Clear()
|
||||
{
|
||||
m_cached_files.clear();
|
||||
}
|
||||
|
||||
std::shared_ptr<const GameFile> GameFileCache::AddOrGet(const std::string& path,
|
||||
bool* cache_changed,
|
||||
const Core::TitleDatabase& title_database)
|
||||
{
|
||||
auto it = std::find_if(
|
||||
m_cached_files.begin(), m_cached_files.end(),
|
||||
[&path](const std::shared_ptr<GameFile>& file) { return file->GetFilePath() == path; });
|
||||
const bool found = it != m_cached_files.cend();
|
||||
if (!found)
|
||||
m_cached_files.emplace_back(std::make_shared<GameFile>(path));
|
||||
std::shared_ptr<GameFile>& result = found ? *it : m_cached_files.back();
|
||||
if (UpdateAdditionalMetadata(&result, title_database) || !found)
|
||||
*cache_changed = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool GameFileCache::Update(const std::vector<std::string>& all_game_paths)
|
||||
{
|
||||
// Copy game paths into a set, except ones that match DiscIO::ShouldHideFromGameList.
|
||||
// TODO: Prevent DoFileSearch from looking inside /files/ directories of DirectoryBlobs at all?
|
||||
// TODO: Make DoFileSearch support filter predicates so we don't have remove things afterwards?
|
||||
std::unordered_set<std::string> game_paths;
|
||||
game_paths.reserve(all_game_paths.size());
|
||||
for (const std::string& path : all_game_paths)
|
||||
{
|
||||
if (!DiscIO::ShouldHideFromGameList(path))
|
||||
game_paths.insert(path);
|
||||
}
|
||||
|
||||
bool cache_changed = false;
|
||||
|
||||
// Delete paths that aren't in game_paths from m_cached_files,
|
||||
// while simultaneously deleting paths that aren't in m_cached_files from game_paths.
|
||||
// For the sake of speed, we don't care about maintaining the order of m_cached_files.
|
||||
{
|
||||
auto it = m_cached_files.begin();
|
||||
auto end = m_cached_files.end();
|
||||
while (it != end)
|
||||
{
|
||||
if (game_paths.erase((*it)->GetFilePath()))
|
||||
{
|
||||
++it;
|
||||
}
|
||||
else
|
||||
{
|
||||
cache_changed = true;
|
||||
--end;
|
||||
*it = std::move(*end);
|
||||
}
|
||||
}
|
||||
m_cached_files.erase(it, m_cached_files.end());
|
||||
}
|
||||
|
||||
// Now that the previous loop has run, game_paths only contains paths that
|
||||
// aren't in m_cached_files, so we simply add all of them to m_cached_files.
|
||||
for (const auto& path : game_paths)
|
||||
{
|
||||
auto file = std::make_shared<GameFile>(path);
|
||||
if (file->IsValid())
|
||||
{
|
||||
cache_changed = true;
|
||||
m_cached_files.push_back(std::move(file));
|
||||
}
|
||||
}
|
||||
|
||||
return cache_changed;
|
||||
}
|
||||
|
||||
bool GameFileCache::UpdateAdditionalMetadata(const Core::TitleDatabase& title_database)
|
||||
{
|
||||
bool cache_changed = false;
|
||||
|
||||
for (auto& file : m_cached_files)
|
||||
cache_changed |= UpdateAdditionalMetadata(&file, title_database);
|
||||
|
||||
return cache_changed;
|
||||
}
|
||||
|
||||
bool GameFileCache::UpdateAdditionalMetadata(std::shared_ptr<GameFile>* game_file,
|
||||
const Core::TitleDatabase& title_database)
|
||||
{
|
||||
const bool emu_state_changed = (*game_file)->EmuStateChanged();
|
||||
const bool banner_changed = (*game_file)->BannerChanged();
|
||||
const bool custom_title_changed = (*game_file)->CustomNameChanged(title_database);
|
||||
if (!emu_state_changed && !banner_changed && !custom_title_changed)
|
||||
return false;
|
||||
|
||||
// If a cached file needs an update, apply the updates to a copy and delete the original.
|
||||
// This makes the usage of cached files in other threads safe.
|
||||
|
||||
std::shared_ptr<GameFile> copy = std::make_shared<GameFile>(**game_file);
|
||||
if (emu_state_changed)
|
||||
copy->EmuStateCommit();
|
||||
if (banner_changed)
|
||||
copy->BannerCommit();
|
||||
if (custom_title_changed)
|
||||
copy->CustomNameCommit();
|
||||
*game_file = std::move(copy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameFileCache::Load()
|
||||
{
|
||||
return SyncCacheFile(false);
|
||||
}
|
||||
|
||||
bool GameFileCache::Save()
|
||||
{
|
||||
return SyncCacheFile(true);
|
||||
}
|
||||
|
||||
bool GameFileCache::SyncCacheFile(bool save)
|
||||
{
|
||||
std::string filename(File::GetUserPath(D_CACHE_IDX) + "gamelist.cache");
|
||||
const char* open_mode = save ? "wb" : "rb";
|
||||
File::IOFile f(filename, open_mode);
|
||||
if (!f)
|
||||
return false;
|
||||
bool success = false;
|
||||
if (save)
|
||||
{
|
||||
// Measure the size of the buffer.
|
||||
u8* ptr = nullptr;
|
||||
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
|
||||
DoState(&p);
|
||||
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
|
||||
|
||||
// Then actually do the write.
|
||||
std::vector<u8> buffer(buffer_size);
|
||||
ptr = &buffer[0];
|
||||
p.SetMode(PointerWrap::MODE_WRITE);
|
||||
DoState(&p, buffer_size);
|
||||
if (f.WriteBytes(buffer.data(), buffer.size()))
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<u8> buffer(f.GetSize());
|
||||
if (buffer.size() && f.ReadBytes(buffer.data(), buffer.size()))
|
||||
{
|
||||
u8* ptr = buffer.data();
|
||||
PointerWrap p(&ptr, PointerWrap::MODE_READ);
|
||||
DoState(&p, buffer.size());
|
||||
if (p.GetMode() == PointerWrap::MODE_READ)
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
if (!success)
|
||||
{
|
||||
// If some file operation failed, try to delete the probably-corrupted cache
|
||||
f.Close();
|
||||
File::Delete(filename);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void GameFileCache::DoState(PointerWrap* p, u64 size)
|
||||
{
|
||||
struct
|
||||
{
|
||||
u32 revision;
|
||||
u64 expected_size;
|
||||
} header = {CACHE_REVISION, size};
|
||||
p->Do(header);
|
||||
if (p->GetMode() == PointerWrap::MODE_READ)
|
||||
{
|
||||
if (header.revision != CACHE_REVISION || header.expected_size != size)
|
||||
{
|
||||
p->SetMode(PointerWrap::MODE_MEASURE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
p->DoEachElement(m_cached_files, [](PointerWrap& state, std::shared_ptr<GameFile>& elem) {
|
||||
if (state.GetMode() == PointerWrap::MODE_READ)
|
||||
elem = std::make_shared<GameFile>();
|
||||
elem->DoState(state);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace DiscIO
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class TitleDatabase;
|
||||
}
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
class GameFile;
|
||||
|
||||
std::vector<std::string> FindAllGamePaths(const std::vector<std::string>& directories_to_scan,
|
||||
bool recursive_scan);
|
||||
|
||||
class GameFileCache
|
||||
{
|
||||
public:
|
||||
void ForEach(std::function<void(const std::shared_ptr<const GameFile>&)> f) const;
|
||||
|
||||
void Clear();
|
||||
|
||||
std::shared_ptr<const GameFile> AddOrGet(const std::string& path, bool* cache_changed,
|
||||
const Core::TitleDatabase& title_database);
|
||||
|
||||
// These functions return true if the call modified the cache.
|
||||
bool Update(const std::vector<std::string>& all_game_paths);
|
||||
bool UpdateAdditionalMetadata(const Core::TitleDatabase& title_database);
|
||||
|
||||
bool Load();
|
||||
bool Save();
|
||||
|
||||
private:
|
||||
bool UpdateAdditionalMetadata(std::shared_ptr<GameFile>* game_file,
|
||||
const Core::TitleDatabase& title_database);
|
||||
|
||||
bool SyncCacheFile(bool save);
|
||||
void DoState(PointerWrap* p, u64 size = 0);
|
||||
|
||||
std::vector<std::shared_ptr<GameFile>> m_cached_files;
|
||||
};
|
||||
|
||||
} // namespace UICommon
|
||||
@@ -2,16 +2,20 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#ifdef _WIN32
|
||||
#include <shlobj.h> // for SHGetFolderPath
|
||||
#endif
|
||||
|
||||
#include "Common/Common.h"
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/LogManager.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "Core/ConfigLoaders/BaseConfigLoader.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
@@ -313,4 +317,21 @@ void EnableScreenSaver(bool enable)
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string FormatSize(u64 bytes)
|
||||
{
|
||||
// i18n: The symbol for the unit "bytes"
|
||||
const char* const unit_symbols[] = {_trans("B"), _trans("KiB"), _trans("MiB"), _trans("GiB"),
|
||||
_trans("TiB"), _trans("PiB"), _trans("EiB")};
|
||||
|
||||
// Find largest power of 2 less than size.
|
||||
// div 10 to get largest named unit less than size
|
||||
// 10 == log2(1024) (number of B in a KiB, KiB in a MiB, etc)
|
||||
// Max value is 63 / 10 = 6
|
||||
const int unit = IntLog2(std::max<u64>(bytes, 1)) / 10;
|
||||
|
||||
// Don't need exact values, only 5 most significant digits
|
||||
const double unit_size = std::pow(2, unit * 10);
|
||||
return StringFromFormat("%.2f %s", bytes / unit_size, GetStringT(unit_symbols[unit]).c_str());
|
||||
}
|
||||
|
||||
} // namespace UICommon
|
||||
|
||||
@@ -25,4 +25,8 @@ void SetUserDirectory(const std::string& custom_path);
|
||||
bool TriggerSTMPowerEvent();
|
||||
|
||||
void SaveWiimoteSources();
|
||||
|
||||
// Return a pretty file size string from byte count.
|
||||
// e.g. 1134278 -> "1.08 MiB"
|
||||
std::string FormatSize(u64 bytes);
|
||||
} // namespace UICommon
|
||||
|
||||
@@ -51,12 +51,16 @@
|
||||
<DisableSpecificWarnings>4200;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VideoUtils.cpp" />
|
||||
<ClCompile Include="GameFile.cpp" />
|
||||
<ClCompile Include="GameFileCache.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CommandLineParse.h" />
|
||||
<ClInclude Include="UICommon.h" />
|
||||
<ClInclude Include="Disassembler.h" />
|
||||
<ClInclude Include="USBUtils.h" />
|
||||
<ClInclude Include="GameFile.h" />
|
||||
<ClInclude Include="GameFileCache.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(ExternalsDir)cpp-optparse\cpp-optparse.vcxproj">
|
||||
|
||||
Reference in New Issue
Block a user