From c30635c70ae4774ac0cf056f81c598dcc74edf4d Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Sun, 1 Jan 2017 20:29:15 +0100 Subject: [PATCH 1/8] IOS/ES: Factor out the ES_Decrypt implementation. WFSI calls into ES to perform this operation, so expose a way for us to do the same thing. --- Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp | 14 ++++++++++---- Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.h | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp index 07553274c1..667945bbfa 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp @@ -105,6 +105,15 @@ void CWII_IPC_HLE_Device_es::LoadWAD(const std::string& _rContentFile) m_ContentFile = _rContentFile; } +void CWII_IPC_HLE_Device_es::DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, + u8* output) +{ + mbedtls_aes_context AES_ctx; + mbedtls_aes_setkey_dec(&AES_ctx, keyTable[key_index], 128); + memcpy(new_iv, iv, 16); + mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_DECRYPT, size, new_iv, input, output); +} + void CWII_IPC_HLE_Device_es::OpenInternal() { auto& contentLoader = DiscIO::CNANDContentManager::Access().GetNANDLoader(m_ContentFile); @@ -914,10 +923,7 @@ IPCCommandResult CWII_IPC_HLE_Device_es::IOCtlV(u32 _CommandAddress) u8* newIV = Memory::GetPointer(Buffer.PayloadBuffer[0].m_Address); u8* destination = Memory::GetPointer(Buffer.PayloadBuffer[1].m_Address); - mbedtls_aes_context AES_ctx; - mbedtls_aes_setkey_dec(&AES_ctx, keyTable[keyIndex], 128); - memcpy(newIV, IV, 16); - mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_DECRYPT, size, newIV, source, destination); + DecryptContent(keyIndex, IV, source, size, newIV, destination); _dbg_assert_msg_(WII_IPC_ES, keyIndex == 6, "IOCTL_ES_DECRYPT: Key type is not SD, data will be crap"); diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.h b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.h index 0d8bc995d3..d679e66d49 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.h +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.h @@ -30,6 +30,9 @@ public: void LoadWAD(const std::string& _rContentFile); + // Internal implementation of the ES_DECRYPT ioctlv. + void DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output); + void OpenInternal(); void DoState(PointerWrap& p) override; From 2ed352698fe1b5d84ca2e663de520a429f8136a5 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Sun, 1 Jan 2017 22:09:57 +0100 Subject: [PATCH 2/8] IOS/ES: Implement ES_AddTicket. Refactor the existing DiscIO::AddTicket to not require the caller to pass the requested title ID. We do not have the title ID in the ES case, and it needs to be extracted from the ticket. Since this is always a safe operation to do (title ID is always in the ticket), the implementation is made default. --- .../Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp | 12 +++++++ Source/Core/DiscIO/NANDContentLoader.cpp | 31 +++++++++++++++++-- Source/Core/DiscIO/NANDContentLoader.h | 2 +- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp index 667945bbfa..2d228d7982 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp @@ -264,6 +264,18 @@ IPCCommandResult CWII_IPC_HLE_Device_es::IOCtlV(u32 _CommandAddress) switch (Buffer.Parameter) { + case IOCTL_ES_ADDTICKET: + { + _dbg_assert_msg_(WII_IPC_ES, Buffer.NumberInBuffer == 3, + "IOCTL_ES_ADDTICKET wrong number of inputs"); + + INFO_LOG(WII_IPC_ES, "IOCTL_ES_ADDTICKET"); + std::vector ticket(Buffer.InBuffer[0].m_Size); + Memory::CopyFromEmu(ticket.data(), Buffer.InBuffer[0].m_Address, Buffer.InBuffer[0].m_Size); + DiscIO::AddTicket(ticket); + break; + } + case IOCTL_ES_GETDEVICEID: { _dbg_assert_msg_(WII_IPC_ES, Buffer.NumberPayloadBuffer == 1, diff --git a/Source/Core/DiscIO/NANDContentLoader.cpp b/Source/Core/DiscIO/NANDContentLoader.cpp index 2c5e0e87e7..b3e0ab741f 100644 --- a/Source/Core/DiscIO/NANDContentLoader.cpp +++ b/Source/Core/DiscIO/NANDContentLoader.cpp @@ -512,7 +512,7 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename) } // Extract and copy WAD's ticket to ticket directory - if (!AddTicket(title_id, content_loader.GetTicket())) + if (!AddTicket(content_loader.GetTicket())) { PanicAlertT("WAD installation failed: error creating ticket"); return 0; @@ -525,8 +525,35 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename) return title_id; } -bool AddTicket(u64 title_id, const std::vector& ticket) +bool AddTicket(const std::vector& ticket) { + // Find the "entry point" of the ticket by skipping the appropriate number of + // bytes, depending on the signature type. We need to parse some of the + // ticket because in some cases (ES_AddTicket) it is the only thing that + // indicated to us what title id the ticket is for. + u32 signature_type = Common::FromBigEndian(*reinterpret_cast(ticket.data())); + u32 entry_offset; + if (signature_type == 0x10000) // RSA4096 + { + entry_offset = 576; + } + else if (signature_type == 0x10001) // RSA2048 + { + entry_offset = 320; + } + else if (signature_type == 0x10002) // ECDSA + { + entry_offset = 128; + } + else + { + ERROR_LOG(DISCIO, "Invalid ticket signature type: %08x", signature_type); + return false; + } + + const u8* ticket_data = ticket.data() + entry_offset; + u64 title_id = Common::FromBigEndian(*reinterpret_cast(ticket_data + 0x9c)); + std::string ticket_filename = Common::GetTicketFileName(title_id, Common::FROM_CONFIGURED_ROOT); File::CreateFullPath(ticket_filename); diff --git a/Source/Core/DiscIO/NANDContentLoader.h b/Source/Core/DiscIO/NANDContentLoader.h index f8d3bb6711..589e75ee4f 100644 --- a/Source/Core/DiscIO/NANDContentLoader.h +++ b/Source/Core/DiscIO/NANDContentLoader.h @@ -22,7 +22,7 @@ namespace DiscIO { enum class Region; -bool AddTicket(u64 title_id, const std::vector& ticket); +bool AddTicket(const std::vector& ticket); class CNANDContentData { From 650a1fdb1f750c631d5b5b700679b13aedb4d432 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Mon, 2 Jan 2017 00:59:21 +0100 Subject: [PATCH 3/8] DiscIO: Implement functions to lookup tickets These two functions load either a signed ticket or a raw ticket from the emulated NAND. The ticket signature skip is refactored out of the ticket writing in order to be usable by the raw ticket reading function. --- Source/Core/DiscIO/NANDContentLoader.cpp | 92 +++++++++++++++++------- Source/Core/DiscIO/NANDContentLoader.h | 4 +- 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/Source/Core/DiscIO/NANDContentLoader.cpp b/Source/Core/DiscIO/NANDContentLoader.cpp index b3e0ab741f..6089c735b3 100644 --- a/Source/Core/DiscIO/NANDContentLoader.cpp +++ b/Source/Core/DiscIO/NANDContentLoader.cpp @@ -29,6 +29,39 @@ namespace DiscIO { +namespace +{ +// Strips the signature part of a ticket, which has variable size based on +// signature type. Returns a new vector which has only the ticket structure +// itself. +std::vector SignedTicketToTicket(const std::vector& signed_ticket) +{ + u32 signature_type = Common::swap32(signed_ticket.data()); + u32 entry_offset; + if (signature_type == 0x10000) // RSA4096 + { + entry_offset = 576; + } + else if (signature_type == 0x10001) // RSA2048 + { + entry_offset = 320; + } + else if (signature_type == 0x10002) // ECDSA + { + entry_offset = 128; + } + else + { + ERROR_LOG(DISCIO, "Invalid ticket signature type: %08x", signature_type); + return std::vector(); + } + + std::vector ticket(signed_ticket.size() - entry_offset); + std::copy(signed_ticket.begin() + entry_offset, signed_ticket.end(), ticket.begin()); + return ticket; +} +} + CNANDContentData::~CNANDContentData() = default; CSharedContent::CSharedContent() @@ -525,34 +558,14 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename) return title_id; } -bool AddTicket(const std::vector& ticket) +bool AddTicket(const std::vector& signed_ticket) { - // Find the "entry point" of the ticket by skipping the appropriate number of - // bytes, depending on the signature type. We need to parse some of the - // ticket because in some cases (ES_AddTicket) it is the only thing that - // indicated to us what title id the ticket is for. - u32 signature_type = Common::FromBigEndian(*reinterpret_cast(ticket.data())); - u32 entry_offset; - if (signature_type == 0x10000) // RSA4096 + std::vector ticket = SignedTicketToTicket(signed_ticket); + if (ticket.empty()) { - entry_offset = 576; - } - else if (signature_type == 0x10001) // RSA2048 - { - entry_offset = 320; - } - else if (signature_type == 0x10002) // ECDSA - { - entry_offset = 128; - } - else - { - ERROR_LOG(DISCIO, "Invalid ticket signature type: %08x", signature_type); return false; } - - const u8* ticket_data = ticket.data() + entry_offset; - u64 title_id = Common::FromBigEndian(*reinterpret_cast(ticket_data + 0x9c)); + u64 title_id = Common::swap64(ticket.data() + 0x9c); std::string ticket_filename = Common::GetTicketFileName(title_id, Common::FROM_CONFIGURED_ROOT); File::CreateFullPath(ticket_filename); @@ -561,7 +574,36 @@ bool AddTicket(const std::vector& ticket) if (!ticket_file) return false; - return ticket_file.WriteBytes(ticket.data(), ticket.size()); + return ticket_file.WriteBytes(signed_ticket.data(), signed_ticket.size()); +} + +std::vector FindSignedTicket(u64 title_id) +{ + std::string ticket_filename = Common::GetTicketFileName(title_id, Common::FROM_CONFIGURED_ROOT); + File::IOFile ticket_file(ticket_filename, "rb"); + if (!ticket_file) + { + return std::vector(); + } + + std::vector signed_ticket(ticket_file.GetSize()); + if (!ticket_file.ReadBytes(signed_ticket.data(), signed_ticket.size())) + { + return std::vector(); + } + + return signed_ticket; +} + +std::vector FindTicket(u64 title_id) +{ + std::vector signed_ticket = FindSignedTicket(title_id); + if (signed_ticket.empty()) + { + return std::vector(); + } + + return SignedTicketToTicket(signed_ticket); } } // namespace end diff --git a/Source/Core/DiscIO/NANDContentLoader.h b/Source/Core/DiscIO/NANDContentLoader.h index 589e75ee4f..ebf42d919a 100644 --- a/Source/Core/DiscIO/NANDContentLoader.h +++ b/Source/Core/DiscIO/NANDContentLoader.h @@ -22,7 +22,9 @@ namespace DiscIO { enum class Region; -bool AddTicket(const std::vector& ticket); +bool AddTicket(const std::vector& signed_ticket); +std::vector FindSignedTicket(u64 title_id); +std::vector FindTicket(u64 title_id); class CNANDContentData { From 334ddf754eac2e92e35e82eff6572b6a73a8a061 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Mon, 2 Jan 2017 05:32:08 +0100 Subject: [PATCH 4/8] DiscIO: Export GetKeyFromTicket This function has more uses than just in DiscIO (e.g. WFS). --- Source/Core/DiscIO/NANDContentLoader.cpp | 42 ++++++++++++------------ Source/Core/DiscIO/NANDContentLoader.h | 4 +-- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/Source/Core/DiscIO/NANDContentLoader.cpp b/Source/Core/DiscIO/NANDContentLoader.cpp index 6089c735b3..f87e0e31c0 100644 --- a/Source/Core/DiscIO/NANDContentLoader.cpp +++ b/Source/Core/DiscIO/NANDContentLoader.cpp @@ -60,6 +60,17 @@ std::vector SignedTicketToTicket(const std::vector& signed_ticket) std::copy(signed_ticket.begin() + entry_offset, signed_ticket.end(), ticket.begin()); return ticket; } + +std::vector AESDecode(const u8* key, u8* iv, const u8* src, u32 size) +{ + mbedtls_aes_context aes_ctx; + std::vector buffer(size); + + mbedtls_aes_setkey_dec(&aes_ctx, key, 128); + mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, size, iv, src, buffer.data()); + + return buffer; +} } CNANDContentData::~CNANDContentData() = default; @@ -323,27 +334,6 @@ void CNANDContentLoader::InitializeContentEntries(const std::vector& tmd, } } -std::vector CNANDContentLoader::AESDecode(const u8* key, u8* iv, const u8* src, u32 size) -{ - mbedtls_aes_context aes_ctx; - std::vector buffer(size); - - mbedtls_aes_setkey_dec(&aes_ctx, key, 128); - mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, size, iv, src, buffer.data()); - - return buffer; -} - -std::vector CNANDContentLoader::GetKeyFromTicket(const std::vector& ticket) -{ - const u8 common_key[16] = {0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4, - 0x48, 0xd9, 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7}; - u8 iv[16] = {}; - - std::copy(&ticket[0x01DC], &ticket[0x01DC + 8], iv); - return AESDecode(common_key, iv, &ticket[0x01BF], 16); -} - DiscIO::Region CNANDContentLoader::GetRegion() const { if (!IsValid()) @@ -606,4 +596,14 @@ std::vector FindTicket(u64 title_id) return SignedTicketToTicket(signed_ticket); } +std::vector GetKeyFromTicket(const std::vector& signed_ticket) +{ + const u8 common_key[16] = {0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4, + 0x48, 0xd9, 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7}; + u8 iv[16] = {}; + + std::copy(&signed_ticket[0x01DC], &signed_ticket[0x01DC + 8], iv); + return AESDecode(common_key, iv, &signed_ticket[0x01BF], 16); +} + } // namespace end diff --git a/Source/Core/DiscIO/NANDContentLoader.h b/Source/Core/DiscIO/NANDContentLoader.h index ebf42d919a..193bee596f 100644 --- a/Source/Core/DiscIO/NANDContentLoader.h +++ b/Source/Core/DiscIO/NANDContentLoader.h @@ -25,6 +25,7 @@ enum class Region; bool AddTicket(const std::vector& signed_ticket); std::vector FindSignedTicket(u64 title_id); std::vector FindTicket(u64 title_id); +std::vector GetKeyFromTicket(const std::vector& ticket); class CNANDContentData { @@ -112,9 +113,6 @@ private: const std::vector& decrypted_title_key, const std::vector& data_app); - static std::vector AESDecode(const u8* key, u8* iv, const u8* src, u32 size); - static std::vector GetKeyFromTicket(const std::vector& ticket); - bool m_Valid; bool m_IsWAD; std::string m_Path; From 0a5cfd8946f34f2b7f9451539c865d81f12f28c4 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Sat, 14 Jan 2017 14:26:44 +0100 Subject: [PATCH 5/8] IOS: Add ESFormats.{cpp,h}. This library implements basic parsing support for some of the IOS ES formats we need to extract data from. Currently only implements TMD functions, but some ticket handling functions from DiscIO should likely be moved here in the future. --- Source/Core/Core/CMakeLists.txt | 1 + Source/Core/Core/Core.vcxproj | 2 + Source/Core/Core/Core.vcxproj.filters | 6 ++ Source/Core/Core/IPC_HLE/ESFormats.cpp | 96 ++++++++++++++++++++++++++ Source/Core/Core/IPC_HLE/ESFormats.h | 46 ++++++++++++ 5 files changed, 151 insertions(+) create mode 100644 Source/Core/Core/IPC_HLE/ESFormats.cpp create mode 100644 Source/Core/Core/IPC_HLE/ESFormats.h diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index a5825e15e7..9b6934cd89 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -135,6 +135,7 @@ set(SRCS ActionReplay.cpp HW/WiimoteEmu/Speaker.cpp HW/WiimoteReal/WiimoteReal.cpp HW/WiiSaveCrypted.cpp + IPC_HLE/ESFormats.cpp IPC_HLE/ICMPLin.cpp IPC_HLE/NWC24Config.cpp IPC_HLE/WII_IPC_HLE.cpp diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index d18d130422..7f4b1b3208 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -167,6 +167,7 @@ + @@ -392,6 +393,7 @@ + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index 4b33dd2e2e..37653a0520 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -565,6 +565,9 @@ DSPCore + + IPC HLE %28IOS/Starlet%29\ES + IPC HLE %28IOS/Starlet%29 @@ -1162,6 +1165,9 @@ DSPCore + + IPC HLE %28IOS/Starlet%29\ES + IPC HLE %28IOS/Starlet%29 diff --git a/Source/Core/Core/IPC_HLE/ESFormats.cpp b/Source/Core/Core/IPC_HLE/ESFormats.cpp new file mode 100644 index 0000000000..7cd56c14c4 --- /dev/null +++ b/Source/Core/Core/IPC_HLE/ESFormats.cpp @@ -0,0 +1,96 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/IPC_HLE/ESFormats.h" + +#include +#include +#include + +#include "Common/ChunkFile.h" +#include "Common/CommonFuncs.h" +#include "Common/CommonTypes.h" + +TMDReader::TMDReader(const std::vector& bytes) : m_bytes(bytes) +{ +} + +TMDReader::TMDReader(std::vector&& bytes) : m_bytes(std::move(bytes)) +{ +} + +void TMDReader::SetBytes(const std::vector& bytes) +{ + m_bytes = bytes; +} + +void TMDReader::SetBytes(std::vector&& bytes) +{ + m_bytes = std::move(bytes); +} + +bool TMDReader::IsValid() const +{ + if (m_bytes.size() < 0x1E4) + { + // TMD is too small to contain its base fields. + return false; + } + + if (m_bytes.size() < 0x1E4 + GetNumContents() * 36u) + { + // TMD is too small to contain all its expected content entries. + return false; + } + + return true; +} + +u64 TMDReader::GetTitleId() const +{ + return Common::swap64(m_bytes.data() + 0x18C); +} + +u16 TMDReader::GetNumContents() const +{ + return Common::swap16(m_bytes.data() + 0x1DE); +} + +bool TMDReader::GetContent(u16 index, Content* content) const +{ + if (index >= GetNumContents()) + { + return false; + } + + const u8* content_base = m_bytes.data() + 0x1E4 + index * 36; + content->id = Common::swap32(content_base); + content->index = Common::swap16(content_base + 4); + content->type = Common::swap16(content_base + 6); + content->size = Common::swap64(content_base + 8); + std::copy(content_base + 16, content_base + 36, content->sha1.begin()); + + return true; +} + +bool TMDReader::FindContentById(u32 id, Content* content) const +{ + for (u16 index = 0; index < GetNumContents(); ++index) + { + if (!GetContent(index, content)) + { + return false; + } + if (content->id == id) + { + return true; + } + } + return false; +} + +void TMDReader::DoState(PointerWrap& p) +{ + p.Do(m_bytes); +} diff --git a/Source/Core/Core/IPC_HLE/ESFormats.h b/Source/Core/Core/IPC_HLE/ESFormats.h new file mode 100644 index 0000000000..74f023244b --- /dev/null +++ b/Source/Core/Core/IPC_HLE/ESFormats.h @@ -0,0 +1,46 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +// Utilities to manipulate files and formats from the Wii's ES module: tickets, +// TMD, and other title informations. + +#pragma once + +#include +#include + +#include "Common/ChunkFile.h" +#include "Common/CommonTypes.h" + +class TMDReader final +{ +public: + TMDReader() = default; + explicit TMDReader(const std::vector& bytes); + explicit TMDReader(std::vector&& bytes); + + void SetBytes(const std::vector& bytes); + void SetBytes(std::vector&& bytes); + + bool IsValid() const; + + u64 GetTitleId() const; + + struct Content + { + u32 id; + u16 index; + u16 type; + u64 size; + std::array sha1; + }; + u16 GetNumContents() const; + bool GetContent(u16 index, Content* content) const; + bool FindContentById(u32 id, Content* content) const; + + void DoState(PointerWrap& p); + +private: + std::vector m_bytes; +}; From a8f7012cf7b332807d9881be01a9332a76723e74 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Sun, 8 Jan 2017 18:55:37 +0100 Subject: [PATCH 6/8] IOS/ES: Implement basic title installation. There are several things wrong with this implementation. The first being that since we still don't have a proper ticket/tmd handling library, we hardcode offsets once again to fetch TMD fields. The second being that we don't stream data to the disk and we buffer everything in memory. The third being that we don't properly fetch the content index for decryption, which is prone to breaking. But hey, it works well enough to install the DQX channel! --- .../Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp | 139 ++++++++++++++++++ .../Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.h | 6 + 2 files changed, 145 insertions(+) diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp index 2d228d7982..dd20bbdb7d 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp @@ -53,6 +53,7 @@ #include "Core/HW/DVDInterface.h" #include "Core/HW/Memmap.h" #include "Core/HW/Wiimote.h" +#include "Core/IPC_HLE/ESFormats.h" #include "Core/IPC_HLE/WII_IPC_HLE_Device_es.h" #include "Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_emu.h" #include "Core/IPC_HLE/WII_IPC_HLE_WiiMote.h" @@ -151,6 +152,10 @@ void CWII_IPC_HLE_Device_es::DoState(PointerWrap& p) p.Do(m_AccessIdentID); p.Do(m_TitleIDs); + m_addtitle_tmd.DoState(p); + p.Do(m_addtitle_content_id); + p.Do(m_addtitle_content_buffer); + u32 Count = (u32)(m_ContentAccessMap.size()); p.Do(Count); @@ -276,6 +281,140 @@ IPCCommandResult CWII_IPC_HLE_Device_es::IOCtlV(u32 _CommandAddress) break; } + case IOCTL_ES_ADDTITLESTART: + { + _dbg_assert_msg_(WII_IPC_ES, Buffer.NumberInBuffer == 4, + "IOCTL_ES_ADDTITLESTART wrong number of inputs"); + + INFO_LOG(WII_IPC_ES, "IOCTL_ES_ADDTITLESTART"); + std::vector tmd(Buffer.InBuffer[0].m_Size); + Memory::CopyFromEmu(tmd.data(), Buffer.InBuffer[0].m_Address, Buffer.InBuffer[0].m_Size); + + m_addtitle_tmd.SetBytes(tmd); + if (!m_addtitle_tmd.IsValid()) + { + ERROR_LOG(WII_IPC_ES, "Invalid TMD while adding title (size = %zd)", tmd.size()); + Memory::Write_U32(ES_INVALID_TMD, _CommandAddress + 0x4); + return GetDefaultReply(); + } + + // Write the TMD to title storage. + std::string tmd_path = + Common::GetTMDFileName(m_addtitle_tmd.GetTitleId(), Common::FROM_SESSION_ROOT); + File::CreateFullPath(tmd_path); + + File::IOFile fp(tmd_path, "wb"); + fp.WriteBytes(tmd.data(), tmd.size()); + break; + } + + case IOCTL_ES_ADDCONTENTSTART: + { + _dbg_assert_msg_(WII_IPC_ES, Buffer.NumberInBuffer == 2, + "IOCTL_ES_ADDCONTENTSTART wrong number of inputs"); + + u64 title_id = Memory::Read_U64(Buffer.InBuffer[0].m_Address); + u32 content_id = Memory::Read_U32(Buffer.InBuffer[1].m_Address); + + if (m_addtitle_content_id != 0xFFFFFFFF) + { + ERROR_LOG(WII_IPC_ES, "Trying to add content when we haven't finished adding " + "another content. Unsupported."); + Memory::Write_U32(ES_WRITE_FAILURE, _CommandAddress + 0x4); + return GetDefaultReply(); + } + m_addtitle_content_id = content_id; + + m_addtitle_content_buffer.clear(); + + INFO_LOG(WII_IPC_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 ", " + "content id %08x", + title_id, m_addtitle_content_id); + + if (title_id != m_addtitle_tmd.GetTitleId()) + { + ERROR_LOG(WII_IPC_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 " != " + "TMD title id %016lx, ignoring", + title_id, m_addtitle_tmd.GetTitleId()); + } + + // We're supposed to return a "content file descriptor" here, which is + // passed to further AddContentData / AddContentFinish. But so far there is + // no known content installer which performs content addition concurrently. + // Instead we just log an error (see above) if this condition is detected. + s32 content_fd = 0; + Memory::Write_U32(content_fd, _CommandAddress + 0x4); + return GetDefaultReply(); + } + + case IOCTL_ES_ADDCONTENTDATA: + { + _dbg_assert_msg_(WII_IPC_ES, Buffer.NumberInBuffer == 2, + "IOCTL_ES_ADDCONTENTDATA wrong number of inputs"); + + u32 content_fd = Memory::Read_U32(Buffer.InBuffer[0].m_Address); + INFO_LOG(WII_IPC_ES, "IOCTL_ES_ADDCONTENTDATA: content fd %08x, " + "size %d", + content_fd, Buffer.InBuffer[1].m_Size); + + u8* data_start = Memory::GetPointer(Buffer.InBuffer[1].m_Address); + u8* data_end = data_start + Buffer.InBuffer[1].m_Size; + m_addtitle_content_buffer.insert(m_addtitle_content_buffer.end(), data_start, data_end); + break; + } + + case IOCTL_ES_ADDCONTENTFINISH: + { + _dbg_assert_msg_(WII_IPC_ES, Buffer.NumberInBuffer == 1, + "IOCTL_ES_ADDCONTENTFINISH wrong number of inputs"); + + u32 content_fd = Memory::Read_U32(Buffer.InBuffer[0].m_Address); + INFO_LOG(WII_IPC_ES, "IOCTL_ES_ADDCONTENTFINISH: content fd %08x", content_fd); + + // Try to find the title key from a pre-installed ticket. + std::vector ticket = DiscIO::FindSignedTicket(m_addtitle_tmd.GetTitleId()); + if (ticket.size() == 0) + { + Memory::Write_U32(ES_NO_TICKET_INSTALLED, _CommandAddress + 0x4); + return GetDefaultReply(); + } + + mbedtls_aes_context aes_ctx; + mbedtls_aes_setkey_dec(&aes_ctx, DiscIO::GetKeyFromTicket(ticket).data(), 128); + + // The IV for title content decryption is the lower two bytes of the + // content index, zero extended. + TMDReader::Content content_info; + if (!m_addtitle_tmd.FindContentById(m_addtitle_content_id, &content_info)) + { + Memory::Write_U32(ES_INVALID_TMD, _CommandAddress + 0x4); + return GetDefaultReply(); + } + u8 iv[16] = {0}; + iv[0] = (content_info.index >> 8) & 0xFF; + iv[1] = content_info.index & 0xFF; + std::vector decrypted_data(m_addtitle_content_buffer.size()); + mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, m_addtitle_content_buffer.size(), iv, + m_addtitle_content_buffer.data(), decrypted_data.data()); + + std::string path = StringFromFormat( + "%s%08x.app", + Common::GetTitleContentPath(m_addtitle_tmd.GetTitleId(), Common::FROM_SESSION_ROOT).c_str(), + m_addtitle_content_id); + + File::IOFile fp(path, "wb"); + fp.WriteBytes(decrypted_data.data(), decrypted_data.size()); + + m_addtitle_content_id = 0xFFFFFFFF; + break; + } + + case IOCTL_ES_ADDTITLEFINISH: + { + INFO_LOG(WII_IPC_ES, "IOCTL_ES_ADDTITLEFINISH"); + break; + } + case IOCTL_ES_GETDEVICEID: { _dbg_assert_msg_(WII_IPC_ES, Buffer.NumberPayloadBuffer == 1, diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.h b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.h index d679e66d49..9ca52d821e 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.h +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.h @@ -10,6 +10,7 @@ #include #include "Common/CommonTypes.h" +#include "Core/IPC_HLE/ESFormats.h" #include "Core/IPC_HLE/WII_IPC_HLE.h" #include "Core/IPC_HLE/WII_IPC_HLE_Device.h" @@ -143,6 +144,11 @@ private: static u8* keyTable[11]; + // For title installation (ioctls IOCTL_ES_ADDTITLE*). + TMDReader m_addtitle_tmd; + u32 m_addtitle_content_id = 0xFFFFFFFF; + std::vector m_addtitle_content_buffer; + const DiscIO::CNANDContentLoader& AccessContentDevice(u64 title_id); u32 OpenTitleContent(u32 CFD, u64 TitleID, u16 Index); From 45d27f7fc7f5b63dd5caaac65eac8d645849d0f6 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 3 Jan 2017 03:31:45 +0100 Subject: [PATCH 7/8] CommonPaths: Add D_WFSROOT_IDX. Defaults to $USERDIR/WFS. Used to store the contents normally stored on WFS mass storage devices. --- Source/Core/Common/CommonPaths.h | 1 + Source/Core/Common/FileUtil.cpp | 1 + Source/Core/Common/FileUtil.h | 1 + 3 files changed, 3 insertions(+) diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index f269968405..046e3bfd90 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -75,6 +75,7 @@ #define ANAGLYPH_DIR "Anaglyph" #define PIPES_DIR "Pipes" #define MEMORYWATCHER_DIR "MemoryWatcher" +#define WFSROOT_DIR "WFS" // This one is only used to remove it if it was present #define SHADERCACHE_LEGACY_DIR "ShaderCache" diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 59c7412072..652a2e466c 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -780,6 +780,7 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[D_MAILLOGS_IDX] = s_user_paths[D_LOGS_IDX] + MAIL_LOGS_DIR DIR_SEP; s_user_paths[D_THEMES_IDX] = s_user_paths[D_USER_IDX] + THEMES_DIR DIR_SEP; s_user_paths[D_PIPES_IDX] = s_user_paths[D_USER_IDX] + PIPES_DIR DIR_SEP; + s_user_paths[D_WFSROOT_IDX] = s_user_paths[D_USER_IDX] + WFSROOT_DIR DIR_SEP; s_user_paths[F_DOLPHINCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DOLPHIN_CONFIG; s_user_paths[F_DEBUGGERCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DEBUGGER_CONFIG; s_user_paths[F_LOGGERCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + LOGGER_CONFIG; diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index df4d6aacd6..bcd28fd968 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -46,6 +46,7 @@ enum D_THEMES_IDX, D_PIPES_IDX, D_MEMORYWATCHER_IDX, + D_WFSROOT_IDX, F_DOLPHINCONFIG_IDX, F_DEBUGGERCONFIG_IDX, F_LOGGERCONFIG_IDX, From 45a123292075eed895a5e6669736478cfc5d35a7 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Sun, 8 Jan 2017 18:59:21 +0100 Subject: [PATCH 8/8] IOS: Add partial wfsi/wfssrv implementations. The current implementations do many things wrong but work well enough to run the Dragon Quest X installer until the very end. The game itself crashes when being launched from its System Menu channel unfortunately so it is hard to verify whether the install properly worked or not. There are plenty of "TODO(wfs)" sprinkled around this codebase with things that are knowingly done wrong. The most important one right now is that content extraction is done by buffering everything into memory instead of properly streaming the data to disk (and processing asynchronously), which causes freezes. It is likely to not cause any practical issues since only the installer and the updater should use this anyway. --- Source/Core/Core/CMakeLists.txt | 2 + Source/Core/Core/Core.vcxproj | 4 + Source/Core/Core/Core.vcxproj.filters | 15 + Source/Core/Core/IPC_HLE/WII_IPC_HLE.cpp | 4 + .../IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.cpp | 243 ++++++++++++++++ .../IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.h | 76 +++++ .../Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.cpp | 263 ++++++++++++++++++ .../Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.h | 73 +++++ 8 files changed, 680 insertions(+) create mode 100644 Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.cpp create mode 100644 Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.h create mode 100644 Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.cpp create mode 100644 Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.h diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 9b6934cd89..7b576f9577 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -155,6 +155,8 @@ set(SRCS ActionReplay.cpp IPC_HLE/WII_IPC_HLE_Device_usb_bt_stub.cpp IPC_HLE/WII_IPC_HLE_Device_usb_kbd.cpp IPC_HLE/WII_IPC_HLE_Device_usb_ven.cpp + IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.cpp + IPC_HLE/WII_IPC_HLE_Device_wfsi.cpp IPC_HLE/WII_IPC_HLE_WiiMote.cpp IPC_HLE/WiiMote_HID_Attr.cpp IPC_HLE/WiiNetConfig.cpp diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index 7f4b1b3208..b9f950a2eb 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -197,6 +197,8 @@ + + @@ -417,6 +419,8 @@ + + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index 37653a0520..555cf460a6 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -133,6 +133,9 @@ {4a090016-76d5-43dd-95a4-abedfc11ef31} + + {f11746cf-277a-4d58-bcf1-578a45348b07} + {8352be4d-d37d-4f55-adec-b940a9712802} @@ -631,6 +634,12 @@ IPC HLE %28IOS/Starlet%29\USB/BT/Wiimote + + IPC HLE %28IOS/Starlet%29\WFS + + + IPC HLE %28IOS/Starlet%29\WFS + IPC HLE %28IOS/Starlet%29\USB/BT/Wiimote @@ -1243,6 +1252,12 @@ IPC HLE %28IOS/Starlet%29\USB/BT/Wiimote + + IPC HLE %28IOS/Starlet%29\WFS + + + IPC HLE %28IOS/Starlet%29\WFS + HW %28Flipper/Hollywood%29\Wiimote diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE.cpp b/Source/Core/Core/IPC_HLE/WII_IPC_HLE.cpp index 1167294875..fdca1f408c 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE.cpp +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE.cpp @@ -48,6 +48,8 @@ #include "Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_real.h" #include "Core/IPC_HLE/WII_IPC_HLE_Device_usb_kbd.h" #include "Core/IPC_HLE/WII_IPC_HLE_Device_usb_ven.h" +#include "Core/IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.h" +#include "Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.h" namespace CoreTiming { @@ -157,6 +159,8 @@ void Reinit() AddDevice("/dev/usb/hid"); #endif AddDevice("/dev/usb/oh1"); + AddDevice("/dev/usb/wfssrv"); + AddDevice("/dev/wfsi"); } void Init() diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.cpp b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.cpp new file mode 100644 index 0000000000..4fabc7200f --- /dev/null +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.cpp @@ -0,0 +1,243 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.h" + +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/NandPaths.h" +#include "Core/HW/DVDInterface.h" +#include "Core/HW/Memmap.h" + +namespace WFS +{ +std::string NativePath(const std::string& wfs_path) +{ + return File::GetUserPath(D_WFSROOT_IDX) + Common::EscapePath(wfs_path); +} +} + +CWII_IPC_HLE_Device_usb_wfssrv::CWII_IPC_HLE_Device_usb_wfssrv(u32 device_id, + const std::string& device_name) + : IWII_IPC_HLE_Device(device_id, device_name) +{ + m_device_name = "msc01"; +} + +IPCCommandResult CWII_IPC_HLE_Device_usb_wfssrv::IOCtl(u32 command_address) +{ + u32 command = Memory::Read_U32(command_address + 0xC); + u32 buffer_in = Memory::Read_U32(command_address + 0x10); + u32 buffer_in_size = Memory::Read_U32(command_address + 0x14); + u32 buffer_out = Memory::Read_U32(command_address + 0x18); + u32 buffer_out_size = Memory::Read_U32(command_address + 0x1C); + + int return_error_code = IPC_SUCCESS; + + switch (command) + { + case IOCTL_WFS_INIT: + // TODO(wfs): Implement. + INFO_LOG(WII_IPC_HLE, "IOCTL_WFS_INIT"); + break; + + case IOCTL_WFS_DEVICE_INFO: + INFO_LOG(WII_IPC_HLE, "IOCTL_WFS_DEVICE_INFO"); + Memory::Write_U64(16ull << 30, buffer_out); // 16GB storage. + Memory::Write_U8(4, buffer_out + 8); + break; + + case IOCTL_WFS_GET_DEVICE_NAME: + { + INFO_LOG(WII_IPC_HLE, "IOCTL_WFS_GET_DEVICE_NAME"); + Memory::Write_U8(static_cast(m_device_name.size()), buffer_out); + Memory::CopyToEmu(buffer_out + 1, m_device_name.data(), m_device_name.size()); + break; + } + + case IOCTL_WFS_ATTACH_DETACH_2: + // TODO(wfs): Implement. + INFO_LOG(WII_IPC_HLE, "IOCTL_WFS_ATTACH_DETACH_2(%d)", command); + Memory::Write_U32(1, buffer_out); + Memory::Write_U32(0, buffer_out + 4); // device id? + Memory::Write_U32(0, buffer_out + 8); + break; + + case IOCTL_WFS_ATTACH_DETACH: + INFO_LOG(WII_IPC_HLE, "IOCTL_WFS_ATTACH_DETACH(%d)", command); + Memory::Write_U32(1, buffer_out); + Memory::Write_U32(0, buffer_out + 4); + Memory::Write_U32(0, buffer_out + 8); + return GetNoReply(); + + // TODO(wfs): Globbing is not really implemented, we just fake the one case + // (listing /vol/*) which is required to get the installer to work. + case IOCTL_WFS_GLOB_START: + INFO_LOG(WII_IPC_HLE, "IOCTL_WFS_GLOB_START(%d)", command); + Memory::Memset(buffer_out, 0, buffer_out_size); + memcpy(Memory::GetPointer(buffer_out + 0x14), m_device_name.data(), m_device_name.size()); + break; + + case IOCTL_WFS_GLOB_NEXT: + INFO_LOG(WII_IPC_HLE, "IOCTL_WFS_GLOB_NEXT(%d)", command); + return_error_code = WFS_EEMPTY; + break; + + case IOCTL_WFS_GLOB_END: + INFO_LOG(WII_IPC_HLE, "IOCTL_WFS_GLOB_END(%d)", command); + Memory::Memset(buffer_out, 0, buffer_out_size); + break; + + case IOCTL_WFS_OPEN: + { + u32 mode = Memory::Read_U32(buffer_in); + u16 path_len = Memory::Read_U16(buffer_in + 0x20); + std::string path = Memory::GetString(buffer_in + 0x22, path_len); + + u16 fd = GetNewFileDescriptor(); + FileDescriptor* fd_obj = &m_fds[fd]; + fd_obj->in_use = true; + fd_obj->path = path; + fd_obj->mode = mode; + fd_obj->position = 0; + + if (!fd_obj->Open()) + { + ERROR_LOG(WII_IPC_HLE, "IOCTL_WFS_OPEN(%s, %d): error opening file", path.c_str(), mode); + ReleaseFileDescriptor(fd); + return_error_code = -1; // TODO(wfs): proper error code. + break; + } + + INFO_LOG(WII_IPC_HLE, "IOCTL_WFS_OPEN(%s, %d) -> %d", path.c_str(), mode, fd); + Memory::Write_U16(fd, buffer_out + 0x14); + break; + } + + case IOCTL_WFS_CLOSE: + { + u16 fd = Memory::Read_U16(buffer_in + 0x4); + INFO_LOG(WII_IPC_HLE, "IOCTL_WFS_CLOSE(%d)", fd); + ReleaseFileDescriptor(fd); + break; + } + + case IOCTL_WFS_READ: + { + u32 addr = Memory::Read_U32(buffer_in); + u16 fd = Memory::Read_U16(buffer_in + 0xC); + u32 size = Memory::Read_U32(buffer_in + 8); + + FileDescriptor* fd_obj = FindFileDescriptor(fd); + if (fd_obj == nullptr) + { + ERROR_LOG(WII_IPC_HLE, "IOCTL_WFS_READ: invalid file descriptor %d", fd); + return_error_code = -1; // TODO(wfs): proper error code. + break; + } + + size_t read_bytes; + if (!fd_obj->file.ReadArray(Memory::GetPointer(addr), size, &read_bytes)) + { + return_error_code = -1; // TODO(wfs): proper error code. + break; + } + fd_obj->position += read_bytes; + + INFO_LOG(WII_IPC_HLE, "IOCTL_WFS_READ: read %zd bytes from FD %d (%s)", read_bytes, fd, + fd_obj->path.c_str()); + return_error_code = static_cast(read_bytes); + break; + } + + default: + // TODO(wfs): Should be returning -3. However until we have everything + // properly stubbed it's easier to simulate the methods succeeding. + WARN_LOG(WII_IPC_HLE, "%s unimplemented IOCtl(0x%08x, size_in=%08x, size_out=%08x)\n%s\n%s", + m_name.c_str(), command, buffer_in_size, buffer_out_size, + HexDump(Memory::GetPointer(buffer_in), buffer_in_size).c_str(), + HexDump(Memory::GetPointer(buffer_out), buffer_out_size).c_str()); + Memory::Memset(buffer_out, 0, buffer_out_size); + break; + } + + Memory::Write_U32(return_error_code, command_address + 4); + return GetDefaultReply(); +} + +IPCCommandResult CWII_IPC_HLE_Device_usb_wfssrv::IOCtlV(u32 command_address) +{ + SIOCtlVBuffer command_buffer(command_address); + ERROR_LOG(WII_IPC_HLE, "IOCtlV on /dev/usb/wfssrv -- unsupported"); + Memory::Write_U32(IPC_EINVAL, command_address + 4); + return GetDefaultReply(); +} + +CWII_IPC_HLE_Device_usb_wfssrv::FileDescriptor* +CWII_IPC_HLE_Device_usb_wfssrv::FindFileDescriptor(u16 fd) +{ + if (fd >= m_fds.size() || !m_fds[fd].in_use) + { + return nullptr; + } + return &m_fds[fd]; +} + +u16 CWII_IPC_HLE_Device_usb_wfssrv::GetNewFileDescriptor() +{ + for (u32 i = 0; i < m_fds.size(); ++i) + { + if (!m_fds[i].in_use) + { + return i; + } + } + m_fds.resize(m_fds.size() + 1); + return static_cast(m_fds.size() - 1); +} + +void CWII_IPC_HLE_Device_usb_wfssrv::ReleaseFileDescriptor(u16 fd) +{ + FileDescriptor* fd_obj = FindFileDescriptor(fd); + if (!fd_obj) + { + return; + } + fd_obj->in_use = false; + + // Garbage collect and shrink the array if possible. + while (m_fds.size() > 0 && !m_fds[m_fds.size() - 1].in_use) + { + m_fds.resize(m_fds.size() - 1); + } +} + +bool CWII_IPC_HLE_Device_usb_wfssrv::FileDescriptor::Open() +{ + const char* mode_string; + + if (mode == 1) + { + mode_string = "rb"; + } + else if (mode == 2) + { + mode_string = "wb"; + } + else if (mode == 3) + { + mode_string = "rwb"; + } + else + { + ERROR_LOG(WII_IPC_HLE, "WFSOpen: invalid mode %d", mode); + return false; + } + + return file.Open(WFS::NativePath(path), mode_string); +} diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.h b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.h new file mode 100644 index 0000000000..f722bde402 --- /dev/null +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.h @@ -0,0 +1,76 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/FileUtil.h" +#include "Core/IPC_HLE/WII_IPC_HLE.h" +#include "Core/IPC_HLE/WII_IPC_HLE_Device.h" +#include "DiscIO/Volume.h" + +namespace WFS +{ +std::string NativePath(const std::string& wfs_path); +} + +class CWII_IPC_HLE_Device_usb_wfssrv : public IWII_IPC_HLE_Device +{ +public: + CWII_IPC_HLE_Device_usb_wfssrv(u32 device_id, const std::string& device_name); + + IPCCommandResult IOCtl(u32 command_address) override; + IPCCommandResult IOCtlV(u32 command_address) override; + +private: + // WFS device name, e.g. msc01/msc02. + std::string m_device_name; + + enum + { + IOCTL_WFS_INIT = 0x02, + IOCTL_WFS_DEVICE_INFO = 0x04, + IOCTL_WFS_GET_DEVICE_NAME = 0x05, + IOCTL_WFS_FLUSH = 0x0a, + IOCTL_WFS_GLOB_START = 0x0d, + IOCTL_WFS_GLOB_NEXT = 0x0e, + IOCTL_WFS_GLOB_END = 0x0f, + IOCTL_WFS_SET_HOMEDIR = 0x10, + IOCTL_WFS_CHDIR = 0x11, + IOCTL_WFS_GET_HOMEDIR = 0x12, + IOCTL_WFS_GETCWD = 0x13, + IOCTL_WFS_DELETE = 0x15, + IOCTL_WFS_GET_ATTRIBUTES = 0x17, + IOCTL_WFS_OPEN = 0x1A, + IOCTL_WFS_CLOSE = 0x1E, + IOCTL_WFS_READ = 0x20, + IOCTL_WFS_WRITE = 0x22, + IOCTL_WFS_ATTACH_DETACH = 0x2d, + IOCTL_WFS_ATTACH_DETACH_2 = 0x2e, + }; + + enum + { + WFS_EEMPTY = -10028, // Directory is empty of iteration completed. + }; + + struct FileDescriptor + { + bool in_use; + std::string path; + int mode; + size_t position; + File::IOFile file; + + bool Open(); + }; + std::vector m_fds; + + FileDescriptor* FindFileDescriptor(u16 fd); + u16 GetNewFileDescriptor(); + void ReleaseFileDescriptor(u16 fd); +}; diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.cpp b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.cpp new file mode 100644 index 0000000000..040ec001c0 --- /dev/null +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.cpp @@ -0,0 +1,263 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.h" + +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Core/HW/Memmap.h" +#include "Core/IPC_HLE/ESFormats.h" +#include "Core/IPC_HLE/WII_IPC_HLE_Device_usb_wfssrv.h" +#include "DiscIO/NANDContentLoader.h" + +void ARCUnpacker::Reset() +{ + m_whole_file.clear(); +} + +void ARCUnpacker::AddBytes(const std::vector& bytes) +{ + m_whole_file.insert(m_whole_file.end(), bytes.begin(), bytes.end()); +} + +void ARCUnpacker::Extract(const WriteCallback& callback) +{ + u32 fourcc = Common::swap32(m_whole_file.data()); + if (fourcc != 0x55AA382D) + { + ERROR_LOG(WII_IPC_HLE, "ARCUnpacker: invalid fourcc (%08x)", fourcc); + return; + } + + // Read the root node to get the number of nodes. + u8* nodes_directory = m_whole_file.data() + 0x20; + u32 nodes_count = Common::swap32(nodes_directory + 8); + constexpr u32 NODE_SIZE = 0xC; + char* string_table = reinterpret_cast(nodes_directory + nodes_count * NODE_SIZE); + + std::stack> directory_stack; + directory_stack.emplace(std::make_pair(nodes_count, "")); + for (u32 i = 1; i < nodes_count; ++i) + { + while (i >= directory_stack.top().first) + { + directory_stack.pop(); + } + const std::string& current_directory = directory_stack.top().second; + u8* node = nodes_directory + i * NODE_SIZE; + u32 name_offset = (node[1] << 16) | Common::swap16(node + 2); + u32 data_offset = Common::swap32(node + 4); + u32 size = Common::swap32(node + 8); + std::string basename = string_table + name_offset; + std::string fullname = + current_directory.empty() ? basename : current_directory + "/" + basename; + + u8 flags = *node; + if (flags == 1) + { + directory_stack.emplace(std::make_pair(size, fullname)); + } + else + { + std::vector contents(m_whole_file.data() + data_offset, + m_whole_file.data() + data_offset + size); + callback(fullname, contents); + } + } +} + +CWII_IPC_HLE_Device_wfsi::CWII_IPC_HLE_Device_wfsi(u32 device_id, const std::string& device_name) + : IWII_IPC_HLE_Device(device_id, device_name) +{ +} + +IPCCommandResult CWII_IPC_HLE_Device_wfsi::Open(u32 command_address, u32 mode) +{ + INFO_LOG(WII_IPC_HLE, "/dev/wfsi: Open"); + return IWII_IPC_HLE_Device::Open(command_address, mode); +} + +IPCCommandResult CWII_IPC_HLE_Device_wfsi::IOCtl(u32 command_address) +{ + u32 command = Memory::Read_U32(command_address + 0xC); + u32 buffer_in = Memory::Read_U32(command_address + 0x10); + u32 buffer_in_size = Memory::Read_U32(command_address + 0x14); + u32 buffer_out = Memory::Read_U32(command_address + 0x18); + u32 buffer_out_size = Memory::Read_U32(command_address + 0x1C); + + u32 return_error_code = IPC_SUCCESS; + + switch (command) + { + case IOCTL_WFSI_PREPARE_DEVICE: + { + u32 tmd_addr = Memory::Read_U32(buffer_in); + u32 tmd_size = Memory::Read_U32(buffer_in + 4); + + INFO_LOG(WII_IPC_HLE, "IOCTL_WFSI_PREPARE_DEVICE"); + + constexpr u32 MAX_TMD_SIZE = 0x4000; + if (tmd_size > MAX_TMD_SIZE) + { + ERROR_LOG(WII_IPC_HLE, "IOCTL_WFSI_INIT: TMD size too large (%d)", tmd_size); + return_error_code = IPC_EINVAL; + break; + } + std::vector tmd_bytes; + tmd_bytes.resize(tmd_size); + Memory::CopyFromEmu(tmd_bytes.data(), tmd_addr, tmd_size); + m_tmd.SetBytes(std::move(tmd_bytes)); + + std::vector ticket = DiscIO::FindSignedTicket(m_tmd.GetTitleId()); + if (ticket.size() == 0) + { + return_error_code = -11028; + break; + } + + memcpy(m_aes_key, DiscIO::GetKeyFromTicket(ticket).data(), sizeof(m_aes_key)); + mbedtls_aes_setkey_dec(&m_aes_ctx, m_aes_key, 128); + + break; + } + + case IOCTL_WFSI_PREPARE_PROFILE: + m_base_extract_path = StringFromFormat("/vol/%s/tmp/", m_device_name.c_str()); + // Fall through intended. + + case IOCTL_WFSI_PREPARE_CONTENT: + { + const char* ioctl_name = command == IOCTL_WFSI_PREPARE_PROFILE ? "IOCTL_WFSI_PREPARE_PROFILE" : + "IOCTL_WFSI_PREPARE_CONTENT"; + + // Initializes the IV from the index of the content in the TMD contents. + u32 content_id = Memory::Read_U32(buffer_in + 8); + TMDReader::Content content_info; + if (!m_tmd.FindContentById(content_id, &content_info)) + { + WARN_LOG(WII_IPC_HLE, "%s: Content id %08x not found", ioctl_name, content_id); + return_error_code = -10003; + break; + } + + memset(m_aes_iv, 0, sizeof(m_aes_iv)); + m_aes_iv[0] = content_info.index >> 8; + m_aes_iv[1] = content_info.index & 0xFF; + INFO_LOG(WII_IPC_HLE, "%s: Content id %08x found at index %d", ioctl_name, content_id, + content_info.index); + + m_arc_unpacker.Reset(); + break; + } + + case IOCTL_WFSI_IMPORT_PROFILE: + case IOCTL_WFSI_IMPORT_CONTENT: + { + const char* ioctl_name = command == IOCTL_WFSI_IMPORT_PROFILE ? "IOCTL_WFSI_IMPORT_PROFILE" : + "IOCTL_WFSI_IMPORT_CONTENT"; + + u32 content_id = Memory::Read_U32(buffer_in + 0xC); + u32 input_ptr = Memory::Read_U32(buffer_in + 0x10); + u32 input_size = Memory::Read_U32(buffer_in + 0x14); + INFO_LOG(WII_IPC_HLE, "%s: %08x bytes of data at %08x from content id %d", ioctl_name, + content_id, input_ptr, input_size); + + std::vector decrypted(input_size); + mbedtls_aes_crypt_cbc(&m_aes_ctx, MBEDTLS_AES_DECRYPT, input_size, m_aes_iv, + Memory::GetPointer(input_ptr), decrypted.data()); + + m_arc_unpacker.AddBytes(decrypted); + break; + } + + case IOCTL_WFSI_FINALIZE_PROFILE: + case IOCTL_WFSI_FINALIZE_CONTENT: + { + const char* ioctl_name = command == IOCTL_WFSI_FINALIZE_PROFILE ? + "IOCTL_WFSI_FINALIZE_PROFILE" : + "IOCTL_WFSI_FINALIZE_CONTENT"; + INFO_LOG(WII_IPC_HLE, "%s", ioctl_name); + + auto callback = [this](const std::string& filename, const std::vector& bytes) { + INFO_LOG(WII_IPC_HLE, "Extract: %s (%zd bytes)", filename.c_str(), bytes.size()); + + std::string path = WFS::NativePath(m_base_extract_path + "/" + filename); + File::CreateFullPath(path); + File::IOFile f(path, "wb"); + if (!f) + { + ERROR_LOG(WII_IPC_HLE, "Could not extract %s to %s", filename.c_str(), path.c_str()); + return; + } + f.WriteBytes(bytes.data(), bytes.size()); + }; + m_arc_unpacker.Extract(callback); + + // Technically not needed, but let's not keep large buffers in RAM for no + // reason if we can avoid it. + m_arc_unpacker.Reset(); + break; + } + + case IOCTL_WFSI_DELETE_TITLE: + // Bytes 0-4: ?? + // Bytes 4-8: game id + // Bytes 1c-1e: title id? + WARN_LOG(WII_IPC_HLE, "IOCTL_WFSI_DELETE_TITLE: unimplemented"); + break; + + case IOCTL_WFSI_IMPORT_TITLE: + WARN_LOG(WII_IPC_HLE, "IOCTL_WFSI_IMPORT_TITLE: unimplemented"); + break; + + case IOCTL_WFSI_INIT: + // Nothing to do. + INFO_LOG(WII_IPC_HLE, "IOCTL_WFSI_INIT"); + break; + + case IOCTL_WFSI_SET_DEVICE_NAME: + INFO_LOG(WII_IPC_HLE, "IOCTL_WFSI_SET_DEVICE_NAME"); + m_device_name = Memory::GetString(buffer_in); + break; + + case IOCTL_WFSI_APPLY_TITLE_PROFILE: + INFO_LOG(WII_IPC_HLE, "IOCTL_WFSI_APPLY_TITLE_PROFILE"); + + m_base_extract_path = StringFromFormat( + "/vol/%s/_install/%c%c%c%c/content", m_device_name.c_str(), + static_cast(m_tmd.GetTitleId() >> 24), static_cast(m_tmd.GetTitleId() >> 16), + static_cast(m_tmd.GetTitleId() >> 8), static_cast(m_tmd.GetTitleId())); + File::CreateFullPath(WFS::NativePath(m_base_extract_path)); + + break; + + default: + // TODO(wfs): Should be returning an error. However until we have + // everything properly stubbed it's easier to simulate the methods + // succeeding. + WARN_LOG(WII_IPC_HLE, "%s unimplemented IOCtl(0x%08x, size_in=%08x, size_out=%08x)\n%s\n%s", + m_name.c_str(), command, buffer_in_size, buffer_out_size, + HexDump(Memory::GetPointer(buffer_in), buffer_in_size).c_str(), + HexDump(Memory::GetPointer(buffer_out), buffer_out_size).c_str()); + Memory::Memset(buffer_out, 0, buffer_out_size); + break; + } + + Memory::Write_U32(return_error_code, command_address + 4); + return GetDefaultReply(); +} + +IPCCommandResult CWII_IPC_HLE_Device_wfsi::IOCtlV(u32 command_address) +{ + ERROR_LOG(WII_IPC_HLE, "IOCtlV on /dev/wfsi -- unsupported"); + Memory::Write_U32(IPC_EINVAL, command_address + 4); + return GetDefaultReply(); +} diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.h b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.h new file mode 100644 index 0000000000..d5afa62a3a --- /dev/null +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_wfsi.h @@ -0,0 +1,73 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include + +#include "Common/CommonTypes.h" +#include "Core/IPC_HLE/ESFormats.h" +#include "Core/IPC_HLE/WII_IPC_HLE.h" +#include "Core/IPC_HLE/WII_IPC_HLE_Device.h" + +class ARCUnpacker +{ +public: + ARCUnpacker() { Reset(); } + void Reset(); + + void AddBytes(const std::vector& bytes); + + using WriteCallback = std::function&)>; + void Extract(const WriteCallback& callback); + +private: + std::vector m_whole_file; +}; + +class CWII_IPC_HLE_Device_wfsi : public IWII_IPC_HLE_Device +{ +public: + CWII_IPC_HLE_Device_wfsi(u32 device_id, const std::string& device_name); + + IPCCommandResult Open(u32 command_address, u32 mode) override; + IPCCommandResult IOCtl(u32 command_address) override; + IPCCommandResult IOCtlV(u32 command_address) override; + +private: + std::string m_device_name; + + mbedtls_aes_context m_aes_ctx; + u8 m_aes_key[0x10] = {}; + u8 m_aes_iv[0x10] = {}; + + TMDReader m_tmd; + std::string m_base_extract_path; + + ARCUnpacker m_arc_unpacker; + + enum + { + IOCTL_WFSI_PREPARE_DEVICE = 0x02, + + IOCTL_WFSI_PREPARE_CONTENT = 0x03, + IOCTL_WFSI_IMPORT_CONTENT = 0x04, + IOCTL_WFSI_FINALIZE_CONTENT = 0x05, + + IOCTL_WFSI_DELETE_TITLE = 0x17, + IOCTL_WFSI_IMPORT_TITLE = 0x2f, + + IOCTL_WFSI_INIT = 0x81, + IOCTL_WFSI_SET_DEVICE_NAME = 0x82, + + IOCTL_WFSI_PREPARE_PROFILE = 0x86, + IOCTL_WFSI_IMPORT_PROFILE = 0x87, + IOCTL_WFSI_FINALIZE_PROFILE = 0x88, + + IOCTL_WFSI_APPLY_TITLE_PROFILE = 0x89, + }; +};