mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-21 05:09:34 -06:00
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:
235
Source/Core/UICommon/GameFileCache.cpp
Normal file
235
Source/Core/UICommon/GameFileCache.cpp
Normal file
@ -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
|
Reference in New Issue
Block a user