dolphin/Source/Core/UICommon/GameFileCache.cpp
JosJuice cec601f1fb Read Wii disc metadata from the unencrypted header
The header of a Wii disc can be read from two places: The
unencrypted area at the beginning of the disc, or the beginning of
the game partition. The two copies are usually identical (except
for 0x60 and 0x61), but there are exceptions. For most of Dolphin's
history, we have been reading from the header inside the game
partition when getting metadata. This was however not the case
starting with 4.0-4901 and ending with 5.0-3762. This commit once
again makes Dolphin read metadata from the unencrypted header,
because of the following reasons that I recently was informed about:

- The "pink fish" disc has the game ID 410E01 in the unencrypted
  header but the placeholder game ID RELSAB in the partition header.
- The revisions of some games differ between the two headers,
  with the unencrypted one making more sense.
  (See https://bugs.dolphin-emu.org/issues/11387)

For better or worse, this also means that sloppily hacked games where
only the game ID in the unencrypted header has been changed now will
use that modified game ID. And unlike with the partition header,
there is no signing or hashing that can tell us whether the
unencrypted header has been modified by someone other than Nintendo.
2018-09-12 13:41:30 +02:00

273 lines
7.4 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 <utility>
#include <vector>
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/File.h"
#include "Common/FileSearch.h"
#include "Common/FileUtil.h"
#include "DiscIO/DirectoryBlob.h"
#include "UICommon/GameFile.h"
namespace UICommon
{
static constexpr u32 CACHE_REVISION = 13; // Last changed in PR 7411
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);
}
GameFileCache::GameFileCache() : m_path(File::GetUserPath(D_CACHE_IDX) + "gamelist.cache")
{
}
GameFileCache::GameFileCache(std::string path) : m_path(std::move(path))
{
}
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);
}
size_t GameFileCache::GetSize() const
{
return m_cached_files.size();
}
void GameFileCache::Clear()
{
m_cached_files.clear();
}
std::shared_ptr<const GameFile> GameFileCache::AddOrGet(const std::string& path,
bool* cache_changed)
{
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)
{
std::shared_ptr<UICommon::GameFile> game = std::make_shared<GameFile>(path);
if (!game->IsValid())
return nullptr;
m_cached_files.emplace_back(std::move(game));
}
std::shared_ptr<GameFile>& result = found ? *it : m_cached_files.back();
if (UpdateAdditionalMetadata(&result) || !found)
*cache_changed = true;
return result;
}
bool GameFileCache::Update(
const std::vector<std::string>& all_game_paths,
std::function<void(const std::shared_ptr<const GameFile>&)> game_added_to_cache,
std::function<void(const std::string&)> game_removed_from_cache)
{
// 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 are 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
{
if (game_removed_from_cache)
game_removed_from_cache((*it)->GetFilePath());
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 std::string& path : game_paths)
{
auto file = std::make_shared<GameFile>(path);
if (file->IsValid())
{
if (game_added_to_cache)
game_added_to_cache(file);
cache_changed = true;
m_cached_files.push_back(std::move(file));
}
}
return cache_changed;
}
bool GameFileCache::UpdateAdditionalMetadata(
std::function<void(const std::shared_ptr<const GameFile>&)> game_updated)
{
bool cache_changed = false;
for (std::shared_ptr<GameFile>& file : m_cached_files)
{
const bool updated = UpdateAdditionalMetadata(&file);
cache_changed |= updated;
if (game_updated && updated)
game_updated(file);
}
return cache_changed;
}
bool GameFileCache::UpdateAdditionalMetadata(std::shared_ptr<GameFile>* game_file)
{
const bool wii_banner_changed = (*game_file)->WiiBannerChanged();
const bool custom_banner_changed = (*game_file)->CustomBannerChanged();
(*game_file)->DownloadDefaultCover();
const bool default_cover_changed = (*game_file)->DefaultCoverChanged();
const bool custom_cover_changed = (*game_file)->CustomCoverChanged();
if (!wii_banner_changed && !custom_banner_changed && !default_cover_changed &&
!custom_cover_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 (wii_banner_changed)
copy->WiiBannerCommit();
if (custom_banner_changed)
copy->CustomBannerCommit();
if (default_cover_changed)
copy->DefaultCoverCommit();
if (custom_cover_changed)
copy->CustomCoverCommit();
*game_file = std::move(copy);
return true;
}
bool GameFileCache::Load()
{
return SyncCacheFile(false);
}
bool GameFileCache::Save()
{
return SyncCacheFile(true);
}
bool GameFileCache::SyncCacheFile(bool save)
{
const char* open_mode = save ? "wb" : "rb";
File::IOFile f(m_path, 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(m_path);
}
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