From 82d20ce352f51d05fbdc998cc1abd4514d5623cc Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Fri, 21 Oct 2022 23:19:40 +0200 Subject: [PATCH 1/2] Support for loading Ancast images --- Source/Core/Core/Boot/AncastTypes.h | 86 +++++++++++++++++++++++ Source/Core/Core/Boot/DolReader.cpp | 103 ++++++++++++++++++++++++++++ Source/Core/Core/Boot/DolReader.h | 4 ++ Source/Core/Core/CMakeLists.txt | 1 + Source/Core/Core/IOS/IOS.cpp | 23 ++++++- 5 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 Source/Core/Core/Boot/AncastTypes.h 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/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."); } From 88c57a00a351148c7c66787dc3af1c2e5e641305 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Fri, 21 Oct 2022 23:22:49 +0200 Subject: [PATCH 2/2] Show vWii System Menu version in Menu Bar --- .../dolphinemu/ui/main/MainActivity.java | 7 +- .../dolphinemu/dolphinemu/utils/WiiUtils.java | 2 + .../app/src/main/res/values/strings.xml | 1 + Source/Android/jni/WiiUtils.cpp | 11 +- Source/Core/Core/IOS/ES/Formats.cpp | 5 + Source/Core/Core/IOS/ES/Formats.h | 4 + Source/Core/DiscIO/Enums.cpp | 114 +++++++++++------- Source/Core/DiscIO/Enums.h | 2 +- Source/Core/DolphinQt/MenuBar.cpp | 12 +- 9 files changed, 104 insertions(+), 54 deletions(-) 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/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/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());