mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-29 00:59:44 -06:00
Merge pull request #5746 from leoetlino/disc-updates
Add support for installing disc updates from the game list
This commit is contained in:
@ -5,7 +5,10 @@
|
||||
#include "Core/WiiUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@ -32,15 +35,19 @@
|
||||
#include "Core/IOS/ES/ES.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "DiscIO/DiscExtractor.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/Filesystem.h"
|
||||
#include "DiscIO/NANDContentLoader.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
#include "DiscIO/VolumeFileBlobReader.h"
|
||||
#include "DiscIO/VolumeWii.h"
|
||||
#include "DiscIO/WiiWad.h"
|
||||
|
||||
namespace WiiUtils
|
||||
{
|
||||
bool InstallWAD(const std::string& wad_path)
|
||||
bool InstallWAD(IOS::HLE::Kernel& ios, const DiscIO::WiiWAD& wad)
|
||||
{
|
||||
const DiscIO::WiiWAD wad{wad_path};
|
||||
if (!wad.IsValid())
|
||||
{
|
||||
PanicAlertT("WAD installation failed: The selected file is not a valid WAD.");
|
||||
@ -48,7 +55,6 @@ bool InstallWAD(const std::string& wad_path)
|
||||
}
|
||||
|
||||
const auto tmd = wad.GetTMD();
|
||||
IOS::HLE::Kernel ios;
|
||||
const auto es = ios.GetES();
|
||||
|
||||
IOS::HLE::Device::ES::Context context;
|
||||
@ -99,10 +105,19 @@ bool InstallWAD(const std::string& wad_path)
|
||||
return false;
|
||||
}
|
||||
|
||||
DiscIO::NANDContentManager::Access().ClearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InstallWAD(const std::string& wad_path)
|
||||
{
|
||||
IOS::HLE::Kernel ios;
|
||||
const DiscIO::WiiWAD wad{wad_path};
|
||||
const bool result = InstallWAD(ios, wad);
|
||||
|
||||
DiscIO::NANDContentManager::Access().ClearCache();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Common functionality for system updaters.
|
||||
class SystemUpdater
|
||||
{
|
||||
@ -118,7 +133,6 @@ protected:
|
||||
|
||||
std::string GetDeviceRegion();
|
||||
std::string GetDeviceId();
|
||||
bool ShouldInstallTitle(const TitleInfo& title);
|
||||
|
||||
IOS::HLE::Kernel m_ios;
|
||||
};
|
||||
@ -149,14 +163,6 @@ std::string SystemUpdater::GetDeviceId()
|
||||
return StringFromFormat("%" PRIu64, (u64(1) << 32) | ios_device_id);
|
||||
}
|
||||
|
||||
bool SystemUpdater::ShouldInstallTitle(const TitleInfo& title)
|
||||
{
|
||||
const auto es = m_ios.GetES();
|
||||
const auto installed_tmd = es->FindInstalledTMD(title.id);
|
||||
return !(installed_tmd.IsValid() && installed_tmd.GetTitleVersion() >= title.version &&
|
||||
es->GetStoredContentsFromTMD(installed_tmd).size() == installed_tmd.GetNumContents());
|
||||
}
|
||||
|
||||
class OnlineSystemUpdater final : public SystemUpdater
|
||||
{
|
||||
public:
|
||||
@ -172,6 +178,7 @@ private:
|
||||
|
||||
Response GetSystemTitles();
|
||||
Response ParseTitlesResponse(const std::vector<u8>& response) const;
|
||||
bool ShouldInstallTitle(const TitleInfo& title);
|
||||
|
||||
UpdateResult InstallTitleFromNUS(const std::string& prefix_url, const TitleInfo& title,
|
||||
std::unordered_set<u64>* updated_titles);
|
||||
@ -241,6 +248,14 @@ OnlineSystemUpdater::ParseTitlesResponse(const std::vector<u8>& response) const
|
||||
return info;
|
||||
}
|
||||
|
||||
bool OnlineSystemUpdater::ShouldInstallTitle(const TitleInfo& title)
|
||||
{
|
||||
const auto es = m_ios.GetES();
|
||||
const auto installed_tmd = es->FindInstalledTMD(title.id);
|
||||
return !(installed_tmd.IsValid() && installed_tmd.GetTitleVersion() >= title.version &&
|
||||
es->GetStoredContentsFromTMD(installed_tmd).size() == installed_tmd.GetNumContents());
|
||||
}
|
||||
|
||||
constexpr const char* GET_SYSTEM_TITLES_REQUEST_PAYLOAD = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
@ -493,6 +508,163 @@ std::optional<std::vector<u8>> OnlineSystemUpdater::DownloadContent(const std::s
|
||||
return m_http.Get(url);
|
||||
}
|
||||
|
||||
class DiscSystemUpdater final : public SystemUpdater
|
||||
{
|
||||
public:
|
||||
DiscSystemUpdater(UpdateCallback update_callback, const std::string& image_path)
|
||||
: m_update_callback{std::move(update_callback)},
|
||||
m_volume{DiscIO::CreateVolumeFromFilename(image_path)}
|
||||
{
|
||||
}
|
||||
UpdateResult DoDiscUpdate();
|
||||
|
||||
private:
|
||||
#pragma pack(push, 8)
|
||||
struct ManifestHeader
|
||||
{
|
||||
char timestamp[0x10]; // YYYY/MM/DD
|
||||
// There is a u32 in newer info files to indicate the number of entries,
|
||||
// but it's not used in older files, and it's not always at the same offset.
|
||||
// Too unreliable to use it.
|
||||
u32 padding[4];
|
||||
};
|
||||
static_assert(sizeof(ManifestHeader) == 32, "Wrong size");
|
||||
|
||||
struct Entry
|
||||
{
|
||||
u32 type;
|
||||
u32 attribute;
|
||||
u32 unknown1;
|
||||
u32 unknown2;
|
||||
char path[0x40];
|
||||
u64 title_id;
|
||||
u16 title_version;
|
||||
char name[0x40];
|
||||
char info[0x40];
|
||||
u8 unused[0x120];
|
||||
};
|
||||
static_assert(sizeof(Entry) == 512, "Wrong size");
|
||||
#pragma pack(pop)
|
||||
|
||||
UpdateResult UpdateFromManifest(const std::string& manifest_name);
|
||||
UpdateResult ProcessEntry(u32 type, std::bitset<32> attrs, const TitleInfo& title,
|
||||
const std::string& path);
|
||||
|
||||
UpdateCallback m_update_callback;
|
||||
std::unique_ptr<DiscIO::Volume> m_volume;
|
||||
std::unique_ptr<DiscIO::FileSystem> m_disc_fs;
|
||||
};
|
||||
|
||||
UpdateResult DiscSystemUpdater::DoDiscUpdate()
|
||||
{
|
||||
if (!m_volume)
|
||||
return UpdateResult::DiscReadFailed;
|
||||
|
||||
// Do not allow mismatched regions, because installing an update will automatically change
|
||||
// the Wii's region and may result in semi/full system menu bricks.
|
||||
const IOS::ES::TMDReader system_menu_tmd = m_ios.GetES()->FindInstalledTMD(Titles::SYSTEM_MENU);
|
||||
if (system_menu_tmd.IsValid() && m_volume->GetRegion() != system_menu_tmd.GetRegion())
|
||||
return UpdateResult::RegionMismatch;
|
||||
|
||||
const auto partitions = m_volume->GetPartitions();
|
||||
const auto update_partition =
|
||||
std::find_if(partitions.cbegin(), partitions.cend(), [&](const DiscIO::Partition& partition) {
|
||||
return m_volume->GetPartitionType(partition) == 1u;
|
||||
});
|
||||
|
||||
if (update_partition == partitions.cend())
|
||||
{
|
||||
ERROR_LOG(CORE, "Could not find any update partition");
|
||||
return UpdateResult::MissingUpdatePartition;
|
||||
}
|
||||
|
||||
m_disc_fs = DiscIO::CreateFileSystem(m_volume.get(), *update_partition);
|
||||
if (!m_disc_fs || !m_disc_fs->IsValid())
|
||||
return UpdateResult::DiscReadFailed;
|
||||
|
||||
return UpdateFromManifest("__update.inf");
|
||||
}
|
||||
|
||||
UpdateResult DiscSystemUpdater::UpdateFromManifest(const std::string& manifest_name)
|
||||
{
|
||||
const std::unique_ptr<DiscIO::FileInfo> update_manifest = m_disc_fs->FindFileInfo(manifest_name);
|
||||
if (!update_manifest ||
|
||||
(update_manifest->GetSize() - sizeof(ManifestHeader)) % sizeof(Entry) != 0)
|
||||
{
|
||||
ERROR_LOG(CORE, "Invalid or missing update manifest");
|
||||
return UpdateResult::DiscReadFailed;
|
||||
}
|
||||
|
||||
const u32 num_entries = (update_manifest->GetSize() - sizeof(ManifestHeader)) / sizeof(Entry);
|
||||
if (num_entries > 200)
|
||||
return UpdateResult::DiscReadFailed;
|
||||
|
||||
std::vector<u8> entry(sizeof(Entry));
|
||||
size_t updates_installed = 0;
|
||||
for (u32 i = 0; i < num_entries; ++i)
|
||||
{
|
||||
const u32 offset = sizeof(ManifestHeader) + sizeof(Entry) * i;
|
||||
if (entry.size() != DiscIO::ReadFile(*m_volume, m_disc_fs->GetPartition(),
|
||||
update_manifest.get(), entry.data(), entry.size(), offset))
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to read update information from update manifest");
|
||||
return UpdateResult::DiscReadFailed;
|
||||
}
|
||||
|
||||
const u32 type = Common::swap32(entry.data() + offsetof(Entry, type));
|
||||
const std::bitset<32> attrs = Common::swap32(entry.data() + offsetof(Entry, attribute));
|
||||
const u64 title_id = Common::swap64(entry.data() + offsetof(Entry, title_id));
|
||||
const u16 title_version = Common::swap16(entry.data() + offsetof(Entry, title_version));
|
||||
const char* path_pointer = reinterpret_cast<const char*>(entry.data() + offsetof(Entry, path));
|
||||
const std::string path{path_pointer, strnlen(path_pointer, sizeof(Entry::path))};
|
||||
|
||||
if (!m_update_callback(i, num_entries, title_id))
|
||||
return UpdateResult::Cancelled;
|
||||
|
||||
const UpdateResult res = ProcessEntry(type, attrs, {title_id, title_version}, path);
|
||||
if (res != UpdateResult::Succeeded && res != UpdateResult::AlreadyUpToDate)
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to update %016" PRIx64 " -- aborting update", title_id);
|
||||
return res;
|
||||
}
|
||||
|
||||
if (res == UpdateResult::Succeeded)
|
||||
++updates_installed;
|
||||
}
|
||||
return updates_installed == 0 ? UpdateResult::AlreadyUpToDate : UpdateResult::Succeeded;
|
||||
}
|
||||
|
||||
UpdateResult DiscSystemUpdater::ProcessEntry(u32 type, std::bitset<32> attrs,
|
||||
const TitleInfo& title, const std::string& path)
|
||||
{
|
||||
// Skip any unknown type and boot2 updates (for now).
|
||||
if (type != 2 && type != 3 && type != 6 && type != 7)
|
||||
return UpdateResult::AlreadyUpToDate;
|
||||
|
||||
const IOS::ES::TMDReader tmd = m_ios.GetES()->FindInstalledTMD(title.id);
|
||||
const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(title.id);
|
||||
|
||||
// Optional titles can be skipped if the ticket is present, even when the title isn't installed.
|
||||
if (attrs.test(16) && ticket.IsValid())
|
||||
return UpdateResult::AlreadyUpToDate;
|
||||
|
||||
// Otherwise, the title is only skipped if it is installed, its ticket is imported,
|
||||
// and the installed version is new enough. No further checks unlike the online updater.
|
||||
if (tmd.IsValid() && tmd.GetTitleVersion() >= title.version)
|
||||
return UpdateResult::AlreadyUpToDate;
|
||||
|
||||
// Import the WAD.
|
||||
const std::unique_ptr<DiscIO::FileInfo> wad_file = m_disc_fs->FindFileInfo(path);
|
||||
if (!wad_file)
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to get info for %s", path.c_str());
|
||||
return UpdateResult::DiscReadFailed;
|
||||
}
|
||||
|
||||
const DiscIO::WiiWAD wad{DiscIO::VolumeFileBlobReader::Create(*m_volume, *m_disc_fs, path)};
|
||||
return InstallWAD(m_ios, wad) ? UpdateResult::Succeeded : UpdateResult::ImportFailed;
|
||||
}
|
||||
|
||||
UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& region)
|
||||
{
|
||||
OnlineSystemUpdater updater{std::move(update_callback), region};
|
||||
@ -500,4 +672,12 @@ UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& r
|
||||
DiscIO::NANDContentManager::Access().ClearCache();
|
||||
return result;
|
||||
}
|
||||
|
||||
UpdateResult DoDiscUpdate(UpdateCallback update_callback, const std::string& image_path)
|
||||
{
|
||||
DiscSystemUpdater updater{std::move(update_callback), image_path};
|
||||
const UpdateResult result = updater.DoDiscUpdate();
|
||||
DiscIO::NANDContentManager::Access().ClearCache();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,18 @@ enum class UpdateResult
|
||||
Succeeded,
|
||||
AlreadyUpToDate,
|
||||
|
||||
// Current region does not match disc region.
|
||||
RegionMismatch,
|
||||
// Missing update partition on disc.
|
||||
MissingUpdatePartition,
|
||||
// Missing or invalid files on disc.
|
||||
DiscReadFailed,
|
||||
|
||||
// NUS errors and failures.
|
||||
ServerFailed,
|
||||
// General download failures.
|
||||
DownloadFailed,
|
||||
|
||||
// Import failures.
|
||||
ImportFailed,
|
||||
// Update was cancelled.
|
||||
@ -37,4 +45,7 @@ using UpdateCallback = std::function<bool(size_t processed, size_t total, u64 ti
|
||||
// If no region is specified, the region of the installed System Menu will be used.
|
||||
// If no region is specified and no system menu is installed, the update will fail.
|
||||
UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& region);
|
||||
|
||||
// Perform a disc update with behaviour similar to the System Menu.
|
||||
UpdateResult DoDiscUpdate(UpdateCallback update_callback, const std::string& image_path);
|
||||
}
|
||||
|
Reference in New Issue
Block a user