mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-02-10 14:39:01 +01:00
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
|
State.cpp
|
||||||
TitleDatabase.cpp
|
TitleDatabase.cpp
|
||||||
WiiRoot.cpp
|
WiiRoot.cpp
|
||||||
|
WiiUtils.cpp
|
||||||
Boot/Boot_BS2Emu.cpp
|
Boot/Boot_BS2Emu.cpp
|
||||||
Boot/Boot.cpp
|
Boot/Boot.cpp
|
||||||
Boot/Boot_WiiWAD.cpp
|
Boot/Boot_WiiWAD.cpp
|
||||||
|
@ -290,6 +290,7 @@
|
|||||||
<ClCompile Include="State.cpp" />
|
<ClCompile Include="State.cpp" />
|
||||||
<ClCompile Include="TitleDatabase.cpp" />
|
<ClCompile Include="TitleDatabase.cpp" />
|
||||||
<ClCompile Include="WiiRoot.cpp" />
|
<ClCompile Include="WiiRoot.cpp" />
|
||||||
|
<ClCompile Include="WiiUtils.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="ActionReplay.h" />
|
<ClInclude Include="ActionReplay.h" />
|
||||||
@ -524,6 +525,7 @@
|
|||||||
<ClInclude Include="Titles.h" />
|
<ClInclude Include="Titles.h" />
|
||||||
<ClInclude Include="TitleDatabase.h" />
|
<ClInclude Include="TitleDatabase.h" />
|
||||||
<ClInclude Include="WiiRoot.h" />
|
<ClInclude Include="WiiRoot.h" />
|
||||||
|
<ClInclude Include="WiiUtils.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Text Include="CMakeLists.txt" />
|
<Text Include="CMakeLists.txt" />
|
||||||
|
@ -179,6 +179,7 @@
|
|||||||
<ClCompile Include="State.cpp" />
|
<ClCompile Include="State.cpp" />
|
||||||
<ClCompile Include="TitleDatabase.cpp" />
|
<ClCompile Include="TitleDatabase.cpp" />
|
||||||
<ClCompile Include="WiiRoot.cpp" />
|
<ClCompile Include="WiiRoot.cpp" />
|
||||||
|
<ClCompile Include="WiiUtils.cpp" />
|
||||||
<ClCompile Include="ActionReplay.cpp">
|
<ClCompile Include="ActionReplay.cpp">
|
||||||
<Filter>ActionReplay</Filter>
|
<Filter>ActionReplay</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@ -898,6 +899,7 @@
|
|||||||
<ClInclude Include="Titles.h" />
|
<ClInclude Include="Titles.h" />
|
||||||
<ClInclude Include="TitleDatabase.h" />
|
<ClInclude Include="TitleDatabase.h" />
|
||||||
<ClInclude Include="WiiRoot.h" />
|
<ClInclude Include="WiiRoot.h" />
|
||||||
|
<ClInclude Include="WiiUtils.h" />
|
||||||
<ClInclude Include="ActionReplay.h">
|
<ClInclude Include="ActionReplay.h">
|
||||||
<Filter>ActionReplay</Filter>
|
<Filter>ActionReplay</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@ -1543,4 +1545,4 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Text Include="CMakeLists.txt" />
|
<Text Include="CMakeLists.txt" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -843,30 +843,21 @@ ReturnCode ES::ReadCertStore(std::vector<u8>* buffer) const
|
|||||||
|
|
||||||
ReturnCode ES::WriteNewCertToStore(const IOS::ES::CertReader& cert)
|
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.
|
// Read the current store to determine if the new cert needs to be written.
|
||||||
const u64 file_size = store_file.GetSize();
|
std::vector<u8> current_store;
|
||||||
if (file_size != 0)
|
const ReturnCode ret = ReadCertStore(¤t_store);
|
||||||
|
if (ret == IPC_SUCCESS)
|
||||||
{
|
{
|
||||||
std::vector<u8> certs_bytes(file_size);
|
const std::map<std::string, IOS::ES::CertReader> certs = IOS::ES::ParseCertChain(current_store);
|
||||||
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);
|
|
||||||
// The cert is already present in the store. Nothing to do.
|
// The cert is already present in the store. Nothing to do.
|
||||||
if (certs.find(cert.GetName()) != certs.end())
|
if (certs.find(cert.GetName()) != certs.end())
|
||||||
return IPC_SUCCESS;
|
return IPC_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, write the new cert at the end of the store.
|
// 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.
|
const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys";
|
||||||
store_file.Seek(0, SEEK_END);
|
File::IOFile store_file{store_path, "ab"};
|
||||||
if (!store_file.WriteBytes(cert.GetBytes().data(), cert.GetBytes().size()))
|
if (!store_file || !store_file.WriteBytes(cert.GetBytes().data(), cert.GetBytes().size()))
|
||||||
return ES_EIO;
|
return ES_EIO;
|
||||||
return IPC_SUCCESS;
|
return IPC_SUCCESS;
|
||||||
}
|
}
|
||||||
|
498
Source/Core/Core/WiiUtils.cpp
Normal file
498
Source/Core/Core/WiiUtils.cpp
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
40
Source/Core/Core/WiiUtils.h
Normal file
40
Source/Core/Core/WiiUtils.h
Normal file
@ -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
|
Resources.cpp
|
||||||
Settings.cpp
|
Settings.cpp
|
||||||
ToolBar.cpp
|
ToolBar.cpp
|
||||||
|
WiiUpdate.cpp
|
||||||
|
WiiUpdate.h
|
||||||
Config/ControllersWindow.cpp
|
Config/ControllersWindow.cpp
|
||||||
Config/FilesystemWidget.cpp
|
Config/FilesystemWidget.cpp
|
||||||
Config/InfoWidget.cpp
|
Config/InfoWidget.cpp
|
||||||
|
@ -164,6 +164,7 @@
|
|||||||
<ClCompile Include="Settings\InterfacePane.cpp" />
|
<ClCompile Include="Settings\InterfacePane.cpp" />
|
||||||
<ClCompile Include="Settings\PathPane.cpp" />
|
<ClCompile Include="Settings\PathPane.cpp" />
|
||||||
<ClCompile Include="ToolBar.cpp" />
|
<ClCompile Include="ToolBar.cpp" />
|
||||||
|
<ClCompile Include="WiiUpdate.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!--Put standard C/C++ headers here-->
|
<!--Put standard C/C++ headers here-->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -185,6 +186,7 @@
|
|||||||
<ClInclude Include="QtUtils\ElidedButton.h" />
|
<ClInclude Include="QtUtils\ElidedButton.h" />
|
||||||
<ClInclude Include="Resources.h" />
|
<ClInclude Include="Resources.h" />
|
||||||
<ClInclude Include="Settings\PathPane.h" />
|
<ClInclude Include="Settings\PathPane.h" />
|
||||||
|
<ClInclude Include="WiiUpdate.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Text Include="CMakeLists.txt" />
|
<Text Include="CMakeLists.txt" />
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
#include "Core/HW/WiiSaveCrypted.h"
|
#include "Core/HW/WiiSaveCrypted.h"
|
||||||
#include "Core/IOS/ES/ES.h"
|
#include "Core/IOS/ES/ES.h"
|
||||||
#include "Core/IOS/IOS.h"
|
#include "Core/IOS/IOS.h"
|
||||||
|
#include "Core/WiiUtils.h"
|
||||||
#include "DiscIO/Blob.h"
|
#include "DiscIO/Blob.h"
|
||||||
#include "DiscIO/Enums.h"
|
#include "DiscIO/Enums.h"
|
||||||
#include "DiscIO/NANDContentLoader.h"
|
#include "DiscIO/NANDContentLoader.h"
|
||||||
@ -22,7 +23,6 @@
|
|||||||
#include "DolphinQt2/GameList/GameFile.h"
|
#include "DolphinQt2/GameList/GameFile.h"
|
||||||
#include "DolphinQt2/Resources.h"
|
#include "DolphinQt2/Resources.h"
|
||||||
#include "DolphinQt2/Settings.h"
|
#include "DolphinQt2/Settings.h"
|
||||||
#include "UICommon/WiiUtils.h"
|
|
||||||
|
|
||||||
static const int CACHE_VERSION = 13; // Last changed in PR #3261
|
static const int CACHE_VERSION = 13; // Last changed in PR #3261
|
||||||
static const int DATASTREAM_VERSION = QDataStream::Qt_5_5;
|
static const int DATASTREAM_VERSION = QDataStream::Qt_5_5;
|
||||||
|
@ -30,7 +30,6 @@
|
|||||||
|
|
||||||
#include "DolphinQt2/AboutDialog.h"
|
#include "DolphinQt2/AboutDialog.h"
|
||||||
#include "DolphinQt2/Config/ControllersWindow.h"
|
#include "DolphinQt2/Config/ControllersWindow.h"
|
||||||
|
|
||||||
#include "DolphinQt2/Config/Mapping/MappingWindow.h"
|
#include "DolphinQt2/Config/Mapping/MappingWindow.h"
|
||||||
#include "DolphinQt2/Config/SettingsWindow.h"
|
#include "DolphinQt2/Config/SettingsWindow.h"
|
||||||
#include "DolphinQt2/Host.h"
|
#include "DolphinQt2/Host.h"
|
||||||
@ -39,6 +38,7 @@
|
|||||||
#include "DolphinQt2/QtUtils/WindowActivationEventFilter.h"
|
#include "DolphinQt2/QtUtils/WindowActivationEventFilter.h"
|
||||||
#include "DolphinQt2/Resources.h"
|
#include "DolphinQt2/Resources.h"
|
||||||
#include "DolphinQt2/Settings.h"
|
#include "DolphinQt2/Settings.h"
|
||||||
|
#include "DolphinQt2/WiiUpdate.h"
|
||||||
|
|
||||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||||
|
|
||||||
@ -165,6 +165,9 @@ void MainWindow::ConnectMenuBar()
|
|||||||
// Options
|
// Options
|
||||||
connect(m_menu_bar, &MenuBar::ConfigureHotkeys, this, &MainWindow::ShowHotkeyDialog);
|
connect(m_menu_bar, &MenuBar::ConfigureHotkeys, this, &MainWindow::ShowHotkeyDialog);
|
||||||
|
|
||||||
|
// Tools
|
||||||
|
connect(m_menu_bar, &MenuBar::PerformOnlineUpdate, this, &MainWindow::PerformOnlineUpdate);
|
||||||
|
|
||||||
// View
|
// View
|
||||||
connect(m_menu_bar, &MenuBar::ShowTable, m_game_list, &GameList::SetTableView);
|
connect(m_menu_bar, &MenuBar::ShowTable, m_game_list, &GameList::SetTableView);
|
||||||
connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView);
|
connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView);
|
||||||
@ -530,6 +533,13 @@ void MainWindow::SetStateSlot(int slot)
|
|||||||
m_state_slot = 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)
|
bool MainWindow::eventFilter(QObject* object, QEvent* event)
|
||||||
{
|
{
|
||||||
if (event->type() == QEvent::Close && !Stop())
|
if (event->type() == QEvent::Close && !Stop())
|
||||||
|
@ -56,6 +56,8 @@ private slots:
|
|||||||
void StateSaveOldest();
|
void StateSaveOldest();
|
||||||
void SetStateSlot(int slot);
|
void SetStateSlot(int slot);
|
||||||
|
|
||||||
|
void PerformOnlineUpdate(const std::string& region);
|
||||||
|
|
||||||
void FullScreen();
|
void FullScreen();
|
||||||
void ScreenShot();
|
void ScreenShot();
|
||||||
|
|
||||||
|
@ -9,6 +9,9 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "Core/CommonTitles.h"
|
||||||
|
#include "Core/IOS/ES/ES.h"
|
||||||
|
#include "Core/IOS/IOS.h"
|
||||||
#include "Core/State.h"
|
#include "Core/State.h"
|
||||||
#include "DolphinQt2/AboutDialog.h"
|
#include "DolphinQt2/AboutDialog.h"
|
||||||
#include "DolphinQt2/GameList/GameFile.h"
|
#include "DolphinQt2/GameList/GameFile.h"
|
||||||
@ -43,6 +46,7 @@ void MenuBar::EmulationStarted()
|
|||||||
m_state_load_menu->setEnabled(true);
|
m_state_load_menu->setEnabled(true);
|
||||||
m_state_save_menu->setEnabled(true);
|
m_state_save_menu->setEnabled(true);
|
||||||
UpdateStateSlotMenu();
|
UpdateStateSlotMenu();
|
||||||
|
UpdateToolsMenu(true);
|
||||||
}
|
}
|
||||||
void MenuBar::EmulationPaused()
|
void MenuBar::EmulationPaused()
|
||||||
{
|
{
|
||||||
@ -66,6 +70,7 @@ void MenuBar::EmulationStopped()
|
|||||||
m_state_load_menu->setEnabled(false);
|
m_state_load_menu->setEnabled(false);
|
||||||
m_state_save_menu->setEnabled(false);
|
m_state_save_menu->setEnabled(false);
|
||||||
UpdateStateSlotMenu();
|
UpdateStateSlotMenu();
|
||||||
|
UpdateToolsMenu(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuBar::AddFileMenu()
|
void MenuBar::AddFileMenu()
|
||||||
@ -79,6 +84,17 @@ void MenuBar::AddToolsMenu()
|
|||||||
{
|
{
|
||||||
QMenu* tools_menu = addMenu(tr("Tools"));
|
QMenu* tools_menu = addMenu(tr("Tools"));
|
||||||
m_wad_install_action = tools_menu->addAction(tr("Install WAD..."), this, SLOT(InstallWAD()));
|
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()
|
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()
|
void MenuBar::InstallWAD()
|
||||||
{
|
{
|
||||||
QString wad_file = QFileDialog::getOpenFileName(this, tr("Select a title to install to NAND"),
|
QString wad_file = QFileDialog::getOpenFileName(this, tr("Select a title to install to NAND"),
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMenuBar>
|
#include <QMenuBar>
|
||||||
|
|
||||||
@ -38,6 +40,8 @@ signals:
|
|||||||
void StateSaveOldest();
|
void StateSaveOldest();
|
||||||
void SetStateSlot(int slot);
|
void SetStateSlot(int slot);
|
||||||
|
|
||||||
|
void PerformOnlineUpdate(const std::string& region);
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
void ConfigureHotkeys();
|
void ConfigureHotkeys();
|
||||||
|
|
||||||
@ -53,6 +57,7 @@ public slots:
|
|||||||
void EmulationPaused();
|
void EmulationPaused();
|
||||||
void EmulationStopped();
|
void EmulationStopped();
|
||||||
void UpdateStateSlotMenu();
|
void UpdateStateSlotMenu();
|
||||||
|
void UpdateToolsMenu(bool emulation_started);
|
||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
void InstallWAD();
|
void InstallWAD();
|
||||||
@ -79,6 +84,8 @@ private:
|
|||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
QAction* m_wad_install_action;
|
QAction* m_wad_install_action;
|
||||||
|
QMenu* m_perform_online_update_menu;
|
||||||
|
QAction* m_perform_online_update_for_current_region;
|
||||||
|
|
||||||
// Emulation
|
// Emulation
|
||||||
QAction* m_play_action;
|
QAction* m_play_action;
|
||||||
|
119
Source/Core/DolphinQt2/WiiUpdate.cpp
Normal file
119
Source/Core/DolphinQt2/WiiUpdate.cpp
Normal file
@ -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>
|
#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 OnUninstallWAD(wxCommandEvent& event);
|
||||||
void OnImportBootMiiBackup(wxCommandEvent& event);
|
void OnImportBootMiiBackup(wxCommandEvent& event);
|
||||||
void OnExtractCertificates(wxCommandEvent& event);
|
void OnExtractCertificates(wxCommandEvent& event);
|
||||||
|
void OnPerformOnlineWiiUpdate(wxCommandEvent& event);
|
||||||
void OnFifoPlayer(wxCommandEvent& event);
|
void OnFifoPlayer(wxCommandEvent& event);
|
||||||
void OnConnectWiimote(wxCommandEvent& event);
|
void OnConnectWiimote(wxCommandEvent& event);
|
||||||
void GameListChanged(wxCommandEvent& event);
|
void GameListChanged(wxCommandEvent& event);
|
||||||
|
@ -3,8 +3,11 @@
|
|||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cinttypes>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <future>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -18,7 +21,6 @@
|
|||||||
#include <wx/panel.h>
|
#include <wx/panel.h>
|
||||||
#include <wx/progdlg.h>
|
#include <wx/progdlg.h>
|
||||||
#include <wx/statusbr.h>
|
#include <wx/statusbr.h>
|
||||||
#include <wx/thread.h>
|
|
||||||
#include <wx/toolbar.h>
|
#include <wx/toolbar.h>
|
||||||
#include <wx/toplevel.h>
|
#include <wx/toplevel.h>
|
||||||
|
|
||||||
@ -54,6 +56,7 @@
|
|||||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||||
#include "Core/PowerPC/PowerPC.h"
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
#include "Core/State.h"
|
#include "Core/State.h"
|
||||||
|
#include "Core/WiiUtils.h"
|
||||||
|
|
||||||
#include "DiscIO/Enums.h"
|
#include "DiscIO/Enums.h"
|
||||||
#include "DiscIO/NANDContentLoader.h"
|
#include "DiscIO/NANDContentLoader.h"
|
||||||
@ -87,7 +90,6 @@
|
|||||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||||
|
|
||||||
#include "UICommon/UICommon.h"
|
#include "UICommon/UICommon.h"
|
||||||
#include "UICommon/WiiUtils.h"
|
|
||||||
|
|
||||||
#include "VideoCommon/RenderBase.h"
|
#include "VideoCommon/RenderBase.h"
|
||||||
#include "VideoCommon/VideoBackendBase.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::OnLoadWiiMenu, this, IDM_LOAD_WII_MENU);
|
||||||
Bind(wxEVT_MENU, &CFrame::OnImportBootMiiBackup, this, IDM_IMPORT_NAND);
|
Bind(wxEVT_MENU, &CFrame::OnImportBootMiiBackup, this, IDM_IMPORT_NAND);
|
||||||
Bind(wxEVT_MENU, &CFrame::OnExtractCertificates, this, IDM_EXTRACT_CERTIFICATES);
|
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::OnFifoPlayer, this, IDM_FIFOPLAYER);
|
||||||
Bind(wxEVT_MENU, &CFrame::OnConnectWiimote, this, IDM_CONNECT_WIIMOTE1, IDM_CONNECT_BALANCEBOARD);
|
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));
|
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
|
void CFrame::UpdateLoadWiiMenuItem() const
|
||||||
{
|
{
|
||||||
GetMenuBar()->Refresh(true, nullptr);
|
GetMenuBar()->Refresh(true, nullptr);
|
||||||
@ -1492,10 +1587,6 @@ void CFrame::UpdateGUI()
|
|||||||
GetMenuBar()
|
GetMenuBar()
|
||||||
->FindItem(IDM_LOAD_GC_IPL_EUR)
|
->FindItem(IDM_LOAD_GC_IPL_EUR)
|
||||||
->Enable(!Initialized && File::Exists(SConfig::GetInstance().GetBootROMPath(EUR_DIR)));
|
->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
|
// Tools
|
||||||
GetMenuBar()->FindItem(IDM_CHEATS)->Enable(SConfig::GetInstance().bEnableCheats);
|
GetMenuBar()->FindItem(IDM_CHEATS)->Enable(SConfig::GetInstance().bEnableCheats);
|
||||||
|
@ -104,6 +104,11 @@ enum
|
|||||||
IDM_LIST_UNINSTALL_WAD,
|
IDM_LIST_UNINSTALL_WAD,
|
||||||
IDM_IMPORT_NAND,
|
IDM_IMPORT_NAND,
|
||||||
IDM_EXTRACT_CERTIFICATES,
|
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_FIFOPLAYER,
|
||||||
IDM_LOAD_GC_IPL_JAP,
|
IDM_LOAD_GC_IPL_JAP,
|
||||||
IDM_LOAD_GC_IPL_USA,
|
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_LOAD_WII_MENU, dummy_string);
|
||||||
tools_menu->Append(IDM_IMPORT_NAND, _("Import BootMii NAND Backup..."));
|
tools_menu->Append(IDM_IMPORT_NAND, _("Import BootMii NAND Backup..."));
|
||||||
tools_menu->Append(IDM_EXTRACT_CERTIFICATES, _("Extract Certificates from NAND"));
|
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->AppendSeparator();
|
||||||
tools_menu->AppendSubMenu(wiimote_menu, _("Connect Wii Remotes"));
|
tools_menu->AppendSubMenu(wiimote_menu, _("Connect Wii Remotes"));
|
||||||
|
|
||||||
@ -562,8 +572,6 @@ void MainMenuBar::RefreshSaveStateMenuLabels() const
|
|||||||
|
|
||||||
void MainMenuBar::RefreshWiiToolsLabels() const
|
void MainMenuBar::RefreshWiiToolsLabels() const
|
||||||
{
|
{
|
||||||
RefreshWiiSystemMenuLabel();
|
|
||||||
|
|
||||||
// The Install WAD option should not be enabled while emulation is running, because
|
// 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
|
// 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).
|
// 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
|
// 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
|
// result in the emulated software being confused, or even worse, exported saves having
|
||||||
// inconsistent data.
|
// inconsistent data.
|
||||||
for (const int index : {IDM_MENU_INSTALL_WAD, IDM_EXPORT_ALL_SAVE, IDM_IMPORT_SAVE,
|
const bool enable_wii_tools = !Core::IsRunning() || !SConfig::GetInstance().bWii;
|
||||||
IDM_IMPORT_NAND, IDM_EXTRACT_CERTIFICATES})
|
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));
|
const wxString version_string = StrToWxStr(DiscIO::GetSysMenuVersionString(version_number));
|
||||||
item->Enable();
|
item->Enable();
|
||||||
item->SetItemLabel(wxString::Format(_("Load Wii System Menu %s"), version_string));
|
item->SetItemLabel(wxString::Format(_("Load Wii System Menu %s"), version_string));
|
||||||
|
EnableUpdateMenu(UpdateMenuMode::CurrentRegionOnly);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
item->Enable(false);
|
item->Enable(false);
|
||||||
item->SetItemLabel(_("Load Wii System Menu"));
|
item->SetItemLabel(_("Load Wii System Menu"));
|
||||||
|
EnableUpdateMenu(UpdateMenuMode::SpecificRegionsOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,12 @@ private:
|
|||||||
void RefreshSaveStateMenuLabels() const;
|
void RefreshSaveStateMenuLabels() const;
|
||||||
void RefreshWiiToolsLabels() const;
|
void RefreshWiiToolsLabels() const;
|
||||||
void RefreshWiiSystemMenuLabel() const;
|
void RefreshWiiSystemMenuLabel() const;
|
||||||
|
enum class UpdateMenuMode
|
||||||
|
{
|
||||||
|
CurrentRegionOnly,
|
||||||
|
SpecificRegionsOnly,
|
||||||
|
};
|
||||||
|
void EnableUpdateMenu(UpdateMenuMode mode) const;
|
||||||
|
|
||||||
void ClearSavedPerspectivesMenu() const;
|
void ClearSavedPerspectivesMenu() const;
|
||||||
void PopulateSavedPerspectivesMenu(const std::vector<std::string>& label_names) const;
|
void PopulateSavedPerspectivesMenu(const std::vector<std::string>& label_names) const;
|
||||||
|
@ -3,7 +3,6 @@ set(SRCS
|
|||||||
Disassembler.cpp
|
Disassembler.cpp
|
||||||
UICommon.cpp
|
UICommon.cpp
|
||||||
USBUtils.cpp
|
USBUtils.cpp
|
||||||
WiiUtils.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if(USE_X11)
|
if(USE_X11)
|
||||||
|
@ -50,14 +50,12 @@
|
|||||||
<ClCompile Include="USBUtils.cpp">
|
<ClCompile Include="USBUtils.cpp">
|
||||||
<DisableSpecificWarnings>4200;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
<DisableSpecificWarnings>4200;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="WiiUtils.cpp" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="CommandLineParse.h" />
|
<ClInclude Include="CommandLineParse.h" />
|
||||||
<ClInclude Include="UICommon.h" />
|
<ClInclude Include="UICommon.h" />
|
||||||
<ClInclude Include="Disassembler.h" />
|
<ClInclude Include="Disassembler.h" />
|
||||||
<ClInclude Include="USBUtils.h" />
|
<ClInclude Include="USBUtils.h" />
|
||||||
<ClInclude Include="WiiUtils.h" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="$(ExternalsDir)cpp-optparse\cpp-optparse.vcxproj">
|
<ProjectReference Include="$(ExternalsDir)cpp-optparse\cpp-optparse.vcxproj">
|
||||||
@ -67,4 +65,4 @@
|
|||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
</ImportGroup>
|
</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…
x
Reference in New Issue
Block a user