diff --git a/fusee_cpp/program/source/fatfs/fusee_diskio.cpp b/fusee_cpp/program/source/fatfs/fusee_diskio.cpp index f6769df63..402a5c7f9 100644 --- a/fusee_cpp/program/source/fatfs/fusee_diskio.cpp +++ b/fusee_cpp/program/source/fatfs/fusee_diskio.cpp @@ -16,7 +16,7 @@ #include #include "diskio_cpp.h" #include "../fusee_sd_card.hpp" - +#include "../fusee_emummc.hpp" bool diskio_read_sd_card(void *dst, size_t size, size_t sector_index, size_t sector_count) { return R_SUCCEEDED(::ams::nxboot::ReadSdCard(dst, size, sector_index, sector_count)); @@ -27,11 +27,9 @@ bool diskio_write_sd_card(size_t sector_index, size_t sector_count, const void * } bool diskio_read_system(void *dst, size_t size, size_t sector_index, size_t sector_count) { - /* TODO */ - return false; + return R_SUCCEEDED(::ams::nxboot::ReadSystem(sector_index * 0x200, dst, size)); } bool diskio_write_system(size_t sector_index, size_t sector_count, const void *src, size_t size) { - /* TODO */ return false; } \ No newline at end of file diff --git a/fusee_cpp/program/source/fs/fusee_fs_api.cpp b/fusee_cpp/program/source/fs/fusee_fs_api.cpp index 4dd708a60..cc4d39e80 100644 --- a/fusee_cpp/program/source/fs/fusee_fs_api.cpp +++ b/fusee_cpp/program/source/fs/fusee_fs_api.cpp @@ -24,8 +24,10 @@ namespace ams::fs { constexpr size_t MaxFiles = 8; constinit bool g_is_sd_mounted = false; + constinit bool g_is_sys_mounted = false; alignas(0x10) constinit FATFS g_sd_fs = {}; + alignas(0x10) constinit FATFS g_sys_fs = {}; alignas(0x10) constinit FIL g_files[MaxFiles] = {}; constinit bool g_files_opened[MaxFiles] = {}; @@ -104,16 +106,40 @@ namespace ams::fs { bool MountSdCard() { AMS_ASSERT(!g_is_sd_mounted); - g_is_sd_mounted = f_mount(std::addressof(g_sd_fs), "sdmc", 1) == FR_OK; + g_is_sd_mounted = f_mount(std::addressof(g_sd_fs), "sdmc:", 1) == FR_OK; return g_is_sd_mounted; } void UnmountSdCard() { AMS_ASSERT(g_is_sd_mounted); - f_unmount(""); + f_unmount("sdmc:"); g_is_sd_mounted = false; } + bool MountSystem() { + AMS_ASSERT(!g_is_sys_mounted); + g_is_sys_mounted = f_mount(std::addressof(g_sys_fs), "sys:", 1) == FR_OK; + return g_is_sys_mounted; + } + + void UnmountSystem() { + AMS_ASSERT(g_is_sys_mounted); + f_unmount("sys:"); + g_is_sys_mounted = false; + } + + Result GetEntryType(DirectoryEntryType *out_entry_type, bool *out_archive, const char *path) { + /* Get the file info. */ + FILINFO info; + R_TRY(TranslateFatFsError(f_stat(path, std::addressof(info)))); + + /* Handle the file. */ + *out_entry_type = (info.fattrib & AM_DIR) ? DirectoryEntryType_Directory : DirectoryEntryType_File; + *out_archive = (info.fattrib & AM_ARC); + + return ResultSuccess(); + } + Result CreateFile(const char *path, s64 size) { /* Create the file. */ FIL fp; diff --git a/fusee_cpp/program/source/fs/fusee_fs_api.hpp b/fusee_cpp/program/source/fs/fusee_fs_api.hpp index 7bf9bacef..237cfc99e 100644 --- a/fusee_cpp/program/source/fs/fusee_fs_api.hpp +++ b/fusee_cpp/program/source/fs/fusee_fs_api.hpp @@ -69,13 +69,27 @@ namespace ams::fs { static_assert(util::is_pod::value && sizeof(WriteOption) == sizeof(u32)); + enum DirectoryEntryType { + DirectoryEntryType_Directory = 0, + DirectoryEntryType_File = 1, + }; + struct FileHandle { void *_handle; }; + struct DirectoryHandle { + void *_handle; + }; + bool MountSdCard(); void UnmountSdCard(); + bool MountSystem(); + void UnmountSystem(); + + Result GetEntryType(DirectoryEntryType *out_entry_type, bool *out_archive, const char *path); + Result CreateFile(const char *path, s64 size); Result CreateDirectory(const char *path); Result OpenFile(FileHandle *out_file, const char *path, int mode); diff --git a/fusee_cpp/program/source/fs/fusee_fs_file_storage.cpp b/fusee_cpp/program/source/fs/fusee_fs_file_storage.cpp new file mode 100644 index 000000000..6e9cc7f5a --- /dev/null +++ b/fusee_cpp/program/source/fs/fusee_fs_file_storage.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "fusee_fs_storage.hpp" + +namespace ams::fs { + + Result FileHandleStorage::UpdateSize() { + R_SUCCEED_IF(m_size != InvalidSize); + return GetFileSize(std::addressof(m_size), m_handle); + } + + Result FileHandleStorage::Read(s64 offset, void *buffer, size_t size) { + /* Immediately succeed if there's nothing to read. */ + R_SUCCEED_IF(size == 0); + + /* Validate buffer. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure our size is valid. */ + R_TRY(this->UpdateSize()); + + /* Ensure our access is valid. */ + R_UNLESS(IStorage::CheckAccessRange(offset, size, m_size), fs::ResultOutOfRange()); + + return ReadFile(m_handle, offset, buffer, size, fs::ReadOption()); + } + + Result FileHandleStorage::Write(s64 offset, const void *buffer, size_t size) { + /* Immediately succeed if there's nothing to write. */ + R_SUCCEED_IF(size == 0); + + /* Validate buffer. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure our size is valid. */ + R_TRY(this->UpdateSize()); + + /* Ensure our access is valid. */ + R_UNLESS(IStorage::CheckAccessRange(offset, size, m_size), fs::ResultOutOfRange()); + + return WriteFile(m_handle, offset, buffer, size, fs::WriteOption()); + } + + Result FileHandleStorage::Flush() { + return FlushFile(m_handle); + } + + Result FileHandleStorage::GetSize(s64 *out_size) { + R_TRY(this->UpdateSize()); + *out_size = m_size; + return ResultSuccess(); + } + + Result FileHandleStorage::SetSize(s64 size) { + m_size = InvalidSize; + return SetFileSize(m_handle, size); + } + +} diff --git a/fusee_cpp/program/source/fs/fusee_fs_storage.hpp b/fusee_cpp/program/source/fs/fusee_fs_storage.hpp new file mode 100644 index 000000000..e302d7169 --- /dev/null +++ b/fusee_cpp/program/source/fs/fusee_fs_storage.hpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "fusee_fs_api.hpp" + +namespace ams::fs { + + class IStorage { + public: + virtual Result Read(s64 offset, void *buffer, size_t size) = 0; + + virtual Result Write(s64 offset, const void *buffer, size_t size) = 0; + + virtual Result Flush() = 0; + + virtual Result SetSize(s64 size) = 0; + + virtual Result GetSize(s64 *out) = 0; + public: + static inline bool CheckAccessRange(s64 offset, s64 size, s64 total_size) { + return offset >= 0 && + size >= 0 && + size <= total_size && + offset <= (total_size - size); + } + + static inline bool CheckAccessRange(s64 offset, size_t size, s64 total_size) { + return CheckAccessRange(offset, static_cast(size), total_size); + } + + static inline bool CheckOffsetAndSize(s64 offset, s64 size) { + return offset >= 0 && + size >= 0 && + offset <= (offset + size); + } + + static inline bool CheckOffsetAndSize(s64 offset, size_t size) { + return CheckOffsetAndSize(offset, static_cast(size)); + } + }; + + class ReadOnlyStorageAdapter : public IStorage { + private: + IStorage &m_storage; + public: + ReadOnlyStorageAdapter(IStorage &s) : m_storage(s) { /* ... */ } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + return m_storage.Read(offset, buffer, size); + } + + virtual Result Flush() override { + return m_storage.Flush(); + } + + virtual Result GetSize(s64 *out) override { + return m_storage.GetSize(out); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return fs::ResultUnsupportedOperation(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperation(); + } + }; + + class SubStorage : public IStorage { + private: + IStorage &m_storage; + s64 m_offset; + s64 m_size; + public: + SubStorage(IStorage &s, s64 o, s64 sz) : m_storage(s), m_offset(o), m_size(sz) { /* ... */ } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Succeed immediately on zero-sized operation. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments and read. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(IStorage::CheckAccessRange(offset, size, m_size), fs::ResultOutOfRange()); + return m_storage.Read(m_offset + offset, buffer, size); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override{ + /* Succeed immediately on zero-sized operation. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments and write. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(IStorage::CheckAccessRange(offset, size, m_size), fs::ResultOutOfRange()); + return m_storage.Write(m_offset + offset, buffer, size); + } + + virtual Result Flush() override { + return m_storage.Flush(); + } + + virtual Result GetSize(s64 *out) override { + *out = m_size; + return ResultSuccess(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperationInSubStorageA(); + } + }; + + class FileHandleStorage : public IStorage { + private: + static constexpr s64 InvalidSize = -1; + private: + FileHandle m_handle; + s64 m_size; + public: + constexpr explicit FileHandleStorage(FileHandle handle) : m_handle(handle), m_size(InvalidSize) { /* ... */ } + + ~FileHandleStorage() { fs::CloseFile(m_handle); } + protected: + Result UpdateSize(); + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + virtual Result Flush() override; + virtual Result GetSize(s64 *out_size) override; + virtual Result SetSize(s64 size) override; + }; + +} diff --git a/fusee_cpp/program/source/fusee_emummc.cpp b/fusee_cpp/program/source/fusee_emummc.cpp new file mode 100644 index 000000000..90884a8ac --- /dev/null +++ b/fusee_cpp/program/source/fusee_emummc.cpp @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "fusee_emummc.hpp" +#include "fusee_mmc.hpp" +#include "fusee_sd_card.hpp" +#include "fusee_fatal.hpp" +#include "fusee_malloc.hpp" +#include "fs/fusee_fs_api.hpp" +#include "fs/fusee_fs_storage.hpp" + +namespace ams::nxboot { + + namespace { + + class SdCardStorage : public fs::IStorage { + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override { + if (!util::IsAligned(offset, sdmmc::SectorSize) || !util::IsAligned(size, sdmmc::SectorSize)) { + ShowFatalError("SdCard: unaligned access to %" PRIx64 ", size=%" PRIx64"\n", static_cast(offset), static_cast(size)); + } + + return ReadSdCard(buffer, size, offset / sdmmc::SectorSize, size / sdmmc::SectorSize); + } + + virtual Result Flush() override { + return ResultSuccess(); + } + + virtual Result GetSize(s64 *out) override { + u32 num_sectors; + R_TRY(GetSdCardMemoryCapacity(std::addressof(num_sectors))); + + *out = num_sectors * sdmmc::SectorSize; + return ResultSuccess(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return fs::ResultUnsupportedOperation(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperation(); + } + }; + + template + class MmcPartitionStorage : public fs::IStorage { + public: + constexpr MmcPartitionStorage() { /* ... */ } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + if (!util::IsAligned(offset, sdmmc::SectorSize) || !util::IsAligned(size, sdmmc::SectorSize)) { + ShowFatalError("SdCard: unaligned access to %" PRIx64 ", size=%" PRIx64"\n", static_cast(offset), static_cast(size)); + } + + return ReadMmc(buffer, size, Partition, offset / sdmmc::SectorSize, size / sdmmc::SectorSize); + } + + virtual Result Flush() override { + return ResultSuccess(); + } + + virtual Result GetSize(s64 *out) override { + u32 num_sectors; + R_TRY(GetMmcMemoryCapacity(std::addressof(num_sectors), Partition)); + + *out = num_sectors * sdmmc::SectorSize; + return ResultSuccess(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return fs::ResultUnsupportedOperation(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperation(); + } + }; + + using MmcBoot0Storage = MmcPartitionStorage; + using MmcUserStorage = MmcPartitionStorage; + + constinit char g_emummc_path[0x300]; + + class EmummcFileStorage : public fs::IStorage { + private: + s64 m_file_size; + fs::FileHandle m_handle; + int m_id; + int m_file_path_ofs; + private: + void SwitchFile(int id) { + if (m_id != id) { + fs::CloseFile(m_handle); + + /* Update path. */ + g_emummc_path[m_file_path_ofs + 1] = '0' + (id % 10); + g_emummc_path[m_file_path_ofs + 0] = '0' + (id / 10); + + /* Open new file. */ + const Result result = fs::OpenFile(std::addressof(m_handle), g_emummc_path, fs::OpenMode_Read); + if (R_FAILED(result)) { + ShowFatalError("Failed to open emummc user %02d file: 0x%08" PRIx32 "!\n", id, result.GetValue()); + } + + m_id = id; + } + } + public: + EmummcFileStorage(fs::FileHandle user00, int ofs) : m_handle(user00), m_id(0), m_file_path_ofs(ofs) { + const Result result = fs::GetFileSize(std::addressof(m_file_size), m_handle); + if (R_FAILED(result)) { + ShowFatalError("Failed to get emummc file size: 0x%08" PRIx32 "!\n", result.GetValue()); + } + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + int file = offset / m_file_size; + s64 subofs = offset % m_file_size; + + u8 *cur_dst = static_cast(buffer); + + for (/* ... */; size > 0; ++file) { + /* Switch to the current file. */ + SwitchFile(file); + + /* Perform the current read. */ + const size_t cur_size = std::min(m_file_size - subofs, size); + R_TRY(fs::ReadFile(m_handle, subofs, cur_dst, cur_size)); + + /* Advance. */ + cur_dst += cur_size; + size -= cur_size; + subofs = 0; + } + + return ResultSuccess(); + } + + virtual Result Flush() override { + return fs::FlushFile(m_handle); + } + + virtual Result GetSize(s64 *out) override { + return fs::ResultUnsupportedOperation(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return fs::ResultUnsupportedOperation(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperation(); + } + }; + + constinit SdCardStorage g_sd_card_storage; + + constinit MmcBoot0Storage g_mmc_boot0_storage; + constinit MmcUserStorage g_mmc_user_storage; + + constinit fs::IStorage *g_boot0_storage = nullptr; + constinit fs::IStorage *g_user_storage = nullptr; + + class SystemPartitionStorage : public fs::IStorage { + private: + static constexpr size_t CacheEntries = BITSIZEOF(u32); + static constexpr size_t SectorSize = 0x4000; + private: + fs::SubStorage m_storage; + u8 *m_sector_cache; + u32 *m_sector_ids; + u32 m_sector_flags; + u32 m_next_idx; + private: + Result LoadSector(u8 *sector, u32 sector_id) { + /* Read the sector data. */ + R_TRY(m_storage.Read(static_cast(sector_id) * SectorSize, sector, SectorSize)); + + /* Decrypt the sector. */ + se::DecryptAes128Xts(sector, SectorSize, pkg1::AesKeySlot_BootloaderSystem0, pkg1::AesKeySlot_BootloaderSystem1, sector, SectorSize, sector_id); + + /* Mark the sector as freshly loaded. */ + m_sector_flags &= ~(1u << (sector_id % CacheEntries)); + + return ResultSuccess(); + } + + Result GetSector(u8 **out_sector, u32 sector_id) { + /* Try to find in the cache. */ + for (size_t i = 0; i < CacheEntries; ++i) { + if (m_sector_ids[i] == sector_id) { + m_sector_flags &= ~(1u << i); + *out_sector = m_sector_cache + SectorSize * i; + return ResultSuccess(); + } + } + + /* Find a sector to evict. */ + while ((m_sector_flags & (1u << m_next_idx)) == 0) { + m_sector_flags |= (1u << m_next_idx); + m_next_idx = (m_next_idx + 1) % CacheEntries; + } + + /* Get the chosen sector. */ + *out_sector = m_sector_cache + SectorSize * m_next_idx; + m_next_idx = (m_next_idx + 1) % CacheEntries; + + /* Load the sector. */ + return this->LoadSector(*out_sector, sector_id); + } + public: + SystemPartitionStorage(s64 ofs, s64 size) : m_storage(*g_user_storage, ofs, size) { + /* Allocate sector cache. */ + m_sector_cache = static_cast(AllocateAligned(CacheEntries * SectorSize, SectorSize)); + + /* Allocate sector ids. */ + m_sector_ids = static_cast(AllocateAligned(CacheEntries * sizeof(u32), alignof(u32))); + for (size_t i = 0; i < CacheEntries; ++i) { + m_sector_ids[i] = std::numeric_limits::max(); + } + + /* All sectors are dirty. */ + m_sector_flags = ~0u; + + /* Next sector is 0. */ + m_next_idx = 0; + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + u32 sector_id = offset / SectorSize; + s64 subofs = offset % SectorSize; + + u8 *cur_dst = static_cast(buffer); + while (size > 0) { + /* Get the current sector. */ + u8 *sector; + R_TRY(this->GetSector(std::addressof(sector), sector_id++)); + + /* Copy the data. */ + const size_t cur_size = std::min(SectorSize - subofs, size); + std::memcpy(cur_dst, sector + subofs, cur_size); + + /* Advance. */ + cur_dst += cur_size; + size -= cur_size; + subofs = 0; + } + + return ResultSuccess(); + } + + virtual Result Flush() override { + return m_storage.Flush(); + } + + virtual Result GetSize(s64 *out) override { + return m_storage.GetSize(out); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return fs::ResultUnsupportedOperation(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperation(); + } + }; + + constinit SystemPartitionStorage *g_system_storage = nullptr; + constinit fs::SubStorage *g_package2_storage = nullptr; + + struct Guid { + u32 data1; + u16 data2; + u16 data3; + u8 data4[8]; + }; + static_assert(sizeof(Guid) == 0x10); + + struct GptHeader { + char signature[8]; + u32 revision; + u32 header_size; + u32 header_crc32; + u32 reserved0; + u64 my_lba; + u64 alt_lba; + u64 first_usable_lba; + u64 last_usable_lba; + Guid disk_guid; + u64 partition_entry_lba; + u32 number_of_partition_entries; + u32 size_of_partition_entry; + u32 partition_entry_array_crc32; + u32 reserved1; + }; + static_assert(sizeof(GptHeader) == 0x60); + + struct GptPartitionEntry { + Guid partition_type_guid; + Guid unique_partition_guid; + u64 starting_lba; + u64 ending_lba; + u64 attributes; + char partition_name[0x48]; + }; + static_assert(sizeof(GptPartitionEntry) == 0x80); + + struct Gpt { + GptHeader header; + u8 padding[0x1A0]; + GptPartitionEntry entries[128]; + }; + static_assert(sizeof(Gpt) == 16_KB + 0x200); + + constexpr const u16 SystemPartitionName[] = { + 'S', 'Y', 'S', 'T', 'E', 'M', 0 + }; + + constexpr const u16 Package2PartitionName[] = { + 'B', 'C', 'P', 'K', 'G', '2', '-', '1', '-', 'N', 'o', 'r', 'm', 'a', 'l', '-', 'M', 'a', 'i', 'n', 0 + }; + + } + + void InitializeEmummc(bool emummc_enabled, const secmon::EmummcConfiguration &emummc_cfg) { + Result result; + if (emummc_enabled) { + /* Get sd card size. */ + s64 sd_card_size; + if (R_FAILED((result = g_sd_card_storage.GetSize(std::addressof(sd_card_size))))) { + ShowFatalError("Failed to get sd card size: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + if (emummc_cfg.base_cfg.type == secmon::EmummcType_Partition) { + const s64 partition_start = emummc_cfg.partition_cfg.start_sector * sdmmc::SectorSize; + g_boot0_storage = AllocateObject(g_sd_card_storage, partition_start, 4_MB); + g_user_storage = AllocateObject(g_sd_card_storage, partition_start + 8_MB, sd_card_size - (partition_start + 8_MB)); + } else if (emummc_cfg.base_cfg.type == secmon::EmummcType_File) { + /* Get the base emummc path. */ + std::memcpy(g_emummc_path, emummc_cfg.file_cfg.path.str, sizeof(emummc_cfg.file_cfg.path.str)); + + /* Get path length. */ + auto len = std::strlen(g_emummc_path); + + /* Append emmc. */ + std::memcpy(g_emummc_path + len, "/eMMC", 6); + len += 6; + + /* Open boot0. */ + fs::FileHandle boot0_file; + std::memcpy(g_emummc_path + len, "/boot0", 7); + if (R_FAILED((result = fs::OpenFile(std::addressof(boot0_file), g_emummc_path, fs::OpenMode_Read)))) { + ShowFatalError("Failed to open emummc boot0 file: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + /* Open boot1. */ + g_emummc_path[len + 5] = '1'; + { + fs::DirectoryEntryType entry_type; + bool is_archive; + if (R_FAILED((result = fs::GetEntryType(std::addressof(entry_type), std::addressof(is_archive), g_emummc_path)))) { + ShowFatalError("Failed to find emummc boot1 file: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + if (entry_type != fs::DirectoryEntryType_File) { + ShowFatalError("emummc boot1 file is not a file!\n"); + } + } + + /* Open userdata. */ + std::memcpy(g_emummc_path + len, "/00", 4); + fs::FileHandle user00_file; + if (R_FAILED((result = fs::OpenFile(std::addressof(user00_file), g_emummc_path, fs::OpenMode_Read)))) { + ShowFatalError("Failed to open emummc user %02d file: 0x%08" PRIx32 "!\n", 0, result.GetValue()); + } + + /* Create partitions. */ + g_boot0_storage = AllocateObject(boot0_file); + g_user_storage = AllocateObject(user00_file, len + 1); + } else { + ShowFatalError("Unknown emummc type %d\n", static_cast(emummc_cfg.base_cfg.type)); + } + } else { + /* Initialize access to mmc. */ + { + const Result result = InitializeMmc(); + if (R_FAILED(result)) { + ShowFatalError("Failed to initialize mmc: 0x%08" PRIx32 "\n", result.GetValue()); + } + } + + /* Create storages. */ + g_boot0_storage = std::addressof(g_mmc_boot0_storage); + g_user_storage = std::addressof(g_mmc_user_storage); + } + if (g_boot0_storage == nullptr) { + ShowFatalError("Failed to initialize BOOT0\n"); + } + if (g_user_storage == nullptr) { + ShowFatalError("Failed to initialize Raw EMMC\n"); + } + + /* Read the GPT. */ + Gpt *gpt = static_cast(AllocateAligned(sizeof(Gpt), 0x200)); + { + const Result result = g_user_storage->Read(0x200, gpt, sizeof(*gpt)); + if (R_FAILED(result)) { + ShowFatalError("Failed to read GPT: 0x%08" PRIx32 "\n", result.GetValue()); + } + } + + /* Check the GPT. */ + if (std::memcmp(gpt->header.signature, "EFI PART", 8) != 0) { + ShowFatalError("Invalid GPT signature\n"); + } + if (gpt->header.number_of_partition_entries > util::size(gpt->entries)) { + ShowFatalError("Too many GPT entries\n"); + } + + /* Create system storage. */ + for (u32 i = 0; i < gpt->header.number_of_partition_entries; ++i) { + if (gpt->entries[i].starting_lba < gpt->header.first_usable_lba) { + continue; + } + + const s64 offset = 0x200 * gpt->entries[i].starting_lba; + const u64 size = 0x200 * (gpt->entries[i].ending_lba + 1 - gpt->entries[i].starting_lba); + + if (std::memcmp(gpt->entries[i].partition_name, SystemPartitionName, sizeof(SystemPartitionName)) == 0) { + g_system_storage = AllocateObject(offset, size); + } else if (std::memcmp(gpt->entries[i].partition_name, Package2PartitionName, sizeof(Package2PartitionName)) == 0) { + g_package2_storage = AllocateObject(*g_user_storage, offset, size); + } + } + + /* Check that we created system storage. */ + if (g_system_storage == nullptr) { + ShowFatalError("Failed to initialize SYSTEM\n"); + } + + /* Check that we created package2 storage. */ + if (g_package2_storage == nullptr) { + ShowFatalError("Failed to initialize Package2\n"); + } + + /* Mount system. */ + if (!fs::MountSystem()) { + ShowFatalError("Failed to mount SYSTEM\n"); + } + } + + Result ReadBoot0(s64 offset, void *dst, size_t size) { + return g_boot0_storage->Read(offset, dst, size); + } + + Result ReadPackage2(s64 offset, void *dst, size_t size) { + return g_package2_storage->Read(offset, dst, size); + } + + Result ReadSystem(s64 offset, void *dst, size_t size) { + return g_system_storage->Read(offset, dst, size); + } + +} \ No newline at end of file diff --git a/fusee_cpp/program/source/fusee_emummc.hpp b/fusee_cpp/program/source/fusee_emummc.hpp new file mode 100644 index 000000000..8127115f4 --- /dev/null +++ b/fusee_cpp/program/source/fusee_emummc.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include + +namespace ams::nxboot { + + void InitializeEmummc(bool emummc_enabled, const secmon::EmummcConfiguration &emummc_cfg); + + Result ReadBoot0(s64 offset, void *dst, size_t size); + Result ReadPackage2(s64 offset, void *dst, size_t size); + Result ReadSystem(s64 offset, void *dst, size_t size); + +} \ No newline at end of file diff --git a/fusee_cpp/program/source/fusee_malloc.hpp b/fusee_cpp/program/source/fusee_malloc.hpp index c522698c8..0dfc54ac8 100644 --- a/fusee_cpp/program/source/fusee_malloc.hpp +++ b/fusee_cpp/program/source/fusee_malloc.hpp @@ -20,13 +20,15 @@ namespace ams::nxboot { void *AllocateMemory(size_t size); - template - inline T *AllocateObject() { - void * const mem = AllocateMemory(sizeof(T) + alignof(T)); + ALWAYS_INLINE void *AllocateAligned(size_t size, size_t align) { + return reinterpret_cast(util::AlignUp(reinterpret_cast(AllocateMemory(size + align)), align)); + } - T * const obj = reinterpret_cast(util::AlignUp(reinterpret_cast(mem), alignof(T))); + template requires std::constructible_from + inline T *AllocateObject(Args &&... args) { + T * const obj = static_cast(AllocateAligned(sizeof(T), alignof(T))); - std::construct_at(obj); + std::construct_at(obj, std::forward(args)...); return obj; } diff --git a/fusee_cpp/program/source/fusee_mmc.cpp b/fusee_cpp/program/source/fusee_mmc.cpp new file mode 100644 index 000000000..b97bf55b7 --- /dev/null +++ b/fusee_cpp/program/source/fusee_mmc.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "fusee_mmc.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr inline auto MmcPort = sdmmc::Port_Mmc0; + + alignas(0x10) constinit u8 g_mmc_work_buffer[sdmmc::MmcWorkBufferSize]; + + constinit inline auto g_mmc_partition = sdmmc::MmcPartition_Unknown; + + Result SelectMmcPartition(sdmmc::MmcPartition partition) { + /* Change partition, if we need to. */ + if (partition != g_mmc_partition) { + R_TRY(sdmmc::SelectMmcPartition(MmcPort, partition)); + + g_mmc_partition = partition; + } + + return ResultSuccess(); + } + + } + + Result InitializeMmc() { + /* Initialize the mmc. */ + sdmmc::Initialize(MmcPort); + + /* Set the mmc work buffer. */ + sdmmc::SetMmcWorkBuffer(MmcPort, g_mmc_work_buffer, sizeof(g_mmc_work_buffer)); + + /* Activate the mmc. */ + return sdmmc::Activate(MmcPort); + } + + Result CheckMmcConnection(sdmmc::SpeedMode *out_sm, sdmmc::BusWidth *out_bw) { + return sdmmc::CheckMmcConnection(out_sm, out_bw, MmcPort); + } + + Result GetMmcMemoryCapacity(u32 *out_num_sectors, sdmmc::MmcPartition partition) { + if (partition == sdmmc::MmcPartition_UserData) { + return sdmmc::GetDeviceMemoryCapacity(out_num_sectors, MmcPort); + } else { + return sdmmc::GetMmcBootPartitionCapacity(out_num_sectors, MmcPort); + } + } + + Result ReadMmc(void *dst, size_t size, sdmmc::MmcPartition partition, size_t sector_index, size_t sector_count) { + R_TRY(SelectMmcPartition(partition)); + return sdmmc::Read(dst, size, MmcPort, sector_index, sector_count); + } + + Result WriteMmc(sdmmc::MmcPartition partition, size_t sector_index, size_t sector_count, const void *src, size_t size) { + R_TRY(SelectMmcPartition(partition)); + return sdmmc::Write(MmcPort, sector_index, sector_count, src, size); + } + +} \ No newline at end of file diff --git a/fusee_cpp/program/source/fusee_mmc.hpp b/fusee_cpp/program/source/fusee_mmc.hpp new file mode 100644 index 000000000..f14eeb37e --- /dev/null +++ b/fusee_cpp/program/source/fusee_mmc.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#pragma once + +namespace ams::nxboot { + + Result InitializeMmc(); + Result CheckMmcConnection(sdmmc::SpeedMode *out_sm, sdmmc::BusWidth *out_bw); + Result GetMmcMemoryCapacity(u32 *out_num_sectors, sdmmc::MmcPartition partition); + + Result ReadMmc(void *dst, size_t size, sdmmc::MmcPartition partition, size_t sector_index, size_t sector_count); + Result WriteMmc(sdmmc::MmcPartition partition, size_t sector_index, size_t sector_count, const void *src, size_t size); + +} \ No newline at end of file diff --git a/fusee_cpp/program/source/fusee_sd_card.cpp b/fusee_cpp/program/source/fusee_sd_card.cpp index 89273fae0..efc2f79cf 100644 --- a/fusee_cpp/program/source/fusee_sd_card.cpp +++ b/fusee_cpp/program/source/fusee_sd_card.cpp @@ -85,6 +85,10 @@ namespace ams::nxboot { return sdmmc::CheckSdCardConnection(out_sm, out_bw, SdCardPort); } + Result GetSdCardMemoryCapacity(u32 *out_num_sectors) { + return sdmmc::GetDeviceMemoryCapacity(out_num_sectors, SdCardPort); + } + Result ReadSdCard(void *dst, size_t size, size_t sector_index, size_t sector_count) { return sdmmc::Read(dst, size, SdCardPort, sector_index, sector_count); } diff --git a/fusee_cpp/program/source/fusee_sd_card.hpp b/fusee_cpp/program/source/fusee_sd_card.hpp index 7b20f6796..acd77e20a 100644 --- a/fusee_cpp/program/source/fusee_sd_card.hpp +++ b/fusee_cpp/program/source/fusee_sd_card.hpp @@ -20,6 +20,8 @@ namespace ams::nxboot { Result InitializeSdCard(); Result CheckSdCardConnection(sdmmc::SpeedMode *out_sm, sdmmc::BusWidth *out_bw); + Result GetSdCardMemoryCapacity(u32 *out_num_sectors); + Result ReadSdCard(void *dst, size_t size, size_t sector_index, size_t sector_count); Result WriteSdCard(size_t sector_index, size_t sector_count, const void *src, size_t size); diff --git a/fusee_cpp/program/source/fusee_setup_horizon.cpp b/fusee_cpp/program/source/fusee_setup_horizon.cpp index 8526f172f..79fc7acad 100644 --- a/fusee_cpp/program/source/fusee_setup_horizon.cpp +++ b/fusee_cpp/program/source/fusee_setup_horizon.cpp @@ -14,12 +14,14 @@ * along with this program. If not, see . */ #include -#include #include "fusee_key_derivation.hpp" #include "fusee_secondary_archive.hpp" #include "fusee_setup_horizon.hpp" #include "fusee_ini.hpp" +#include "fusee_emummc.hpp" +#include "fusee_mmc.hpp" #include "fusee_fatal.hpp" +#include "fs/fusee_fs_api.hpp" namespace ams::nxboot { @@ -101,6 +103,24 @@ namespace ams::nxboot { } } + bool IsDirectoryExist(const char *path) { + fs::DirectoryEntryType entry_type; + bool archive; + return R_SUCCEEDED(fs::GetEntryType(std::addressof(entry_type), std::addressof(archive), path)) && entry_type == fs::DirectoryEntryType_Directory; + } + + [[maybe_unused]] bool IsFileExist(const char *path) { + fs::DirectoryEntryType entry_type; + bool archive; + return R_SUCCEEDED(fs::GetEntryType(std::addressof(entry_type), std::addressof(archive), path)) && entry_type == fs::DirectoryEntryType_File; + } + + [[maybe_unused]] bool IsConcatenationFileExist(const char *path) { + fs::DirectoryEntryType entry_type; + bool archive; + return R_SUCCEEDED(fs::GetEntryType(std::addressof(entry_type), std::addressof(archive), path)) && ((entry_type == fs::DirectoryEntryType_File) || (entry_type == fs::DirectoryEntryType_Directory && archive)); + } + bool ConfigureEmummc() { /* Set magic. */ g_emummc_cfg.base_cfg.magic = secmon::EmummcBaseConfiguration::Magic; @@ -147,13 +167,11 @@ namespace ams::nxboot { if (sector > 0) { g_emummc_cfg.base_cfg.type = secmon::EmummcType_Partition; g_emummc_cfg.partition_cfg.start_sector = sector; - - /* TODO */ - } else if (/* TODO: directory exists */false) { + } else if (path[0] != '\x00' && IsDirectoryExist(path)) { g_emummc_cfg.base_cfg.type = secmon::EmummcType_File; - /* TODO */ - AMS_UNUSED(path); + std::strncpy(g_emummc_cfg.file_cfg.path.str, path, sizeof(g_emummc_cfg.file_cfg.path.str)); + g_emummc_cfg.file_cfg.path.str[sizeof(g_emummc_cfg.file_cfg.path.str) - 1] = '\x00'; } else { ShowFatalError("Invalid emummc setting!\n"); } @@ -174,7 +192,9 @@ namespace ams::nxboot { /* Determine whether we're using emummc. */ const bool emummc_enabled = ConfigureEmummc(); - AMS_UNUSED(emummc_enabled); + + /* Initialize emummc. */ + InitializeEmummc(emummc_enabled, g_emummc_cfg); AMS_UNUSED(hw_type); ShowFatalError("SetupAndStartHorizon not fully implemented\n"); diff --git a/libraries/libexosphere/include/exosphere/se/se_aes.hpp b/libraries/libexosphere/include/exosphere/se/se_aes.hpp index b5a828648..ba7e116e1 100644 --- a/libraries/libexosphere/include/exosphere/se/se_aes.hpp +++ b/libraries/libexosphere/include/exosphere/se/se_aes.hpp @@ -47,6 +47,8 @@ namespace ams::se { void DecryptAes128Cbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size); void DecryptAes256Cbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size); + void DecryptAes128Xts(void *dst, size_t dst_size, int slot_enc, int slot_tweak, const void *src, size_t src_size, size_t sector); + void EncryptAes128CbcAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler); void DecryptAes128CbcAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler); void ComputeAes128CtrAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler); diff --git a/libraries/libexosphere/source/se/se_aes.cpp b/libraries/libexosphere/source/se/se_aes.cpp index f5950552c..3d3f659c0 100644 --- a/libraries/libexosphere/source/se/se_aes.cpp +++ b/libraries/libexosphere/source/se/se_aes.cpp @@ -191,6 +191,21 @@ namespace ams::se { } } + void ExpandSubkeyLittleEndian(u8 *subkey) { + /* Shift everything left one bit. */ + u8 prev = 0; + for (size_t i = 0; i < AesBlockSize; ++i) { + const u8 top = (subkey[i] >> 7); + subkey[i] = ((subkey[i] << 1) | prev); + prev = top; + } + + /* And xor with Rb if necessary. */ + if (prev != 0) { + subkey[0] ^= 0x87; + } + } + void GetCmacResult(volatile SecurityEngineRegisters *SE, void *dst, size_t dst_size) { const int num_words = dst_size / sizeof(u32); for (int i = 0; i < num_words; ++i) { @@ -339,6 +354,80 @@ namespace ams::se { ExecuteOperation(SE, SE_OPERATION_OP_START, dst, dst_size, src, aligned_size); } + void XorWithXtsTweak(void *dst, size_t dst_size, const void *src, size_t src_size, const void *base_tweak) { + /* Copy tweak. */ + u8 tweak[se::AesBlockSize]; + std::memcpy(tweak, base_tweak, sizeof(tweak)); + + /* Perform xor. */ + u8 *dst_u8 = static_cast(dst); + const u8 *src_u8 = static_cast(src); + + const size_t num_blocks = std::min(dst_size, src_size) / sizeof(tweak); + for (size_t i = 0; i < num_blocks; ++i) { + for (size_t j = 0; j < sizeof(tweak); ++j) { + dst_u8[j] = src_u8[j] ^ tweak[j]; + } + + dst_u8 += sizeof(tweak); + src_u8 += sizeof(tweak); + + ExpandSubkeyLittleEndian(tweak); + } + } + + void DecryptAesXts(void *dst, size_t dst_size, int slot_enc, int slot_tweak, const void *src, size_t src_size, size_t sector, AesMode mode) { + /* If nothing to decrypt, succeed. */ + if (src_size == 0) { return; } + + /* Validate input. */ + AMS_ABORT_UNLESS(util::IsAligned(dst_size, AesBlockSize)); + AMS_ABORT_UNLESS(util::IsAligned(src_size, AesBlockSize)); + AMS_ABORT_UNLESS(0 <= slot_enc && slot_enc < AesKeySlotCount); + AMS_ABORT_UNLESS(0 <= slot_tweak && slot_tweak < AesKeySlotCount); + + /* Generate tweak. */ + u32 base_tweak[se::AesBlockSize / sizeof(u32)] = {}; + base_tweak[util::size(base_tweak) - 1] = util::ConvertToBigEndian(static_cast(sector)); + if constexpr (sizeof(sector) > sizeof(u32)) { + static_assert(sizeof(sector) <= sizeof(u64)); + base_tweak[util::size(base_tweak) - 2] = util::ConvertToBigEndian(static_cast(sector >> BITSIZEOF(u32))); + } + se::EncryptAes128(base_tweak, sizeof(base_tweak), slot_tweak, base_tweak, sizeof(base_tweak)); + + /* Xor all data. */ + XorWithXtsTweak(dst, dst_size, src, src_size, base_tweak); + + /* Ensure the SE sees correct data. */ + hw::FlushDataCache(dst, dst_size); + + /* Decrypt all data. */ + { + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Determine extents. */ + const size_t num_blocks = dst_size / AesBlockSize; + + /* Configure for AES-ECB decryption to memory. */ + SetConfig(SE, false, SE_CONFIG_DST_MEMORY); + SetAesConfig(SE, slot_enc, false, AesConfigEcb); + UpdateAesMode(SE, mode); + + /* Set the block count. */ + SetBlockCount(SE, num_blocks); + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, dst, dst_size, dst, dst_size); + + /* Ensure the cpu sees correct data. */ + hw::InvalidateDataCache(dst, dst_size); + } + + /* Xor all data. */ + XorWithXtsTweak(dst, dst_size, dst, dst_size, base_tweak); + } + void ComputeAes128Async(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, DoneHandler handler, u32 config, bool encrypt, volatile SecurityEngineRegisters *SE) { /* If nothing to decrypt, succeed. */ if (size == 0) { return; } @@ -562,6 +651,10 @@ namespace ams::se { return DecryptAesCbc(dst, dst_size, slot, src, src_size, iv, iv_size, AesMode_Aes256); } + void DecryptAes128Xts(void *dst, size_t dst_size, int slot_enc, int slot_tweak, const void *src, size_t src_size, size_t sector) { + return DecryptAesXts(dst, dst_size, slot_enc, slot_tweak, src, src_size, sector, AesMode_Aes128); + } + void EncryptAes128CbcAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler) { /* Validate the iv. */ AMS_ABORT_UNLESS(iv_size == AesBlockSize);