fusee_cpp: implement emummc/system partition mounting

This commit is contained in:
Michael Scire 2021-08-31 22:51:51 -07:00 committed by SciresM
parent 8560713a60
commit 2f7012cbc6
15 changed files with 1009 additions and 18 deletions

View file

@ -16,7 +16,7 @@
#include <exosphere.hpp> #include <exosphere.hpp>
#include "diskio_cpp.h" #include "diskio_cpp.h"
#include "../fusee_sd_card.hpp" #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) { 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)); 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) { bool diskio_read_system(void *dst, size_t size, size_t sector_index, size_t sector_count) {
/* TODO */ return R_SUCCEEDED(::ams::nxboot::ReadSystem(sector_index * 0x200, dst, size));
return false;
} }
bool diskio_write_system(size_t sector_index, size_t sector_count, const void *src, size_t size) { bool diskio_write_system(size_t sector_index, size_t sector_count, const void *src, size_t size) {
/* TODO */
return false; return false;
} }

View file

@ -24,8 +24,10 @@ namespace ams::fs {
constexpr size_t MaxFiles = 8; constexpr size_t MaxFiles = 8;
constinit bool g_is_sd_mounted = false; 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_sd_fs = {};
alignas(0x10) constinit FATFS g_sys_fs = {};
alignas(0x10) constinit FIL g_files[MaxFiles] = {}; alignas(0x10) constinit FIL g_files[MaxFiles] = {};
constinit bool g_files_opened[MaxFiles] = {}; constinit bool g_files_opened[MaxFiles] = {};
@ -104,16 +106,40 @@ namespace ams::fs {
bool MountSdCard() { bool MountSdCard() {
AMS_ASSERT(!g_is_sd_mounted); 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; return g_is_sd_mounted;
} }
void UnmountSdCard() { void UnmountSdCard() {
AMS_ASSERT(g_is_sd_mounted); AMS_ASSERT(g_is_sd_mounted);
f_unmount(""); f_unmount("sdmc:");
g_is_sd_mounted = false; 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) { Result CreateFile(const char *path, s64 size) {
/* Create the file. */ /* Create the file. */
FIL fp; FIL fp;

View file

@ -69,13 +69,27 @@ namespace ams::fs {
static_assert(util::is_pod<WriteOption>::value && sizeof(WriteOption) == sizeof(u32)); static_assert(util::is_pod<WriteOption>::value && sizeof(WriteOption) == sizeof(u32));
enum DirectoryEntryType {
DirectoryEntryType_Directory = 0,
DirectoryEntryType_File = 1,
};
struct FileHandle { struct FileHandle {
void *_handle; void *_handle;
}; };
struct DirectoryHandle {
void *_handle;
};
bool MountSdCard(); bool MountSdCard();
void UnmountSdCard(); 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 CreateFile(const char *path, s64 size);
Result CreateDirectory(const char *path); Result CreateDirectory(const char *path);
Result OpenFile(FileHandle *out_file, const char *path, int mode); Result OpenFile(FileHandle *out_file, const char *path, int mode);

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <exosphere.hpp>
#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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <exosphere.hpp>
#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<s64>(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<s64>(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;
};
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <exosphere.hpp>
#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<u64>(offset), static_cast<u64>(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<sdmmc::MmcPartition Partition>
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<u64>(offset), static_cast<u64>(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<sdmmc::MmcPartition_BootPartition1>;
using MmcUserStorage = MmcPartitionStorage<sdmmc::MmcPartition_UserData>;
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<u8 *>(buffer);
for (/* ... */; size > 0; ++file) {
/* Switch to the current file. */
SwitchFile(file);
/* Perform the current read. */
const size_t cur_size = std::min<size_t>(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<s64>(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<u8 *>(AllocateAligned(CacheEntries * SectorSize, SectorSize));
/* Allocate sector ids. */
m_sector_ids = static_cast<u32 *>(AllocateAligned(CacheEntries * sizeof(u32), alignof(u32)));
for (size_t i = 0; i < CacheEntries; ++i) {
m_sector_ids[i] = std::numeric_limits<u32>::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<u8 *>(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<size_t>(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<fs::SubStorage>(g_sd_card_storage, partition_start, 4_MB);
g_user_storage = AllocateObject<fs::SubStorage>(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<fs::FileHandleStorage>(boot0_file);
g_user_storage = AllocateObject<EmummcFileStorage>(user00_file, len + 1);
} else {
ShowFatalError("Unknown emummc type %d\n", static_cast<int>(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<Gpt *>(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<SystemPartitionStorage>(offset, size);
} else if (std::memcmp(gpt->entries[i].partition_name, Package2PartitionName, sizeof(Package2PartitionName)) == 0) {
g_package2_storage = AllocateObject<fs::SubStorage>(*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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
#include <exosphere/secmon/secmon_emummc_context.hpp>
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);
}

View file

@ -20,13 +20,15 @@ namespace ams::nxboot {
void *AllocateMemory(size_t size); void *AllocateMemory(size_t size);
template<typename T> ALWAYS_INLINE void *AllocateAligned(size_t size, size_t align) {
inline T *AllocateObject() { return reinterpret_cast<void *>(util::AlignUp(reinterpret_cast<uintptr_t>(AllocateMemory(size + align)), align));
void * const mem = AllocateMemory(sizeof(T) + alignof(T)); }
T * const obj = reinterpret_cast<T *>(util::AlignUp(reinterpret_cast<uintptr_t>(mem), alignof(T))); template<typename T, typename... Args> requires std::constructible_from<T, Args...>
inline T *AllocateObject(Args &&... args) {
T * const obj = static_cast<T *>(AllocateAligned(sizeof(T), alignof(T)));
std::construct_at(obj); std::construct_at(obj, std::forward<Args>(args)...);
return obj; return obj;
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <exosphere.hpp>
#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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <vapours.hpp>
#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);
}

View file

@ -85,6 +85,10 @@ namespace ams::nxboot {
return sdmmc::CheckSdCardConnection(out_sm, out_bw, SdCardPort); 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) { 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); return sdmmc::Read(dst, size, SdCardPort, sector_index, sector_count);
} }

View file

@ -20,6 +20,8 @@ namespace ams::nxboot {
Result InitializeSdCard(); Result InitializeSdCard();
Result CheckSdCardConnection(sdmmc::SpeedMode *out_sm, sdmmc::BusWidth *out_bw); 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 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); Result WriteSdCard(size_t sector_index, size_t sector_count, const void *src, size_t size);

View file

@ -14,12 +14,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <exosphere.hpp> #include <exosphere.hpp>
#include <exosphere/secmon/secmon_emummc_context.hpp>
#include "fusee_key_derivation.hpp" #include "fusee_key_derivation.hpp"
#include "fusee_secondary_archive.hpp" #include "fusee_secondary_archive.hpp"
#include "fusee_setup_horizon.hpp" #include "fusee_setup_horizon.hpp"
#include "fusee_ini.hpp" #include "fusee_ini.hpp"
#include "fusee_emummc.hpp"
#include "fusee_mmc.hpp"
#include "fusee_fatal.hpp" #include "fusee_fatal.hpp"
#include "fs/fusee_fs_api.hpp"
namespace ams::nxboot { 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() { bool ConfigureEmummc() {
/* Set magic. */ /* Set magic. */
g_emummc_cfg.base_cfg.magic = secmon::EmummcBaseConfiguration::Magic; g_emummc_cfg.base_cfg.magic = secmon::EmummcBaseConfiguration::Magic;
@ -147,13 +167,11 @@ namespace ams::nxboot {
if (sector > 0) { if (sector > 0) {
g_emummc_cfg.base_cfg.type = secmon::EmummcType_Partition; g_emummc_cfg.base_cfg.type = secmon::EmummcType_Partition;
g_emummc_cfg.partition_cfg.start_sector = sector; g_emummc_cfg.partition_cfg.start_sector = sector;
} else if (path[0] != '\x00' && IsDirectoryExist(path)) {
/* TODO */
} else if (/* TODO: directory exists */false) {
g_emummc_cfg.base_cfg.type = secmon::EmummcType_File; g_emummc_cfg.base_cfg.type = secmon::EmummcType_File;
/* TODO */ std::strncpy(g_emummc_cfg.file_cfg.path.str, path, sizeof(g_emummc_cfg.file_cfg.path.str));
AMS_UNUSED(path); g_emummc_cfg.file_cfg.path.str[sizeof(g_emummc_cfg.file_cfg.path.str) - 1] = '\x00';
} else { } else {
ShowFatalError("Invalid emummc setting!\n"); ShowFatalError("Invalid emummc setting!\n");
} }
@ -174,7 +192,9 @@ namespace ams::nxboot {
/* Determine whether we're using emummc. */ /* Determine whether we're using emummc. */
const bool emummc_enabled = ConfigureEmummc(); const bool emummc_enabled = ConfigureEmummc();
AMS_UNUSED(emummc_enabled);
/* Initialize emummc. */
InitializeEmummc(emummc_enabled, g_emummc_cfg);
AMS_UNUSED(hw_type); AMS_UNUSED(hw_type);
ShowFatalError("SetupAndStartHorizon not fully implemented\n"); ShowFatalError("SetupAndStartHorizon not fully implemented\n");

View file

@ -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 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 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 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 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); void ComputeAes128CtrAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler);

View file

@ -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) { void GetCmacResult(volatile SecurityEngineRegisters *SE, void *dst, size_t dst_size) {
const int num_words = dst_size / sizeof(u32); const int num_words = dst_size / sizeof(u32);
for (int i = 0; i < num_words; ++i) { 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); 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<u8 *>(dst);
const u8 *src_u8 = static_cast<const u8 *>(src);
const size_t num_blocks = std::min<size_t>(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<u32>(static_cast<u32>(sector));
if constexpr (sizeof(sector) > sizeof(u32)) {
static_assert(sizeof(sector) <= sizeof(u64));
base_tweak[util::size(base_tweak) - 2] = util::ConvertToBigEndian<u32>(static_cast<u32>(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) { 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 nothing to decrypt, succeed. */
if (size == 0) { return; } 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); 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) { 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. */ /* Validate the iv. */
AMS_ABORT_UNLESS(iv_size == AesBlockSize); AMS_ABORT_UNLESS(iv_size == AesBlockSize);