Merge pull request #5610 from leoetlino/online-wii-updates
Add ability to perform Wii online updates (without the system menu)
This commit is contained in:
commit
e14a82a87e
|
@ -19,6 +19,7 @@ set(SRCS
|
|||
State.cpp
|
||||
TitleDatabase.cpp
|
||||
WiiRoot.cpp
|
||||
WiiUtils.cpp
|
||||
Boot/Boot_BS2Emu.cpp
|
||||
Boot/Boot.cpp
|
||||
Boot/Boot_WiiWAD.cpp
|
||||
|
|
|
@ -290,6 +290,7 @@
|
|||
<ClCompile Include="State.cpp" />
|
||||
<ClCompile Include="TitleDatabase.cpp" />
|
||||
<ClCompile Include="WiiRoot.cpp" />
|
||||
<ClCompile Include="WiiUtils.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ActionReplay.h" />
|
||||
|
@ -524,6 +525,7 @@
|
|||
<ClInclude Include="Titles.h" />
|
||||
<ClInclude Include="TitleDatabase.h" />
|
||||
<ClInclude Include="WiiRoot.h" />
|
||||
<ClInclude Include="WiiUtils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
|
|
|
@ -179,6 +179,7 @@
|
|||
<ClCompile Include="State.cpp" />
|
||||
<ClCompile Include="TitleDatabase.cpp" />
|
||||
<ClCompile Include="WiiRoot.cpp" />
|
||||
<ClCompile Include="WiiUtils.cpp" />
|
||||
<ClCompile Include="ActionReplay.cpp">
|
||||
<Filter>ActionReplay</Filter>
|
||||
</ClCompile>
|
||||
|
@ -898,6 +899,7 @@
|
|||
<ClInclude Include="Titles.h" />
|
||||
<ClInclude Include="TitleDatabase.h" />
|
||||
<ClInclude Include="WiiRoot.h" />
|
||||
<ClInclude Include="WiiUtils.h" />
|
||||
<ClInclude Include="ActionReplay.h">
|
||||
<Filter>ActionReplay</Filter>
|
||||
</ClInclude>
|
||||
|
@ -1543,4 +1545,4 @@
|
|||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -843,30 +843,21 @@ ReturnCode ES::ReadCertStore(std::vector<u8>* buffer) const
|
|||
|
||||
ReturnCode ES::WriteNewCertToStore(const IOS::ES::CertReader& cert)
|
||||
{
|
||||
const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys";
|
||||
// The certificate store file may not exist, so we use a+b and not r+b here.
|
||||
File::IOFile store_file{store_path, "a+b"};
|
||||
if (!store_file)
|
||||
return ES_EIO;
|
||||
|
||||
// Read the current store to determine if the new cert needs to be written.
|
||||
const u64 file_size = store_file.GetSize();
|
||||
if (file_size != 0)
|
||||
std::vector<u8> current_store;
|
||||
const ReturnCode ret = ReadCertStore(¤t_store);
|
||||
if (ret == IPC_SUCCESS)
|
||||
{
|
||||
std::vector<u8> certs_bytes(file_size);
|
||||
if (!store_file.ReadBytes(certs_bytes.data(), certs_bytes.size()))
|
||||
return ES_SHORT_READ;
|
||||
|
||||
const std::map<std::string, IOS::ES::CertReader> certs = IOS::ES::ParseCertChain(certs_bytes);
|
||||
const std::map<std::string, IOS::ES::CertReader> certs = IOS::ES::ParseCertChain(current_store);
|
||||
// The cert is already present in the store. Nothing to do.
|
||||
if (certs.find(cert.GetName()) != certs.end())
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
// Otherwise, write the new cert at the end of the store.
|
||||
// When opening a file in read-write mode, a seek is required before a write.
|
||||
store_file.Seek(0, SEEK_END);
|
||||
if (!store_file.WriteBytes(cert.GetBytes().data(), cert.GetBytes().size()))
|
||||
const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys";
|
||||
File::IOFile store_file{store_path, "ab"};
|
||||
if (!store_file || !store_file.WriteBytes(cert.GetBytes().data(), cert.GetBytes().size()))
|
||||
return ES_EIO;
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,498 @@
|
|||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "Core/WiiUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/HttpRequest.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/NandPaths.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/CommonTitles.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/IOS/Device.h"
|
||||
#include "Core/IOS/ES/ES.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/NANDContentLoader.h"
|
||||
#include "DiscIO/WiiWad.h"
|
||||
|
||||
namespace WiiUtils
|
||||
{
|
||||
bool InstallWAD(const std::string& wad_path)
|
||||
{
|
||||
const DiscIO::WiiWAD wad{wad_path};
|
||||
if (!wad.IsValid())
|
||||
{
|
||||
PanicAlertT("WAD installation failed: The selected file is not a valid WAD.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto tmd = wad.GetTMD();
|
||||
IOS::HLE::Kernel ios;
|
||||
const auto es = ios.GetES();
|
||||
|
||||
IOS::HLE::Device::ES::Context context;
|
||||
IOS::HLE::ReturnCode ret;
|
||||
const bool checks_enabled = SConfig::GetInstance().m_enable_signature_checks;
|
||||
while ((ret = es->ImportTicket(wad.GetTicket().GetBytes(), wad.GetCertificateChain())) < 0 ||
|
||||
(ret = es->ImportTitleInit(context, tmd.GetBytes(), wad.GetCertificateChain())) < 0)
|
||||
{
|
||||
if (checks_enabled && ret == IOS::HLE::IOSC_FAIL_CHECKVALUE &&
|
||||
AskYesNoT("This WAD has not been signed by Nintendo. Continue to import?"))
|
||||
{
|
||||
SConfig::GetInstance().m_enable_signature_checks = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
SConfig::GetInstance().m_enable_signature_checks = checks_enabled;
|
||||
PanicAlertT("WAD installation failed: Could not initialise title import.");
|
||||
return false;
|
||||
}
|
||||
SConfig::GetInstance().m_enable_signature_checks = checks_enabled;
|
||||
|
||||
const bool contents_imported = [&]() {
|
||||
const u64 title_id = tmd.GetTitleId();
|
||||
for (const IOS::ES::Content& content : tmd.GetContents())
|
||||
{
|
||||
const std::vector<u8> data = wad.GetContent(content.index);
|
||||
|
||||
if (es->ImportContentBegin(context, title_id, content.id) < 0 ||
|
||||
es->ImportContentData(context, 0, data.data(), static_cast<u32>(data.size())) < 0 ||
|
||||
es->ImportContentEnd(context, 0) < 0)
|
||||
{
|
||||
PanicAlertT("WAD installation failed: Could not import content %08x.", content.id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
|
||||
if ((contents_imported && es->ImportTitleDone(context) < 0) ||
|
||||
(!contents_imported && es->ImportTitleCancel(context) < 0))
|
||||
{
|
||||
PanicAlertT("WAD installation failed: Could not finalise title import.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DiscIO::NANDContentManager::Access().ClearCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Common functionality for system updaters.
|
||||
class SystemUpdater
|
||||
{
|
||||
public:
|
||||
virtual ~SystemUpdater() = default;
|
||||
|
||||
protected:
|
||||
struct TitleInfo
|
||||
{
|
||||
u64 id;
|
||||
u16 version;
|
||||
};
|
||||
|
||||
std::string GetDeviceRegion();
|
||||
std::string GetDeviceId();
|
||||
bool ShouldInstallTitle(const TitleInfo& title);
|
||||
|
||||
IOS::HLE::Kernel m_ios;
|
||||
};
|
||||
|
||||
std::string SystemUpdater::GetDeviceRegion()
|
||||
{
|
||||
// Try to determine the region from an installed system menu.
|
||||
const auto tmd = m_ios.GetES()->FindInstalledTMD(Titles::SYSTEM_MENU);
|
||||
if (tmd.IsValid())
|
||||
{
|
||||
const DiscIO::Region region = tmd.GetRegion();
|
||||
static const std::map<DiscIO::Region, std::string> regions = {
|
||||
{DiscIO::Region::NTSC_J, "JPN"},
|
||||
{DiscIO::Region::NTSC_U, "USA"},
|
||||
{DiscIO::Region::PAL, "EUR"},
|
||||
{DiscIO::Region::NTSC_K, "KOR"},
|
||||
{DiscIO::Region::UNKNOWN_REGION, "EUR"}};
|
||||
return regions.at(region);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string SystemUpdater::GetDeviceId()
|
||||
{
|
||||
u32 ios_device_id;
|
||||
if (m_ios.GetES()->GetDeviceId(&ios_device_id) < 0)
|
||||
return "";
|
||||
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:
|
||||
OnlineSystemUpdater(UpdateCallback update_callback, const std::string& region);
|
||||
UpdateResult DoOnlineUpdate();
|
||||
|
||||
private:
|
||||
struct Response
|
||||
{
|
||||
std::string content_prefix_url;
|
||||
std::vector<TitleInfo> titles;
|
||||
};
|
||||
|
||||
Response GetSystemTitles();
|
||||
Response ParseTitlesResponse(const std::vector<u8>& response) const;
|
||||
|
||||
UpdateResult InstallTitleFromNUS(const std::string& prefix_url, const TitleInfo& title,
|
||||
std::unordered_set<u64>* updated_titles);
|
||||
|
||||
// Helper functions to download contents from NUS.
|
||||
std::pair<IOS::ES::TMDReader, std::vector<u8>> DownloadTMD(const std::string& prefix_url,
|
||||
const TitleInfo& title);
|
||||
std::pair<std::vector<u8>, std::vector<u8>> DownloadTicket(const std::string& prefix_url,
|
||||
const TitleInfo& title);
|
||||
std::optional<std::vector<u8>> DownloadContent(const std::string& prefix_url,
|
||||
const TitleInfo& title, u32 cid);
|
||||
|
||||
UpdateCallback m_update_callback;
|
||||
std::string m_requested_region;
|
||||
Common::HttpRequest m_http{std::chrono::minutes{3}};
|
||||
};
|
||||
|
||||
OnlineSystemUpdater::OnlineSystemUpdater(UpdateCallback update_callback, const std::string& region)
|
||||
: m_update_callback(std::move(update_callback)), m_requested_region(region)
|
||||
{
|
||||
}
|
||||
|
||||
OnlineSystemUpdater::Response
|
||||
OnlineSystemUpdater::ParseTitlesResponse(const std::vector<u8>& response) const
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_buffer(response.data(), response.size());
|
||||
if (!result)
|
||||
{
|
||||
ERROR_LOG(CORE, "ParseTitlesResponse: Could not parse response");
|
||||
return {};
|
||||
}
|
||||
|
||||
// pugixml doesn't fully support namespaces and ignores them.
|
||||
const pugi::xml_node node = doc.select_node("//GetSystemUpdateResponse").node();
|
||||
if (!node)
|
||||
{
|
||||
ERROR_LOG(CORE, "ParseTitlesResponse: Could not find response node");
|
||||
return {};
|
||||
}
|
||||
|
||||
const int code = node.child("ErrorCode").text().as_int();
|
||||
if (code != 0)
|
||||
{
|
||||
ERROR_LOG(CORE, "ParseTitlesResponse: Non-zero error code (%d)", code);
|
||||
return {};
|
||||
}
|
||||
|
||||
// libnup uses the uncached URL, not the cached one. However, that one is way, way too slow,
|
||||
// so let's use the cached endpoint.
|
||||
Response info;
|
||||
info.content_prefix_url = node.child("ContentPrefixURL").text().as_string();
|
||||
// Disable HTTPS because we can't use it without a device certificate.
|
||||
info.content_prefix_url = ReplaceAll(info.content_prefix_url, "https://", "http://");
|
||||
if (info.content_prefix_url.empty())
|
||||
{
|
||||
ERROR_LOG(CORE, "ParseTitlesResponse: Empty content prefix URL");
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const pugi::xml_node& title_node : node.children("TitleVersion"))
|
||||
{
|
||||
const u64 title_id = std::stoull(title_node.child("TitleId").text().as_string(), nullptr, 16);
|
||||
const u16 title_version = static_cast<u16>(title_node.child("Version").text().as_uint());
|
||||
info.titles.push_back({title_id, title_version});
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
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"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<soapenv:Body>
|
||||
<GetSystemUpdateRequest xmlns="urn:nus.wsapi.broadon.com">
|
||||
<Version>1.0</Version>
|
||||
<MessageId>0</MessageId>
|
||||
<DeviceId></DeviceId>
|
||||
<RegionId></RegionId>
|
||||
</GetSystemUpdateRequest>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
)";
|
||||
|
||||
OnlineSystemUpdater::Response OnlineSystemUpdater::GetSystemTitles()
|
||||
{
|
||||
// Construct the request by loading the template first, then updating some fields.
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_string(GET_SYSTEM_TITLES_REQUEST_PAYLOAD);
|
||||
_assert_(result);
|
||||
|
||||
// Nintendo does not really care about the device ID or verify that we *are* that device,
|
||||
// as long as it is a valid Wii device ID.
|
||||
const std::string device_id = GetDeviceId();
|
||||
_assert_(doc.select_node("//DeviceId").node().text().set(device_id.c_str()));
|
||||
|
||||
// Write the correct device region.
|
||||
const std::string region = m_requested_region.empty() ? GetDeviceRegion() : m_requested_region;
|
||||
_assert_(doc.select_node("//RegionId").node().text().set(region.c_str()));
|
||||
|
||||
std::ostringstream stream;
|
||||
doc.save(stream);
|
||||
const std::string request = stream.str();
|
||||
|
||||
// Note: We don't use HTTPS because that would require the user to have
|
||||
// a device certificate which cannot be redistributed with Dolphin.
|
||||
// This is fine, because IOS has signature checks.
|
||||
const Common::HttpRequest::Response response =
|
||||
m_http.Post("http://nus.shop.wii.com/nus/services/NetUpdateSOAP", request,
|
||||
{
|
||||
{"SOAPAction", "urn:nus.wsapi.broadon.com/GetSystemUpdate"},
|
||||
{"User-Agent", "wii libnup/1.0"},
|
||||
{"Content-Type", "text/xml; charset=utf-8"},
|
||||
});
|
||||
|
||||
if (!response)
|
||||
return {};
|
||||
return ParseTitlesResponse(*response);
|
||||
}
|
||||
|
||||
UpdateResult OnlineSystemUpdater::DoOnlineUpdate()
|
||||
{
|
||||
const Response info = GetSystemTitles();
|
||||
if (info.titles.empty())
|
||||
return UpdateResult::ServerFailed;
|
||||
|
||||
// Download and install any title that is older than the NUS version.
|
||||
// The order is determined by the server response, which is: boot2, System Menu, IOSes, channels.
|
||||
// As we install any IOS required by titles, the real order is boot2, SM IOS, SM, IOSes, channels.
|
||||
std::unordered_set<u64> updated_titles;
|
||||
size_t processed = 0;
|
||||
for (const TitleInfo& title : info.titles)
|
||||
{
|
||||
if (!m_update_callback(processed++, info.titles.size(), title.id))
|
||||
return UpdateResult::Cancelled;
|
||||
|
||||
const UpdateResult res = InstallTitleFromNUS(info.content_prefix_url, title, &updated_titles);
|
||||
if (res != UpdateResult::Succeeded)
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to update %016" PRIx64 " -- aborting update", title.id);
|
||||
return res;
|
||||
}
|
||||
|
||||
m_update_callback(processed, info.titles.size(), title.id);
|
||||
}
|
||||
|
||||
if (updated_titles.empty())
|
||||
{
|
||||
NOTICE_LOG(CORE, "Update finished - Already up-to-date");
|
||||
return UpdateResult::AlreadyUpToDate;
|
||||
}
|
||||
NOTICE_LOG(CORE, "Update finished - %zu updates installed", updated_titles.size());
|
||||
return UpdateResult::Succeeded;
|
||||
}
|
||||
|
||||
UpdateResult OnlineSystemUpdater::InstallTitleFromNUS(const std::string& prefix_url,
|
||||
const TitleInfo& title,
|
||||
std::unordered_set<u64>* updated_titles)
|
||||
{
|
||||
// We currently don't support boot2 updates at all, so ignore any attempt to install it.
|
||||
if (title.id == Titles::BOOT2)
|
||||
return UpdateResult::Succeeded;
|
||||
|
||||
if (!ShouldInstallTitle(title) || updated_titles->find(title.id) != updated_titles->end())
|
||||
return UpdateResult::Succeeded;
|
||||
|
||||
NOTICE_LOG(CORE, "Updating title %016" PRIx64, title.id);
|
||||
|
||||
// Download the ticket and certificates.
|
||||
const auto ticket = DownloadTicket(prefix_url, title);
|
||||
if (ticket.first.empty() || ticket.second.empty())
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to download ticket and certs");
|
||||
return UpdateResult::DownloadFailed;
|
||||
}
|
||||
|
||||
// Import the ticket.
|
||||
IOS::HLE::ReturnCode ret = IOS::HLE::IPC_SUCCESS;
|
||||
const auto es = m_ios.GetES();
|
||||
if ((ret = es->ImportTicket(ticket.first, ticket.second)) < 0)
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to import ticket: error %d", ret);
|
||||
return UpdateResult::ImportFailed;
|
||||
}
|
||||
|
||||
// Download the TMD.
|
||||
const auto tmd = DownloadTMD(prefix_url, title);
|
||||
if (!tmd.first.IsValid())
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to download TMD");
|
||||
return UpdateResult::DownloadFailed;
|
||||
}
|
||||
|
||||
// Download and import any required system title first.
|
||||
const u64 ios_id = tmd.first.GetIOSId();
|
||||
if (ios_id != 0 && IOS::ES::IsTitleType(ios_id, IOS::ES::TitleType::System))
|
||||
{
|
||||
if (!es->FindInstalledTMD(ios_id).IsValid())
|
||||
{
|
||||
WARN_LOG(CORE, "Importing required system title %016" PRIx64 " first", ios_id);
|
||||
const UpdateResult res = InstallTitleFromNUS(prefix_url, {ios_id, 0}, updated_titles);
|
||||
if (res != UpdateResult::Succeeded)
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to import required system title %016" PRIx64, ios_id);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise the title import.
|
||||
IOS::HLE::Device::ES::Context context;
|
||||
if ((ret = es->ImportTitleInit(context, tmd.first.GetBytes(), tmd.second)) < 0)
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to initialise title import: error %d", ret);
|
||||
return UpdateResult::ImportFailed;
|
||||
}
|
||||
|
||||
// Now download and install contents listed in the TMD.
|
||||
const std::vector<IOS::ES::Content> stored_contents = es->GetStoredContentsFromTMD(tmd.first);
|
||||
const UpdateResult import_result = [&]() {
|
||||
for (const IOS::ES::Content& content : tmd.first.GetContents())
|
||||
{
|
||||
const bool is_already_installed = std::find_if(stored_contents.begin(), stored_contents.end(),
|
||||
[&content](const auto& stored_content) {
|
||||
return stored_content.id == content.id;
|
||||
}) != stored_contents.end();
|
||||
|
||||
// Do skip what is already installed on the NAND.
|
||||
if (is_already_installed)
|
||||
continue;
|
||||
|
||||
if ((ret = es->ImportContentBegin(context, title.id, content.id)) < 0)
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to initialise import for content %08x: error %d", content.id, ret);
|
||||
return UpdateResult::ImportFailed;
|
||||
}
|
||||
|
||||
const std::optional<std::vector<u8>> data = DownloadContent(prefix_url, title, content.id);
|
||||
if (!data)
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to download content %08x", content.id);
|
||||
return UpdateResult::DownloadFailed;
|
||||
}
|
||||
|
||||
if (es->ImportContentData(context, 0, data->data(), static_cast<u32>(data->size())) < 0 ||
|
||||
es->ImportContentEnd(context, 0) < 0)
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to import content %08x", content.id);
|
||||
return UpdateResult::ImportFailed;
|
||||
}
|
||||
}
|
||||
return UpdateResult::Succeeded;
|
||||
}();
|
||||
const bool all_contents_imported = import_result == UpdateResult::Succeeded;
|
||||
|
||||
if ((all_contents_imported && (ret = es->ImportTitleDone(context)) < 0) ||
|
||||
(!all_contents_imported && (ret = es->ImportTitleCancel(context)) < 0))
|
||||
{
|
||||
ERROR_LOG(CORE, "Failed to finalise title import: error %d", ret);
|
||||
return UpdateResult::ImportFailed;
|
||||
}
|
||||
|
||||
if (!all_contents_imported)
|
||||
return import_result;
|
||||
|
||||
updated_titles->emplace(title.id);
|
||||
return UpdateResult::Succeeded;
|
||||
}
|
||||
|
||||
std::pair<IOS::ES::TMDReader, std::vector<u8>>
|
||||
OnlineSystemUpdater::DownloadTMD(const std::string& prefix_url, const TitleInfo& title)
|
||||
{
|
||||
const std::string url =
|
||||
(title.version == 0) ?
|
||||
prefix_url + StringFromFormat("/%016" PRIx64 "/tmd", title.id) :
|
||||
prefix_url + StringFromFormat("/%016" PRIx64 "/tmd.%u", title.id, title.version);
|
||||
const Common::HttpRequest::Response response = m_http.Get(url);
|
||||
if (!response)
|
||||
return {};
|
||||
|
||||
// Too small to contain both the TMD and a cert chain.
|
||||
if (response->size() <= sizeof(IOS::ES::TMDHeader))
|
||||
return {};
|
||||
const size_t tmd_size =
|
||||
sizeof(IOS::ES::TMDHeader) +
|
||||
sizeof(IOS::ES::Content) *
|
||||
Common::swap16(response->data() + offsetof(IOS::ES::TMDHeader, num_contents));
|
||||
if (response->size() <= tmd_size)
|
||||
return {};
|
||||
|
||||
const auto tmd_begin = response->begin();
|
||||
const auto tmd_end = tmd_begin + tmd_size;
|
||||
|
||||
return {IOS::ES::TMDReader(std::vector<u8>(tmd_begin, tmd_end)),
|
||||
std::vector<u8>(tmd_end, response->end())};
|
||||
}
|
||||
|
||||
std::pair<std::vector<u8>, std::vector<u8>>
|
||||
OnlineSystemUpdater::DownloadTicket(const std::string& prefix_url, const TitleInfo& title)
|
||||
{
|
||||
const std::string url = prefix_url + StringFromFormat("/%016" PRIx64 "/cetk", title.id);
|
||||
const Common::HttpRequest::Response response = m_http.Get(url);
|
||||
if (!response)
|
||||
return {};
|
||||
|
||||
// Too small to contain both the ticket and a cert chain.
|
||||
if (response->size() <= sizeof(IOS::ES::Ticket))
|
||||
return {};
|
||||
|
||||
const auto ticket_begin = response->begin();
|
||||
const auto ticket_end = ticket_begin + sizeof(IOS::ES::Ticket);
|
||||
return {std::vector<u8>(ticket_begin, ticket_end), std::vector<u8>(ticket_end, response->end())};
|
||||
}
|
||||
|
||||
std::optional<std::vector<u8>> OnlineSystemUpdater::DownloadContent(const std::string& prefix_url,
|
||||
const TitleInfo& title, u32 cid)
|
||||
{
|
||||
const std::string url = prefix_url + StringFromFormat("/%016" PRIx64 "/%08x", title.id, cid);
|
||||
return m_http.Get(url);
|
||||
}
|
||||
|
||||
UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& region)
|
||||
{
|
||||
OnlineSystemUpdater updater{std::move(update_callback), region};
|
||||
const UpdateResult result = updater.DoOnlineUpdate();
|
||||
DiscIO::NANDContentManager::Access().ClearCache();
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
// Small utility functions for common Wii related tasks.
|
||||
|
||||
namespace WiiUtils
|
||||
{
|
||||
bool InstallWAD(const std::string& wad_path);
|
||||
|
||||
enum class UpdateResult
|
||||
{
|
||||
Succeeded,
|
||||
AlreadyUpToDate,
|
||||
|
||||
// NUS errors and failures.
|
||||
ServerFailed,
|
||||
// General download failures.
|
||||
DownloadFailed,
|
||||
// Import failures.
|
||||
ImportFailed,
|
||||
// Update was cancelled.
|
||||
Cancelled,
|
||||
};
|
||||
|
||||
// Return false to cancel the update as soon as the current title has finished updating.
|
||||
using UpdateCallback = std::function<bool(size_t processed, size_t total, u64 title_id)>;
|
||||
// Download and install the latest version of all titles (if missing) from NUS.
|
||||
// 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);
|
||||
}
|
|
@ -19,6 +19,8 @@ set(SRCS
|
|||
Resources.cpp
|
||||
Settings.cpp
|
||||
ToolBar.cpp
|
||||
WiiUpdate.cpp
|
||||
WiiUpdate.h
|
||||
Config/ControllersWindow.cpp
|
||||
Config/FilesystemWidget.cpp
|
||||
Config/InfoWidget.cpp
|
||||
|
|
|
@ -164,6 +164,7 @@
|
|||
<ClCompile Include="Settings\InterfacePane.cpp" />
|
||||
<ClCompile Include="Settings\PathPane.cpp" />
|
||||
<ClCompile Include="ToolBar.cpp" />
|
||||
<ClCompile Include="WiiUpdate.cpp" />
|
||||
</ItemGroup>
|
||||
<!--Put standard C/C++ headers here-->
|
||||
<ItemGroup>
|
||||
|
@ -185,6 +186,7 @@
|
|||
<ClInclude Include="QtUtils\ElidedButton.h" />
|
||||
<ClInclude Include="Resources.h" />
|
||||
<ClInclude Include="Settings\PathPane.h" />
|
||||
<ClInclude Include="WiiUpdate.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "Core/HW/WiiSaveCrypted.h"
|
||||
#include "Core/IOS/ES/ES.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/WiiUtils.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/NANDContentLoader.h"
|
||||
|
@ -22,7 +23,6 @@
|
|||
#include "DolphinQt2/GameList/GameFile.h"
|
||||
#include "DolphinQt2/Resources.h"
|
||||
#include "DolphinQt2/Settings.h"
|
||||
#include "UICommon/WiiUtils.h"
|
||||
|
||||
static const int CACHE_VERSION = 13; // Last changed in PR #3261
|
||||
static const int DATASTREAM_VERSION = QDataStream::Qt_5_5;
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
#include "DolphinQt2/AboutDialog.h"
|
||||
#include "DolphinQt2/Config/ControllersWindow.h"
|
||||
|
||||
#include "DolphinQt2/Config/Mapping/MappingWindow.h"
|
||||
#include "DolphinQt2/Config/SettingsWindow.h"
|
||||
#include "DolphinQt2/Host.h"
|
||||
|
@ -39,6 +38,7 @@
|
|||
#include "DolphinQt2/QtUtils/WindowActivationEventFilter.h"
|
||||
#include "DolphinQt2/Resources.h"
|
||||
#include "DolphinQt2/Settings.h"
|
||||
#include "DolphinQt2/WiiUpdate.h"
|
||||
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
|
@ -165,6 +165,9 @@ void MainWindow::ConnectMenuBar()
|
|||
// Options
|
||||
connect(m_menu_bar, &MenuBar::ConfigureHotkeys, this, &MainWindow::ShowHotkeyDialog);
|
||||
|
||||
// Tools
|
||||
connect(m_menu_bar, &MenuBar::PerformOnlineUpdate, this, &MainWindow::PerformOnlineUpdate);
|
||||
|
||||
// View
|
||||
connect(m_menu_bar, &MenuBar::ShowTable, m_game_list, &GameList::SetTableView);
|
||||
connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView);
|
||||
|
@ -530,6 +533,13 @@ void MainWindow::SetStateSlot(int slot)
|
|||
m_state_slot = slot;
|
||||
}
|
||||
|
||||
void MainWindow::PerformOnlineUpdate(const std::string& region)
|
||||
{
|
||||
WiiUpdate::PerformOnlineUpdate(region, this);
|
||||
// Since the update may have installed a newer system menu, refresh the tools menu.
|
||||
m_menu_bar->UpdateToolsMenu(false);
|
||||
}
|
||||
|
||||
bool MainWindow::eventFilter(QObject* object, QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::Close && !Stop())
|
||||
|
|
|
@ -56,6 +56,8 @@ private slots:
|
|||
void StateSaveOldest();
|
||||
void SetStateSlot(int slot);
|
||||
|
||||
void PerformOnlineUpdate(const std::string& region);
|
||||
|
||||
void FullScreen();
|
||||
void ScreenShot();
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
#include <QMessageBox>
|
||||
#include <QUrl>
|
||||
|
||||
#include "Core/CommonTitles.h"
|
||||
#include "Core/IOS/ES/ES.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/State.h"
|
||||
#include "DolphinQt2/AboutDialog.h"
|
||||
#include "DolphinQt2/GameList/GameFile.h"
|
||||
|
@ -43,6 +46,7 @@ void MenuBar::EmulationStarted()
|
|||
m_state_load_menu->setEnabled(true);
|
||||
m_state_save_menu->setEnabled(true);
|
||||
UpdateStateSlotMenu();
|
||||
UpdateToolsMenu(true);
|
||||
}
|
||||
void MenuBar::EmulationPaused()
|
||||
{
|
||||
|
@ -66,6 +70,7 @@ void MenuBar::EmulationStopped()
|
|||
m_state_load_menu->setEnabled(false);
|
||||
m_state_save_menu->setEnabled(false);
|
||||
UpdateStateSlotMenu();
|
||||
UpdateToolsMenu(false);
|
||||
}
|
||||
|
||||
void MenuBar::AddFileMenu()
|
||||
|
@ -79,6 +84,17 @@ void MenuBar::AddToolsMenu()
|
|||
{
|
||||
QMenu* tools_menu = addMenu(tr("Tools"));
|
||||
m_wad_install_action = tools_menu->addAction(tr("Install WAD..."), this, SLOT(InstallWAD()));
|
||||
|
||||
m_perform_online_update_menu = tools_menu->addMenu(tr("Perform Online System Update"));
|
||||
m_perform_online_update_for_current_region = m_perform_online_update_menu->addAction(
|
||||
tr("Current Region"), [this] { emit PerformOnlineUpdate(""); });
|
||||
m_perform_online_update_menu->addSeparator();
|
||||
m_perform_online_update_menu->addAction(tr("Europe"),
|
||||
[this] { emit PerformOnlineUpdate("EUR"); });
|
||||
m_perform_online_update_menu->addAction(tr("Japan"), [this] { emit PerformOnlineUpdate("JPN"); });
|
||||
m_perform_online_update_menu->addAction(tr("Korea"), [this] { emit PerformOnlineUpdate("KOR"); });
|
||||
m_perform_online_update_menu->addAction(tr("United States"),
|
||||
[this] { emit PerformOnlineUpdate("USA"); });
|
||||
}
|
||||
|
||||
void MenuBar::AddEmulationMenu()
|
||||
|
@ -248,6 +264,20 @@ void MenuBar::AddTableColumnsMenu(QMenu* view_menu)
|
|||
}
|
||||
}
|
||||
|
||||
void MenuBar::UpdateToolsMenu(bool emulation_started)
|
||||
{
|
||||
const bool enable_wii_tools = !emulation_started || !Settings::Instance().IsWiiGameRunning();
|
||||
m_perform_online_update_menu->setEnabled(enable_wii_tools);
|
||||
if (enable_wii_tools)
|
||||
{
|
||||
IOS::HLE::Kernel ios;
|
||||
const auto tmd = ios.GetES()->FindInstalledTMD(Titles::SYSTEM_MENU);
|
||||
for (QAction* action : m_perform_online_update_menu->actions())
|
||||
action->setEnabled(!tmd.IsValid());
|
||||
m_perform_online_update_for_current_region->setEnabled(tmd.IsValid());
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::InstallWAD()
|
||||
{
|
||||
QString wad_file = QFileDialog::getOpenFileName(this, tr("Select a title to install to NAND"),
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
|
||||
|
@ -38,6 +40,8 @@ signals:
|
|||
void StateSaveOldest();
|
||||
void SetStateSlot(int slot);
|
||||
|
||||
void PerformOnlineUpdate(const std::string& region);
|
||||
|
||||
// Options
|
||||
void ConfigureHotkeys();
|
||||
|
||||
|
@ -53,6 +57,7 @@ public slots:
|
|||
void EmulationPaused();
|
||||
void EmulationStopped();
|
||||
void UpdateStateSlotMenu();
|
||||
void UpdateToolsMenu(bool emulation_started);
|
||||
|
||||
// Tools
|
||||
void InstallWAD();
|
||||
|
@ -79,6 +84,8 @@ private:
|
|||
|
||||
// Tools
|
||||
QAction* m_wad_install_action;
|
||||
QMenu* m_perform_online_update_menu;
|
||||
QAction* m_perform_online_update_for_current_region;
|
||||
|
||||
// Emulation
|
||||
QAction* m_play_action;
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt2/WiiUpdate.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <future>
|
||||
|
||||
#include <QCloseEvent>
|
||||
#include <QMessageBox>
|
||||
#include <QObject>
|
||||
#include <QProgressDialog>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Flag.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/WiiUtils.h"
|
||||
#include "DiscIO/NANDImporter.h"
|
||||
|
||||
namespace WiiUpdate
|
||||
{
|
||||
void PerformOnlineUpdate(const std::string& region, QWidget* parent)
|
||||
{
|
||||
const int confirm = QMessageBox::question(
|
||||
parent, QObject::tr("Confirm"),
|
||||
QObject::tr("Connect to the Internet and perform an online system update?"));
|
||||
if (confirm != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
// Do not allow the user to close the dialog. Instead, wait until the update is finished
|
||||
// or cancelled.
|
||||
class UpdateProgressDialog final : public QProgressDialog
|
||||
{
|
||||
public:
|
||||
using QProgressDialog::QProgressDialog;
|
||||
|
||||
protected:
|
||||
void reject() override {}
|
||||
};
|
||||
|
||||
UpdateProgressDialog dialog{parent};
|
||||
dialog.setLabelText(QObject::tr("Preparing to update...\nThis can take a while."));
|
||||
dialog.setWindowTitle(QObject::tr("Updating"));
|
||||
// QProgressDialog doesn't set its minimum size correctly.
|
||||
dialog.setMinimumSize(360, 150);
|
||||
|
||||
// QProgressDialog doesn't allow us to disable the cancel button when it's pressed,
|
||||
// so we have to pass it our own push button. Note: the dialog takes ownership of it.
|
||||
auto* cancel_button = new QPushButton(QObject::tr("&Cancel"), parent);
|
||||
dialog.setCancelButton(cancel_button);
|
||||
Common::Flag was_cancelled;
|
||||
QObject::disconnect(&dialog, &QProgressDialog::canceled, &dialog, &QProgressDialog::cancel);
|
||||
QObject::connect(&dialog, &QProgressDialog::canceled, [&] {
|
||||
dialog.setLabelText(QObject::tr("Finishing the update...\nThis can take a while."));
|
||||
cancel_button->setEnabled(false);
|
||||
was_cancelled.Set();
|
||||
});
|
||||
|
||||
std::future<WiiUtils::UpdateResult> result = std::async(std::launch::async, [&] {
|
||||
const WiiUtils::UpdateResult res = WiiUtils::DoOnlineUpdate(
|
||||
[&](size_t processed, size_t total, u64 title_id) {
|
||||
Core::QueueHostJob(
|
||||
[&dialog, &was_cancelled, processed, total, title_id]() {
|
||||
if (was_cancelled.IsSet())
|
||||
return;
|
||||
|
||||
dialog.setRange(0, static_cast<int>(total));
|
||||
dialog.setValue(static_cast<int>(processed));
|
||||
dialog.setLabelText(QObject::tr("Updating title %1...\nThis can take a while.")
|
||||
.arg(title_id, 16, 16, QLatin1Char('0')));
|
||||
},
|
||||
true);
|
||||
return !was_cancelled.IsSet();
|
||||
},
|
||||
region);
|
||||
Core::QueueHostJob([&dialog] { dialog.close(); }, true);
|
||||
return res;
|
||||
});
|
||||
|
||||
dialog.exec();
|
||||
|
||||
switch (result.get())
|
||||
{
|
||||
case WiiUtils::UpdateResult::Succeeded:
|
||||
QMessageBox::information(parent, QObject::tr("Update completed"),
|
||||
QObject::tr("The emulated Wii console has been updated."));
|
||||
DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX));
|
||||
break;
|
||||
case WiiUtils::UpdateResult::AlreadyUpToDate:
|
||||
QMessageBox::information(parent, QObject::tr("Update completed"),
|
||||
QObject::tr("The emulated Wii console is already up-to-date."));
|
||||
DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX));
|
||||
break;
|
||||
case WiiUtils::UpdateResult::ServerFailed:
|
||||
QMessageBox::critical(parent, QObject::tr("Update failed"),
|
||||
QObject::tr("Could not download update information from Nintendo. "
|
||||
"Please check your Internet connection and try again."));
|
||||
break;
|
||||
case WiiUtils::UpdateResult::DownloadFailed:
|
||||
QMessageBox::critical(parent, QObject::tr("Update failed"),
|
||||
QObject::tr("Could not download update files from Nintendo. "
|
||||
"Please check your Internet connection and try again."));
|
||||
break;
|
||||
case WiiUtils::UpdateResult::ImportFailed:
|
||||
QMessageBox::critical(parent, QObject::tr("Update failed"),
|
||||
QObject::tr("Could not install an update to the Wii system memory. "
|
||||
"Please refer to logs for more information."));
|
||||
break;
|
||||
case WiiUtils::UpdateResult::Cancelled:
|
||||
QMessageBox::warning(
|
||||
parent, QObject::tr("Update cancelled"),
|
||||
QObject::tr("The update has been cancelled. It is strongly recommended to "
|
||||
"finish it in order to avoid inconsistent system software versions."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}; // namespace WiiUpdate
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
// Small utility functions for common Wii related tasks.
|
||||
class QWidget;
|
||||
|
||||
namespace WiiUtils
|
||||
namespace WiiUpdate
|
||||
{
|
||||
bool InstallWAD(const std::string& wad_path);
|
||||
}
|
||||
void PerformOnlineUpdate(const std::string& region, QWidget* parent = nullptr);
|
||||
}; // namespace WiiUpdate
|
|
@ -345,6 +345,7 @@ private:
|
|||
void OnUninstallWAD(wxCommandEvent& event);
|
||||
void OnImportBootMiiBackup(wxCommandEvent& event);
|
||||
void OnExtractCertificates(wxCommandEvent& event);
|
||||
void OnPerformOnlineWiiUpdate(wxCommandEvent& event);
|
||||
void OnFifoPlayer(wxCommandEvent& event);
|
||||
void OnConnectWiimote(wxCommandEvent& event);
|
||||
void GameListChanged(wxCommandEvent& event);
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cinttypes>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
@ -18,7 +21,6 @@
|
|||
#include <wx/panel.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/statusbr.h>
|
||||
#include <wx/thread.h>
|
||||
#include <wx/toolbar.h>
|
||||
#include <wx/toplevel.h>
|
||||
|
||||
|
@ -54,6 +56,7 @@
|
|||
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/State.h"
|
||||
#include "Core/WiiUtils.h"
|
||||
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/NANDContentLoader.h"
|
||||
|
@ -87,7 +90,6 @@
|
|||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
#include "UICommon/WiiUtils.h"
|
||||
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
|
@ -182,6 +184,12 @@ void CFrame::BindMenuBarEvents()
|
|||
Bind(wxEVT_MENU, &CFrame::OnLoadWiiMenu, this, IDM_LOAD_WII_MENU);
|
||||
Bind(wxEVT_MENU, &CFrame::OnImportBootMiiBackup, this, IDM_IMPORT_NAND);
|
||||
Bind(wxEVT_MENU, &CFrame::OnExtractCertificates, this, IDM_EXTRACT_CERTIFICATES);
|
||||
for (const int idm : {IDM_PERFORM_ONLINE_UPDATE_CURRENT, IDM_PERFORM_ONLINE_UPDATE_EUR,
|
||||
IDM_PERFORM_ONLINE_UPDATE_JPN, IDM_PERFORM_ONLINE_UPDATE_KOR,
|
||||
IDM_PERFORM_ONLINE_UPDATE_USA})
|
||||
{
|
||||
Bind(wxEVT_MENU, &CFrame::OnPerformOnlineWiiUpdate, this, idm);
|
||||
}
|
||||
Bind(wxEVT_MENU, &CFrame::OnFifoPlayer, this, IDM_FIFOPLAYER);
|
||||
Bind(wxEVT_MENU, &CFrame::OnConnectWiimote, this, IDM_CONNECT_WIIMOTE1, IDM_CONNECT_BALANCEBOARD);
|
||||
|
||||
|
@ -1293,6 +1301,93 @@ void CFrame::OnExtractCertificates(wxCommandEvent& WXUNUSED(event))
|
|||
DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX));
|
||||
}
|
||||
|
||||
static std::string GetUpdateRegionFromIdm(int idm)
|
||||
{
|
||||
switch (idm)
|
||||
{
|
||||
case IDM_PERFORM_ONLINE_UPDATE_EUR:
|
||||
return "EUR";
|
||||
case IDM_PERFORM_ONLINE_UPDATE_JPN:
|
||||
return "JPN";
|
||||
case IDM_PERFORM_ONLINE_UPDATE_KOR:
|
||||
return "KOR";
|
||||
case IDM_PERFORM_ONLINE_UPDATE_USA:
|
||||
return "USA";
|
||||
case IDM_PERFORM_ONLINE_UPDATE_CURRENT:
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void CFrame::OnPerformOnlineWiiUpdate(wxCommandEvent& event)
|
||||
{
|
||||
int confirm = wxMessageBox(_("Connect to the Internet and perform an online system update?"),
|
||||
_("System Update"), wxYES_NO, this);
|
||||
if (confirm != wxYES)
|
||||
return;
|
||||
|
||||
wxProgressDialog dialog(_("Updating"), _("Preparing to update...\nThis can take a while."), 1,
|
||||
this, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_SMOOTH | wxPD_CAN_ABORT);
|
||||
|
||||
const std::string region = GetUpdateRegionFromIdm(event.GetId());
|
||||
std::future<WiiUtils::UpdateResult> result = std::async(std::launch::async, [&] {
|
||||
const WiiUtils::UpdateResult res = WiiUtils::DoOnlineUpdate(
|
||||
[&](size_t processed, size_t total, u64 title_id) {
|
||||
Core::QueueHostJob(
|
||||
[&dialog, processed, total, title_id] {
|
||||
dialog.SetRange(total);
|
||||
dialog.Update(processed, wxString::Format(_("Updating title %016" PRIx64 "...\n"
|
||||
"This can take a while."),
|
||||
title_id));
|
||||
dialog.Fit();
|
||||
},
|
||||
true);
|
||||
return !dialog.WasCancelled();
|
||||
},
|
||||
region);
|
||||
Core::QueueHostJob([&dialog] { dialog.EndModal(0); }, true);
|
||||
return res;
|
||||
});
|
||||
|
||||
dialog.ShowModal();
|
||||
|
||||
switch (result.get())
|
||||
{
|
||||
case WiiUtils::UpdateResult::Succeeded:
|
||||
wxMessageBox(_("The emulated Wii console has been updated."), _("Update completed"),
|
||||
wxOK | wxICON_INFORMATION);
|
||||
DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX));
|
||||
break;
|
||||
case WiiUtils::UpdateResult::AlreadyUpToDate:
|
||||
wxMessageBox(_("The emulated Wii console is already up-to-date."), _("Update completed"),
|
||||
wxOK | wxICON_INFORMATION);
|
||||
DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX));
|
||||
break;
|
||||
case WiiUtils::UpdateResult::ServerFailed:
|
||||
wxMessageBox(_("Could not download update information from Nintendo. "
|
||||
"Please check your Internet connection and try again."),
|
||||
_("Update failed"), wxOK | wxICON_ERROR);
|
||||
break;
|
||||
case WiiUtils::UpdateResult::DownloadFailed:
|
||||
wxMessageBox(_("Could not download update files from Nintendo. "
|
||||
"Please check your Internet connection and try again."),
|
||||
_("Update failed"), wxOK | wxICON_ERROR);
|
||||
break;
|
||||
case WiiUtils::UpdateResult::ImportFailed:
|
||||
wxMessageBox(_("Could not install an update to the Wii system memory. "
|
||||
"Please refer to logs for more information."),
|
||||
_("Update failed"), wxOK | wxICON_ERROR);
|
||||
break;
|
||||
case WiiUtils::UpdateResult::Cancelled:
|
||||
wxMessageBox(_("The update has been cancelled. It is strongly recommended to "
|
||||
"finish it in order to avoid inconsistent system software versions."),
|
||||
_("Update cancelled"), wxOK | wxICON_WARNING);
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateLoadWiiMenuItem();
|
||||
}
|
||||
|
||||
void CFrame::UpdateLoadWiiMenuItem() const
|
||||
{
|
||||
GetMenuBar()->Refresh(true, nullptr);
|
||||
|
@ -1492,10 +1587,6 @@ void CFrame::UpdateGUI()
|
|||
GetMenuBar()
|
||||
->FindItem(IDM_LOAD_GC_IPL_EUR)
|
||||
->Enable(!Initialized && File::Exists(SConfig::GetInstance().GetBootROMPath(EUR_DIR)));
|
||||
if (DiscIO::NANDContentManager::Access()
|
||||
.GetNANDLoader(Titles::SYSTEM_MENU, Common::FROM_CONFIGURED_ROOT)
|
||||
.IsValid())
|
||||
GetMenuBar()->FindItem(IDM_LOAD_WII_MENU)->Enable(!Initialized);
|
||||
|
||||
// Tools
|
||||
GetMenuBar()->FindItem(IDM_CHEATS)->Enable(SConfig::GetInstance().bEnableCheats);
|
||||
|
|
|
@ -104,6 +104,11 @@ enum
|
|||
IDM_LIST_UNINSTALL_WAD,
|
||||
IDM_IMPORT_NAND,
|
||||
IDM_EXTRACT_CERTIFICATES,
|
||||
IDM_PERFORM_ONLINE_UPDATE_CURRENT,
|
||||
IDM_PERFORM_ONLINE_UPDATE_EUR,
|
||||
IDM_PERFORM_ONLINE_UPDATE_JPN,
|
||||
IDM_PERFORM_ONLINE_UPDATE_KOR,
|
||||
IDM_PERFORM_ONLINE_UPDATE_USA,
|
||||
IDM_FIFOPLAYER,
|
||||
IDM_LOAD_GC_IPL_JAP,
|
||||
IDM_LOAD_GC_IPL_USA,
|
||||
|
|
|
@ -235,6 +235,16 @@ wxMenu* MainMenuBar::CreateToolsMenu() const
|
|||
tools_menu->Append(IDM_LOAD_WII_MENU, dummy_string);
|
||||
tools_menu->Append(IDM_IMPORT_NAND, _("Import BootMii NAND Backup..."));
|
||||
tools_menu->Append(IDM_EXTRACT_CERTIFICATES, _("Extract Certificates from NAND"));
|
||||
auto* const online_update_menu = new wxMenu;
|
||||
online_update_menu->Append(IDM_PERFORM_ONLINE_UPDATE_CURRENT, _("Current Region"));
|
||||
online_update_menu->AppendSeparator();
|
||||
online_update_menu->Append(IDM_PERFORM_ONLINE_UPDATE_EUR, _("Europe"));
|
||||
online_update_menu->Append(IDM_PERFORM_ONLINE_UPDATE_JPN, _("Japan"));
|
||||
online_update_menu->Append(IDM_PERFORM_ONLINE_UPDATE_KOR, _("Korean"));
|
||||
online_update_menu->Append(IDM_PERFORM_ONLINE_UPDATE_USA, _("United States"));
|
||||
tools_menu->AppendSubMenu(
|
||||
online_update_menu, _("Perform Online System Update"),
|
||||
_("Update the Wii system software to the latest version from Nintendo."));
|
||||
tools_menu->AppendSeparator();
|
||||
tools_menu->AppendSubMenu(wiimote_menu, _("Connect Wii Remotes"));
|
||||
|
||||
|
@ -562,8 +572,6 @@ void MainMenuBar::RefreshSaveStateMenuLabels() const
|
|||
|
||||
void MainMenuBar::RefreshWiiToolsLabels() const
|
||||
{
|
||||
RefreshWiiSystemMenuLabel();
|
||||
|
||||
// The Install WAD option should not be enabled while emulation is running, because
|
||||
// having unexpected title changes can confuse emulated software; and of course, this is
|
||||
// not possible on a real Wii and won't be if we have IOS LLE (or simply more accurate IOS HLE).
|
||||
|
@ -571,10 +579,26 @@ void MainMenuBar::RefreshWiiToolsLabels() const
|
|||
// For similar reasons, it should not be possible to export or import saves, because this can
|
||||
// result in the emulated software being confused, or even worse, exported saves having
|
||||
// inconsistent data.
|
||||
for (const int index : {IDM_MENU_INSTALL_WAD, IDM_EXPORT_ALL_SAVE, IDM_IMPORT_SAVE,
|
||||
IDM_IMPORT_NAND, IDM_EXTRACT_CERTIFICATES})
|
||||
const bool enable_wii_tools = !Core::IsRunning() || !SConfig::GetInstance().bWii;
|
||||
for (const int index :
|
||||
{IDM_MENU_INSTALL_WAD, IDM_EXPORT_ALL_SAVE, IDM_IMPORT_SAVE, IDM_IMPORT_NAND,
|
||||
IDM_EXTRACT_CERTIFICATES, IDM_LOAD_WII_MENU, IDM_PERFORM_ONLINE_UPDATE_CURRENT,
|
||||
IDM_PERFORM_ONLINE_UPDATE_EUR, IDM_PERFORM_ONLINE_UPDATE_JPN, IDM_PERFORM_ONLINE_UPDATE_KOR,
|
||||
IDM_PERFORM_ONLINE_UPDATE_USA})
|
||||
{
|
||||
FindItem(index)->Enable(!Core::IsRunning() || !SConfig::GetInstance().bWii);
|
||||
FindItem(index)->Enable(enable_wii_tools);
|
||||
}
|
||||
if (enable_wii_tools)
|
||||
RefreshWiiSystemMenuLabel();
|
||||
}
|
||||
|
||||
void MainMenuBar::EnableUpdateMenu(UpdateMenuMode mode) const
|
||||
{
|
||||
FindItem(IDM_PERFORM_ONLINE_UPDATE_CURRENT)->Enable(mode == UpdateMenuMode::CurrentRegionOnly);
|
||||
for (const int idm : {IDM_PERFORM_ONLINE_UPDATE_EUR, IDM_PERFORM_ONLINE_UPDATE_JPN,
|
||||
IDM_PERFORM_ONLINE_UPDATE_KOR, IDM_PERFORM_ONLINE_UPDATE_USA})
|
||||
{
|
||||
FindItem(idm)->Enable(mode == UpdateMenuMode::SpecificRegionsOnly);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -591,11 +615,13 @@ void MainMenuBar::RefreshWiiSystemMenuLabel() const
|
|||
const wxString version_string = StrToWxStr(DiscIO::GetSysMenuVersionString(version_number));
|
||||
item->Enable();
|
||||
item->SetItemLabel(wxString::Format(_("Load Wii System Menu %s"), version_string));
|
||||
EnableUpdateMenu(UpdateMenuMode::CurrentRegionOnly);
|
||||
}
|
||||
else
|
||||
{
|
||||
item->Enable(false);
|
||||
item->SetItemLabel(_("Load Wii System Menu"));
|
||||
EnableUpdateMenu(UpdateMenuMode::SpecificRegionsOnly);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,12 @@ private:
|
|||
void RefreshSaveStateMenuLabels() const;
|
||||
void RefreshWiiToolsLabels() const;
|
||||
void RefreshWiiSystemMenuLabel() const;
|
||||
enum class UpdateMenuMode
|
||||
{
|
||||
CurrentRegionOnly,
|
||||
SpecificRegionsOnly,
|
||||
};
|
||||
void EnableUpdateMenu(UpdateMenuMode mode) const;
|
||||
|
||||
void ClearSavedPerspectivesMenu() const;
|
||||
void PopulateSavedPerspectivesMenu(const std::vector<std::string>& label_names) const;
|
||||
|
|
|
@ -3,7 +3,6 @@ set(SRCS
|
|||
Disassembler.cpp
|
||||
UICommon.cpp
|
||||
USBUtils.cpp
|
||||
WiiUtils.cpp
|
||||
)
|
||||
|
||||
if(USE_X11)
|
||||
|
|
|
@ -50,14 +50,12 @@
|
|||
<ClCompile Include="USBUtils.cpp">
|
||||
<DisableSpecificWarnings>4200;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WiiUtils.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CommandLineParse.h" />
|
||||
<ClInclude Include="UICommon.h" />
|
||||
<ClInclude Include="Disassembler.h" />
|
||||
<ClInclude Include="USBUtils.h" />
|
||||
<ClInclude Include="WiiUtils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(ExternalsDir)cpp-optparse\cpp-optparse.vcxproj">
|
||||
|
@ -67,4 +65,4 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "UICommon/WiiUtils.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/IOS/ES/ES.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "DiscIO/NANDContentLoader.h"
|
||||
#include "DiscIO/WiiWad.h"
|
||||
|
||||
namespace WiiUtils
|
||||
{
|
||||
bool InstallWAD(const std::string& wad_path)
|
||||
{
|
||||
const DiscIO::WiiWAD wad{wad_path};
|
||||
if (!wad.IsValid())
|
||||
{
|
||||
PanicAlertT("WAD installation failed: The selected file is not a valid WAD.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto tmd = wad.GetTMD();
|
||||
IOS::HLE::Kernel ios;
|
||||
const auto es = ios.GetES();
|
||||
|
||||
IOS::HLE::Device::ES::Context context;
|
||||
IOS::HLE::ReturnCode ret;
|
||||
const bool checks_enabled = SConfig::GetInstance().m_enable_signature_checks;
|
||||
while ((ret = es->ImportTicket(wad.GetTicket().GetBytes(), wad.GetCertificateChain())) < 0 ||
|
||||
(ret = es->ImportTitleInit(context, tmd.GetBytes(), wad.GetCertificateChain())) < 0)
|
||||
{
|
||||
if (checks_enabled && ret == IOS::HLE::IOSC_FAIL_CHECKVALUE &&
|
||||
AskYesNoT("This WAD has not been signed by Nintendo. Continue to import?"))
|
||||
{
|
||||
SConfig::GetInstance().m_enable_signature_checks = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
SConfig::GetInstance().m_enable_signature_checks = checks_enabled;
|
||||
PanicAlertT("WAD installation failed: Could not initialise title import.");
|
||||
return false;
|
||||
}
|
||||
SConfig::GetInstance().m_enable_signature_checks = checks_enabled;
|
||||
|
||||
const bool contents_imported = [&]() {
|
||||
const u64 title_id = tmd.GetTitleId();
|
||||
for (const IOS::ES::Content& content : tmd.GetContents())
|
||||
{
|
||||
const std::vector<u8> data = wad.GetContent(content.index);
|
||||
|
||||
if (es->ImportContentBegin(context, title_id, content.id) < 0 ||
|
||||
es->ImportContentData(context, 0, data.data(), static_cast<u32>(data.size())) < 0 ||
|
||||
es->ImportContentEnd(context, 0) < 0)
|
||||
{
|
||||
PanicAlertT("WAD installation failed: Could not import content %08x.", content.id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
|
||||
if ((contents_imported && es->ImportTitleDone(context) < 0) ||
|
||||
(!contents_imported && es->ImportTitleCancel(context) < 0))
|
||||
{
|
||||
PanicAlertT("WAD installation failed: Could not finalise title import.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DiscIO::NANDContentManager::Access().ClearCache();
|
||||
return true;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue