diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java
index 480e7472e3..8fa685128b 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java
@@ -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;
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/WiiUtils.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/WiiUtils.java
index fb1d7e90cb..61dfedf5cd 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/WiiUtils.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/WiiUtils.java
@@ -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();
diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml
index 23eccd0c10..72c4d73356 100644
--- a/Source/Android/app/src/main/res/values/strings.xml
+++ b/Source/Android/app/src/main/res/values/strings.xml
@@ -441,6 +441,7 @@
Perform Online System Update
Load Wii System Menu
Load Wii System Menu (%s)
+ Load vWii System Menu (%s)
Importing...
Exporting...
Do not close the app!
diff --git a/Source/Android/jni/WiiUtils.cpp b/Source/Android/jni/WiiUtils.cpp
index e26ac485e9..5939816b8e 100644
--- a/Source/Android/jni/WiiUtils.cpp
+++ b/Source/Android/jni/WiiUtils.cpp
@@ -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
diff --git a/Source/Core/Core/Boot/AncastTypes.h b/Source/Core/Core/Boot/AncastTypes.h
new file mode 100644
index 0000000000..0785964497
--- /dev/null
+++ b/Source/Core/Core/Boot/AncastTypes.h
@@ -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);
diff --git a/Source/Core/Core/Boot/DolReader.cpp b/Source/Core/Core/Boot/DolReader.cpp
index b9db73ebfe..15fd3a9f5e 100644
--- a/Source/Core/Core/Boot/DolReader.cpp
+++ b/Source/Core/Core/Boot/DolReader.cpp
@@ -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 buffer) : BootExecutableReader(std::move(buffer))
@@ -94,6 +95,17 @@ bool DolReader::Initialize(const std::vector& 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(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 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 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;
+}
diff --git a/Source/Core/Core/Boot/DolReader.h b/Source/Core/Core/Boot/DolReader.h
index 8816f69da3..5025b1c82d 100644
--- a/Source/Core/Core/Boot/DolReader.h
+++ b/Source/Core/Core/Boot/DolReader.h
@@ -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& buffer);
+
+ bool LoadAncastIntoMemory() const;
};
diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index d8a562761d..b197cfa4d5 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -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
diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp
index 7adb86e772..f091cf1769 100644
--- a/Source/Core/Core/IOS/ES/Formats.cpp
+++ b/Source/Core/Core/IOS/ES/Formats.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];
diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h
index 5918a04a0e..d1c19ce75c 100644
--- a/Source/Core/Core/IOS/ES/Formats.h
+++ b/Source/Core/Core/IOS/ES/Formats.h
@@ -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,
diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp
index 341f204d66..25808585f0 100644
--- a/Source/Core/Core/IOS/IOS.cpp
+++ b/Source/Core/Core/IOS/IOS.cpp
@@ -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.");
}
diff --git a/Source/Core/DiscIO/Enums.cpp b/Source/Core/DiscIO/Enums.cpp
index 0594e3161a..393f30076f 100644
--- a/Source/Core/DiscIO/Enums.cpp
+++ b/Source/Core/DiscIO/Enums.cpp
@@ -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')
diff --git a/Source/Core/DiscIO/Enums.h b/Source/Core/DiscIO/Enums.h
index 087234b59d..ebbef8cd42 100644
--- a/Source/Core/DiscIO/Enums.h
+++ b/Source/Core/DiscIO/Enums.h
@@ -88,7 +88,7 @@ Country CountryCodeToCountry(u8 country_code, Platform platform, Region region =
std::optional 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
diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp
index f901f3e726..f36a1d9a1d 100644
--- a/Source/Core/DolphinQt/MenuBar.cpp
+++ b/Source/Core/DolphinQt/MenuBar.cpp
@@ -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());