mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-15 13:57:57 -07:00
1f1dae367d
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.
236 lines
6.7 KiB
C++
236 lines
6.7 KiB
C++
// 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
|