Merge pull request #5746 from leoetlino/disc-updates

Add support for installing disc updates from the game list
This commit is contained in:
Leo Lam
2017-08-16 19:02:42 +08:00
committed by GitHub
17 changed files with 491 additions and 119 deletions

View File

@ -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;
}
}

View File

@ -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);
}