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, diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index a5825e15e7..7b576f9577 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 @@ -154,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 d18d130422..b9f950a2eb 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -167,6 +167,7 @@ + @@ -196,6 +197,8 @@ + + @@ -392,6 +395,7 @@ + @@ -415,6 +419,8 @@ + + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index 4b33dd2e2e..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} @@ -565,6 +568,9 @@ DSPCore + + IPC HLE %28IOS/Starlet%29\ES + IPC HLE %28IOS/Starlet%29 @@ -628,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 @@ -1162,6 +1174,9 @@ DSPCore + + IPC HLE %28IOS/Starlet%29\ES + IPC HLE %28IOS/Starlet%29 @@ -1237,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/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; +}; 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_es.cpp b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_es.cpp index 07553274c1..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" @@ -105,6 +106,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); @@ -142,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); @@ -255,6 +269,152 @@ 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_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, @@ -914,10 +1074,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..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" @@ -30,6 +31,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; @@ -140,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); 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, + }; +}; diff --git a/Source/Core/DiscIO/NANDContentLoader.cpp b/Source/Core/DiscIO/NANDContentLoader.cpp index 2c5e0e87e7..f87e0e31c0 100644 --- a/Source/Core/DiscIO/NANDContentLoader.cpp +++ b/Source/Core/DiscIO/NANDContentLoader.cpp @@ -29,6 +29,50 @@ 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; +} + +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; CSharedContent::CSharedContent() @@ -290,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()) @@ -512,7 +535,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 +548,15 @@ 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& signed_ticket) { + std::vector ticket = SignedTicketToTicket(signed_ticket); + if (ticket.empty()) + { + return false; + } + u64 title_id = Common::swap64(ticket.data() + 0x9c); + std::string ticket_filename = Common::GetTicketFileName(title_id, Common::FROM_CONFIGURED_ROOT); File::CreateFullPath(ticket_filename); @@ -534,7 +564,46 @@ bool AddTicket(u64 title_id, 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); +} + +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 f8d3bb6711..193bee596f 100644 --- a/Source/Core/DiscIO/NANDContentLoader.h +++ b/Source/Core/DiscIO/NANDContentLoader.h @@ -22,7 +22,10 @@ namespace DiscIO { enum class Region; -bool AddTicket(u64 title_id, const std::vector& ticket); +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 { @@ -110,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;