Merge pull request #11188 from GaryOderNichts/feature/vwii_ancast
Support for loading vWii System Menu
This commit is contained in:
commit
804e42150e
|
@ -161,9 +161,12 @@ public final class MainActivity extends AppCompatActivity
|
|||
|
||||
if (WiiUtils.isSystemMenuInstalled())
|
||||
{
|
||||
int resId = WiiUtils.isSystemMenuvWii() ?
|
||||
R.string.grid_menu_load_vwii_system_menu_installed :
|
||||
R.string.grid_menu_load_wii_system_menu_installed;
|
||||
|
||||
menu.findItem(R.id.menu_load_wii_system_menu).setTitle(
|
||||
getString(R.string.grid_menu_load_wii_system_menu_installed,
|
||||
WiiUtils.getSystemMenuVersion()));
|
||||
getString(resId, WiiUtils.getSystemMenuVersion()));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -32,6 +32,8 @@ public final class WiiUtils
|
|||
|
||||
public static native boolean isSystemMenuInstalled();
|
||||
|
||||
public static native boolean isSystemMenuvWii();
|
||||
|
||||
public static native String getSystemMenuVersion();
|
||||
|
||||
public static native boolean syncSdFolderToSdImage();
|
||||
|
|
|
@ -441,6 +441,7 @@
|
|||
<string name="grid_menu_online_system_update">Perform Online System Update</string>
|
||||
<string name="grid_menu_load_wii_system_menu">Load Wii System Menu</string>
|
||||
<string name="grid_menu_load_wii_system_menu_installed">Load Wii System Menu (%s)</string>
|
||||
<string name="grid_menu_load_vwii_system_menu_installed">Load vWii System Menu (%s)</string>
|
||||
<string name="import_in_progress">Importing...</string>
|
||||
<string name="export_in_progress">Exporting...</string>
|
||||
<string name="do_not_close_app">Do not close the app!</string>
|
||||
|
|
|
@ -165,6 +165,15 @@ Java_org_dolphinemu_dolphinemu_utils_WiiUtils_isSystemMenuInstalled(JNIEnv* env,
|
|||
return tmd.IsValid();
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_utils_WiiUtils_isSystemMenuvWii(JNIEnv* env, jclass)
|
||||
{
|
||||
IOS::HLE::Kernel ios;
|
||||
const auto tmd = ios.GetES()->FindInstalledTMD(Titles::SYSTEM_MENU);
|
||||
|
||||
return tmd.IsvWii();
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_utils_WiiUtils_getSystemMenuVersion(JNIEnv* env, jclass)
|
||||
{
|
||||
|
@ -176,7 +185,7 @@ Java_org_dolphinemu_dolphinemu_utils_WiiUtils_getSystemMenuVersion(JNIEnv* env,
|
|||
return ToJString(env, "");
|
||||
}
|
||||
|
||||
return ToJString(env, DiscIO::GetSysMenuVersionString(tmd.GetTitleVersion()));
|
||||
return ToJString(env, DiscIO::GetSysMenuVersionString(tmd.GetTitleVersion(), tmd.IsvWii()));
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Crypto/SHA1.h"
|
||||
|
||||
// Magic number
|
||||
constexpr u32 ANCAST_MAGIC = 0xEFA282D9;
|
||||
|
||||
// The location in memory where PPC ancast images are booted from
|
||||
constexpr u32 ESPRESSO_ANCAST_LOCATION_PHYS = 0x01330000;
|
||||
constexpr u32 ESPRESSO_ANCAST_LOCATION_VIRT = 0x81330000;
|
||||
|
||||
// The image type
|
||||
enum AncastImageType
|
||||
{
|
||||
ANCAST_IMAGE_TYPE_ESPRESSO_WIIU = 0x11,
|
||||
ANCAST_IMAGE_TYPE_ESPRESSO_WII = 0x13,
|
||||
ANCAST_IMAGE_TYPE_STARBUCK_NAND = 0x21,
|
||||
ANCAST_IMAGE_TYPE_STARBUCK_SD = 0x22,
|
||||
};
|
||||
|
||||
// The console type of the image
|
||||
enum AncastConsoleType
|
||||
{
|
||||
ANCAST_CONSOLE_TYPE_DEV = 0x01,
|
||||
ANCAST_CONSOLE_TYPE_RETAIL = 0x02,
|
||||
};
|
||||
|
||||
// Start of each header
|
||||
struct AncastHeaderBlock
|
||||
{
|
||||
u32 magic;
|
||||
u32 unknown;
|
||||
u32 header_block_size;
|
||||
u8 padding[0x14];
|
||||
};
|
||||
static_assert(sizeof(AncastHeaderBlock) == 0x20);
|
||||
|
||||
// Signature block for type 1
|
||||
struct AncastSignatureBlockv1
|
||||
{
|
||||
u32 signature_type;
|
||||
u8 signature[0x38];
|
||||
u8 padding[0x44];
|
||||
};
|
||||
|
||||
// Signature block for type 2
|
||||
struct AncastSignatureBlockv2
|
||||
{
|
||||
u32 signature_type;
|
||||
u8 signature[0x100];
|
||||
u8 padding[0x7c];
|
||||
};
|
||||
|
||||
// General info about the image
|
||||
struct AncastInfoBlock
|
||||
{
|
||||
u32 unknown;
|
||||
u32 image_type;
|
||||
u32 console_type;
|
||||
u32 body_size;
|
||||
Common::SHA1::Digest body_hash;
|
||||
u32 version;
|
||||
u8 padding[0x38];
|
||||
};
|
||||
|
||||
// The header of espresso ancast images
|
||||
struct EspressoAncastHeader
|
||||
{
|
||||
AncastHeaderBlock header_block;
|
||||
AncastSignatureBlockv1 signature_block;
|
||||
AncastInfoBlock info_block;
|
||||
};
|
||||
static_assert(sizeof(EspressoAncastHeader) == 0x100);
|
||||
|
||||
// The header of starbuck ancast images
|
||||
struct StarbuckAncastHeader
|
||||
{
|
||||
AncastHeaderBlock header_block;
|
||||
AncastSignatureBlockv2 signature_block;
|
||||
AncastInfoBlock info_block;
|
||||
};
|
||||
static_assert(sizeof(StarbuckAncastHeader) == 0x200);
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/Boot/AncastTypes.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
|
||||
DolReader::DolReader(std::vector<u8> buffer) : BootExecutableReader(std::move(buffer))
|
||||
|
@ -94,6 +95,17 @@ bool DolReader::Initialize(const std::vector<u8>& buffer)
|
|||
}
|
||||
}
|
||||
|
||||
// Check if this dol contains an ancast image
|
||||
// The ancast image will always be in the first data section
|
||||
m_is_ancast = false;
|
||||
if (m_data_sections[0].size() > sizeof(EspressoAncastHeader) &&
|
||||
m_dolheader.dataAddress[0] == ESPRESSO_ANCAST_LOCATION_VIRT)
|
||||
{
|
||||
// Check for the ancast magic
|
||||
if (Common::swap32(m_data_sections[0].data()) == ANCAST_MAGIC)
|
||||
m_is_ancast = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -102,6 +114,9 @@ bool DolReader::LoadIntoMemory(bool only_in_mem1) const
|
|||
if (!m_is_valid)
|
||||
return false;
|
||||
|
||||
if (m_is_ancast)
|
||||
return LoadAncastIntoMemory();
|
||||
|
||||
// load all text (code) sections
|
||||
for (size_t i = 0; i < m_text_sections.size(); ++i)
|
||||
if (!m_text_sections[i].empty() &&
|
||||
|
@ -124,3 +139,91 @@ bool DolReader::LoadIntoMemory(bool only_in_mem1) const
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
// On a real console this would be done in the Espresso bootrom
|
||||
bool DolReader::LoadAncastIntoMemory() const
|
||||
{
|
||||
// The ancast image will always be in data section 0
|
||||
const auto& section = m_data_sections[0];
|
||||
const u32 section_address = m_dolheader.dataAddress[0];
|
||||
|
||||
const auto* header = reinterpret_cast<const EspressoAncastHeader*>(section.data());
|
||||
|
||||
// Verify header block size
|
||||
if (Common::swap32(header->header_block.header_block_size) != sizeof(AncastHeaderBlock))
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Ancast: Invalid header block size: 0x{:x}",
|
||||
Common::swap32(header->header_block.header_block_size));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this is a PPC ancast image
|
||||
if (Common::swap32(header->signature_block.signature_type) != 0x01)
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Ancast: Invalid signature type: 0x{:x}",
|
||||
Common::swap32(header->signature_block.signature_type));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this is a Wii-Mode ancast image
|
||||
if (Common::swap32(header->info_block.image_type) != ANCAST_IMAGE_TYPE_ESPRESSO_WII)
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Ancast: Invalid image type: 0x{:x}",
|
||||
Common::swap32(header->info_block.image_type));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the body size
|
||||
const u32 body_size = Common::swap32(header->info_block.body_size);
|
||||
if (body_size + sizeof(EspressoAncastHeader) > section.size())
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Ancast: Invalid body size: 0x{:x}", body_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the body hash
|
||||
const auto digest =
|
||||
Common::SHA1::CalculateDigest(section.data() + sizeof(EspressoAncastHeader), body_size);
|
||||
if (digest != header->info_block.body_hash)
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Ancast: Body hash mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if this is a retail or dev image
|
||||
bool is_dev = false;
|
||||
if (Common::swap32(header->info_block.console_type) == ANCAST_CONSOLE_TYPE_DEV)
|
||||
{
|
||||
is_dev = true;
|
||||
}
|
||||
else if (Common::swap32(header->info_block.console_type) != ANCAST_CONSOLE_TYPE_RETAIL)
|
||||
{
|
||||
ERROR_LOG_FMT(BOOT, "Ancast: Invalid console type: 0x{:x}",
|
||||
Common::swap32(header->info_block.console_type));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decrypt the Ancast image
|
||||
static constexpr u8 vwii_ancast_retail_key[0x10] = {0x2e, 0xfe, 0x8a, 0xbc, 0xed, 0xbb,
|
||||
0x7b, 0xaa, 0xe3, 0xc0, 0xed, 0x92,
|
||||
0xfa, 0x29, 0xf8, 0x66};
|
||||
static constexpr u8 vwii_ancast_dev_key[0x10] = {0x26, 0xaf, 0xf4, 0xbb, 0xac, 0x88, 0xbb, 0x76,
|
||||
0x9d, 0xfc, 0x54, 0xdd, 0x56, 0xd8, 0xef, 0xbd};
|
||||
std::unique_ptr<Common::AES::Context> ctx =
|
||||
Common::AES::CreateContextDecrypt(is_dev ? vwii_ancast_dev_key : vwii_ancast_retail_key);
|
||||
|
||||
static constexpr u8 vwii_ancast_iv[0x10] = {0x59, 0x6d, 0x5a, 0x9a, 0xd7, 0x05, 0xf9, 0x4f,
|
||||
0xe1, 0x58, 0x02, 0x6f, 0xea, 0xa7, 0xb8, 0x87};
|
||||
std::vector<u8> decrypted(body_size);
|
||||
if (!ctx->Crypt(vwii_ancast_iv, section.data() + sizeof(EspressoAncastHeader), decrypted.data(),
|
||||
body_size))
|
||||
return false;
|
||||
|
||||
// Copy the Ancast header to the emu
|
||||
Memory::CopyToEmu(section_address, header, sizeof(EspressoAncastHeader));
|
||||
|
||||
// Copy the decrypted body to the emu
|
||||
Memory::CopyToEmu(section_address + sizeof(EspressoAncastHeader), decrypted.data(), body_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ public:
|
|||
|
||||
bool IsValid() const override { return m_is_valid; }
|
||||
bool IsWii() const override { return m_is_wii; }
|
||||
bool IsAncast() const { return m_is_ancast; };
|
||||
u32 GetEntryPoint() const override { return m_dolheader.entryPoint; }
|
||||
bool LoadIntoMemory(bool only_in_mem1 = false) const override;
|
||||
bool LoadSymbols() const override { return false; }
|
||||
|
@ -57,7 +58,10 @@ private:
|
|||
|
||||
bool m_is_valid;
|
||||
bool m_is_wii;
|
||||
bool m_is_ancast;
|
||||
|
||||
// Copy sections to internal buffers
|
||||
bool Initialize(const std::vector<u8>& buffer);
|
||||
|
||||
bool LoadAncastIntoMemory() const;
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@ add_library(core
|
|||
ActionReplay.h
|
||||
ARDecrypt.cpp
|
||||
ARDecrypt.h
|
||||
Boot/AncastTypes.h
|
||||
Boot/Boot_BS2Emu.cpp
|
||||
Boot/Boot_WiiWAD.cpp
|
||||
Boot/Boot.cpp
|
||||
|
|
|
@ -291,6 +291,11 @@ DiscIO::Region TMDReader::GetRegion() const
|
|||
return region <= DiscIO::Region::NTSC_K ? region : DiscIO::Region::Unknown;
|
||||
}
|
||||
|
||||
bool TMDReader::IsvWii() const
|
||||
{
|
||||
return *(m_bytes.data() + offsetof(TMDHeader, is_vwii));
|
||||
}
|
||||
|
||||
std::string TMDReader::GetGameID() const
|
||||
{
|
||||
char game_id[6];
|
||||
|
|
|
@ -68,6 +68,8 @@ struct TMDHeader
|
|||
u8 tmd_version;
|
||||
u8 ca_crl_version;
|
||||
u8 signer_crl_version;
|
||||
// This is usually an always 0 padding byte, which is set to 1 on vWii TMDs
|
||||
u8 is_vwii;
|
||||
u64 ios_id;
|
||||
u64 title_id;
|
||||
u32 title_flags;
|
||||
|
@ -85,6 +87,7 @@ struct TMDHeader
|
|||
u16 fill2;
|
||||
};
|
||||
static_assert(sizeof(TMDHeader) == 0x1e4, "TMDHeader has the wrong size");
|
||||
static_assert(offsetof(TMDHeader, ios_id) == 0x184);
|
||||
|
||||
struct Content
|
||||
{
|
||||
|
@ -200,6 +203,7 @@ public:
|
|||
u16 GetTitleVersion() const;
|
||||
u16 GetGroupId() const;
|
||||
DiscIO::Region GetRegion() const;
|
||||
bool IsvWii() const;
|
||||
|
||||
// Constructs a 6-character game ID in the format typically used by Dolphin.
|
||||
// If the 6-character game ID would contain unprintable characters,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Timer.h"
|
||||
#include "Core/Boot/AncastTypes.h"
|
||||
#include "Core/Boot/DolReader.h"
|
||||
#include "Core/Boot/ElfReader.h"
|
||||
#include "Core/CommonTitles.h"
|
||||
|
@ -206,6 +207,15 @@ static void ReleasePPC()
|
|||
PC = 0x3400;
|
||||
}
|
||||
|
||||
static void ReleasePPCAncast()
|
||||
{
|
||||
Memory::Write_U32(0, 0);
|
||||
// On a real console the Espresso verifies and decrypts the Ancast image,
|
||||
// then jumps to the decrypted ancast body.
|
||||
// The Ancast loader already did this, so just jump to the decrypted body.
|
||||
PC = ESPRESSO_ANCAST_LOCATION_VIRT + sizeof(EspressoAncastHeader);
|
||||
}
|
||||
|
||||
void RAMOverrideForIOSMemoryValues(MemorySetupType setup_type)
|
||||
{
|
||||
// Don't touch anything if the feature isn't enabled.
|
||||
|
@ -393,11 +403,14 @@ bool Kernel::BootstrapPPC(const std::string& boot_content_path)
|
|||
// Reset the PPC and pause its execution until we're ready.
|
||||
ResetAndPausePPC();
|
||||
|
||||
if (dol.IsAncast())
|
||||
INFO_LOG_FMT(IOS, "BootstrapPPC: Loading ancast image");
|
||||
|
||||
if (!dol.LoadIntoMemory())
|
||||
return false;
|
||||
|
||||
INFO_LOG_FMT(IOS, "BootstrapPPC: {}", boot_content_path);
|
||||
CoreTiming::ScheduleEvent(ticks, s_event_finish_ppc_bootstrap);
|
||||
CoreTiming::ScheduleEvent(ticks, s_event_finish_ppc_bootstrap, dol.IsAncast());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -865,7 +878,13 @@ IOSC& Kernel::GetIOSC()
|
|||
|
||||
static void FinishPPCBootstrap(u64 userdata, s64 cycles_late)
|
||||
{
|
||||
ReleasePPC();
|
||||
// See Kernel::BootstrapPPC
|
||||
const bool is_ancast = userdata == 1;
|
||||
if (is_ancast)
|
||||
ReleasePPCAncast();
|
||||
else
|
||||
ReleasePPC();
|
||||
|
||||
SConfig::OnNewTitleLoad();
|
||||
INFO_LOG_FMT(IOS, "Bootstrapping done.");
|
||||
}
|
||||
|
|
|
@ -362,7 +362,7 @@ Region GetSysMenuRegion(u16 title_version)
|
|||
}
|
||||
}
|
||||
|
||||
std::string GetSysMenuVersionString(u16 title_version)
|
||||
std::string GetSysMenuVersionString(u16 title_version, bool is_vwii)
|
||||
{
|
||||
std::string version;
|
||||
char region_letter = '\0';
|
||||
|
@ -386,52 +386,74 @@ std::string GetSysMenuVersionString(u16 title_version)
|
|||
break;
|
||||
}
|
||||
|
||||
switch (title_version & 0xff0)
|
||||
if (is_vwii)
|
||||
{
|
||||
case 32:
|
||||
version = "1.0";
|
||||
break;
|
||||
case 96:
|
||||
case 128:
|
||||
version = "2.0";
|
||||
break;
|
||||
case 160:
|
||||
version = "2.1";
|
||||
break;
|
||||
case 192:
|
||||
version = "2.2";
|
||||
break;
|
||||
case 224:
|
||||
version = "3.0";
|
||||
break;
|
||||
case 256:
|
||||
version = "3.1";
|
||||
break;
|
||||
case 288:
|
||||
version = "3.2";
|
||||
break;
|
||||
case 320:
|
||||
case 352:
|
||||
version = "3.3";
|
||||
break;
|
||||
case 384:
|
||||
version = (region_letter != 'K' ? "3.4" : "3.5");
|
||||
break;
|
||||
case 416:
|
||||
version = "4.0";
|
||||
break;
|
||||
case 448:
|
||||
version = "4.1";
|
||||
break;
|
||||
case 480:
|
||||
version = "4.2";
|
||||
break;
|
||||
case 512:
|
||||
version = "4.3";
|
||||
break;
|
||||
default:
|
||||
version = "?.?";
|
||||
break;
|
||||
// For vWii return the Wii U version which installed the menu
|
||||
switch (title_version & 0xff0)
|
||||
{
|
||||
case 512:
|
||||
version = "1.0.0";
|
||||
break;
|
||||
case 544:
|
||||
version = "4.0.0";
|
||||
break;
|
||||
case 608:
|
||||
version = "5.2.0";
|
||||
break;
|
||||
default:
|
||||
version = "?.?.?";
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (title_version & 0xff0)
|
||||
{
|
||||
case 32:
|
||||
version = "1.0";
|
||||
break;
|
||||
case 96:
|
||||
case 128:
|
||||
version = "2.0";
|
||||
break;
|
||||
case 160:
|
||||
version = "2.1";
|
||||
break;
|
||||
case 192:
|
||||
version = "2.2";
|
||||
break;
|
||||
case 224:
|
||||
version = "3.0";
|
||||
break;
|
||||
case 256:
|
||||
version = "3.1";
|
||||
break;
|
||||
case 288:
|
||||
version = "3.2";
|
||||
break;
|
||||
case 320:
|
||||
case 352:
|
||||
version = "3.3";
|
||||
break;
|
||||
case 384:
|
||||
version = (region_letter != 'K' ? "3.4" : "3.5");
|
||||
break;
|
||||
case 416:
|
||||
version = "4.0";
|
||||
break;
|
||||
case 448:
|
||||
version = "4.1";
|
||||
break;
|
||||
case 480:
|
||||
version = "4.2";
|
||||
break;
|
||||
case 512:
|
||||
version = "4.3";
|
||||
break;
|
||||
default:
|
||||
version = "?.?";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (region_letter != '\0')
|
||||
|
|
|
@ -88,7 +88,7 @@ Country CountryCodeToCountry(u8 country_code, Platform platform, Region region =
|
|||
std::optional<u16> revision = {});
|
||||
|
||||
Region GetSysMenuRegion(u16 title_version);
|
||||
std::string GetSysMenuVersionString(u16 title_version);
|
||||
std::string GetSysMenuVersionString(u16 title_version, bool is_vwii);
|
||||
|
||||
const std::string& GetCompanyFromID(const std::string& company_id);
|
||||
} // namespace DiscIO
|
||||
|
|
|
@ -1010,10 +1010,14 @@ void MenuBar::UpdateToolsMenu(bool emulation_started)
|
|||
const auto tmd = ios.GetES()->FindInstalledTMD(Titles::SYSTEM_MENU);
|
||||
|
||||
const QString sysmenu_version =
|
||||
tmd.IsValid() ?
|
||||
QString::fromStdString(DiscIO::GetSysMenuVersionString(tmd.GetTitleVersion())) :
|
||||
QString{};
|
||||
m_boot_sysmenu->setText(tr("Load Wii System Menu %1").arg(sysmenu_version));
|
||||
tmd.IsValid() ? QString::fromStdString(
|
||||
DiscIO::GetSysMenuVersionString(tmd.GetTitleVersion(), tmd.IsvWii())) :
|
||||
QString{};
|
||||
|
||||
const QString sysmenu_text = (tmd.IsValid() && tmd.IsvWii()) ? tr("Load vWii System Menu %1") :
|
||||
tr("Load Wii System Menu %1");
|
||||
|
||||
m_boot_sysmenu->setText(sysmenu_text.arg(sysmenu_version));
|
||||
|
||||
m_boot_sysmenu->setEnabled(tmd.IsValid());
|
||||
|
||||
|
|
Loading…
Reference in New Issue