From 8560713a60b98d60e3044122eda5aadc3d260f3d Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Sat, 28 Aug 2021 03:05:03 -0700 Subject: [PATCH] fusee: implement parsing for emummc.ini --- fusee_cpp/program/source/fs/fusee_fs_api.cpp | 2 +- fusee_cpp/program/source/fusee_ini.cpp | 167 ++++++++++++++++++ fusee_cpp/program/source/fusee_ini.hpp | 45 +++++ fusee_cpp/program/source/fusee_malloc.cpp | 40 +++++ fusee_cpp/program/source/fusee_malloc.hpp | 34 ++++ .../program/source/fusee_setup_horizon.cpp | 116 +++++++++++- 6 files changed, 397 insertions(+), 7 deletions(-) create mode 100644 fusee_cpp/program/source/fusee_ini.cpp create mode 100644 fusee_cpp/program/source/fusee_ini.hpp create mode 100644 fusee_cpp/program/source/fusee_malloc.cpp create mode 100644 fusee_cpp/program/source/fusee_malloc.hpp diff --git a/fusee_cpp/program/source/fs/fusee_fs_api.cpp b/fusee_cpp/program/source/fs/fusee_fs_api.cpp index 754aab0f2..4dd708a60 100644 --- a/fusee_cpp/program/source/fs/fusee_fs_api.cpp +++ b/fusee_cpp/program/source/fs/fusee_fs_api.cpp @@ -104,7 +104,7 @@ namespace ams::fs { bool MountSdCard() { AMS_ASSERT(!g_is_sd_mounted); - g_is_sd_mounted = f_mount(std::addressof(g_sd_fs), "", 1) == FR_OK; + g_is_sd_mounted = f_mount(std::addressof(g_sd_fs), "sdmc", 1) == FR_OK; return g_is_sd_mounted; } diff --git a/fusee_cpp/program/source/fusee_ini.cpp b/fusee_cpp/program/source/fusee_ini.cpp new file mode 100644 index 000000000..8bcc340b0 --- /dev/null +++ b/fusee_cpp/program/source/fusee_ini.cpp @@ -0,0 +1,167 @@ +/* + * 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_ini.hpp" +#include "fusee_malloc.hpp" +#include "fs/fusee_fs_api.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr s64 IniFileSizeMax = 64_KB; + + constexpr bool IsWhiteSpace(char c) { + return c == ' ' || c == '\r'; + } + + } + + ParseIniResult ParseIniFile(IniSectionList &out_sections, const char *ini_path) { + /* Open the ini file. */ + fs::FileHandle file; + if (R_FAILED(fs::OpenFile(std::addressof(file), ini_path, fs::OpenMode_Read))) { + return ParseIniResult_NoFile; + } + + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get file size. */ + s64 file_size; + if (R_FAILED(fs::GetFileSize(std::addressof(file_size), file))) { + return ParseIniResult_NoFile; + } + + /* Cap file size. */ + file_size = std::min(IniFileSizeMax, file_size); + + /* Allocate memory for file. */ + char *buffer = static_cast(AllocateMemory(util::AlignUp(file_size + 1, 0x10))); + buffer[file_size] = '\x00'; + + /* Read file. */ + if (R_FAILED(fs::ReadFile(file, 0, buffer, file_size))) { + return ParseIniResult_NoFile; + } + + /* Parse the file. */ + enum class State { + Newline, + Comment, + SectionName, + Key, + KvSpace, + Value, + TrailingSpace, + }; + + char *sec_start, *key_start, *val_start; + IniSection *cur_sec = nullptr; + + State state = State::Newline; + for (int i = 0; i < file_size; ++i) { + const char c = buffer[i]; + + switch (state) { + case State::Newline: + if (c == '[') { + sec_start = buffer + i + 1; + state = State::SectionName; + } else if (c == ';' || c == '#') { + state = State::Comment; + } else if (IsWhiteSpace(c) || c == '\n') { + state = State::Newline; + } else if (cur_sec != nullptr) { + key_start = buffer + i; + state = State::Key; + } else { + return ParseIniResult_InvalidFormat; + } + break; + case State::Comment: + if (c == '\n') { + state = State::Newline; + } + break; + case State::SectionName: + if (c == '\n') { + return ParseIniResult_InvalidFormat; + } else if (c == ']') { + cur_sec = AllocateObject(); + + cur_sec->name = sec_start; + buffer[i] = '\x00'; + + out_sections.push_back(*cur_sec); + + state = State::TrailingSpace; + } + break; + case State::Key: + if (c == '\n') { + return ParseIniResult_InvalidFormat; + } else if (IsWhiteSpace(c)) { + buffer[i] = '\x00'; + + state = State::KvSpace; + } else if (c == '=') { + buffer[i] = '\x00'; + + val_start = buffer + i + 1; + + state = State::Value; + } + break; + case State::KvSpace: + if (c == '=') { + val_start = buffer + i + 1; + + state = State::Value; + } else if (!IsWhiteSpace(c)) { + return ParseIniResult_InvalidFormat; + } + break; + case State::Value: + if (IsWhiteSpace(c) || c == '\n') { + buffer[i] = '\x00'; + + auto *entry = AllocateObject(); + entry->key = key_start; + entry->value = val_start; + + cur_sec->kv_list.push_back(*entry); + + state = (c == '\n') ? State::Newline : State::TrailingSpace; + } + break; + case State::TrailingSpace: + if (c == '\n') { + state = State::Newline; + } else if (!IsWhiteSpace(c)) { + return ParseIniResult_InvalidFormat; + } + break; + } + } + + if (state == State::TrailingSpace || state == State::Comment || state == State::Newline) { + return ParseIniResult_Success; + } else { + return ParseIniResult_InvalidFormat; + } + } + +} diff --git a/fusee_cpp/program/source/fusee_ini.hpp b/fusee_cpp/program/source/fusee_ini.hpp new file mode 100644 index 000000000..1990ae9ef --- /dev/null +++ b/fusee_cpp/program/source/fusee_ini.hpp @@ -0,0 +1,45 @@ +/* + * 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 { + + struct IniKeyValueEntry { + util::IntrusiveListNode list_node; + char *key; + char *value; + }; + + using IniKeyValueList = typename util::IntrusiveListMemberTraits<&IniKeyValueEntry::list_node>::ListType; + + struct IniSection { + util::IntrusiveListNode list_node; + IniKeyValueList kv_list; + char *name; + }; + + using IniSectionList = typename util::IntrusiveListMemberTraits<&IniSection::list_node>::ListType; + + enum ParseIniResult { + ParseIniResult_Success, + ParseIniResult_NoFile, + ParseIniResult_InvalidFormat, + }; + + ParseIniResult ParseIniFile(IniSectionList &out_sections, const char *ini_path); + +} \ No newline at end of file diff --git a/fusee_cpp/program/source/fusee_malloc.cpp b/fusee_cpp/program/source/fusee_malloc.cpp new file mode 100644 index 000000000..d41fe2293 --- /dev/null +++ b/fusee_cpp/program/source/fusee_malloc.cpp @@ -0,0 +1,40 @@ +/* + * 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_malloc.hpp" +#include "fusee_secondary_archive.hpp" +#include "fusee_fatal.hpp" + +namespace ams::nxboot { + + namespace { + + constinit uintptr_t g_heap_address = 0xC1000000; + + } + + void *AllocateMemory(size_t size) { + /* Get the current heap address. */ + void * const allocated = reinterpret_cast(g_heap_address); + + /* Advance the current heap address. */ + g_heap_address += size; + + /* Return the allocated chunk. */ + return allocated; + } + +} diff --git a/fusee_cpp/program/source/fusee_malloc.hpp b/fusee_cpp/program/source/fusee_malloc.hpp new file mode 100644 index 000000000..c522698c8 --- /dev/null +++ b/fusee_cpp/program/source/fusee_malloc.hpp @@ -0,0 +1,34 @@ +/* + * 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 { + + void *AllocateMemory(size_t size); + + template + inline T *AllocateObject() { + void * const mem = AllocateMemory(sizeof(T) + alignof(T)); + + T * const obj = reinterpret_cast(util::AlignUp(reinterpret_cast(mem), alignof(T))); + + std::construct_at(obj); + + return obj; + } + +} \ No newline at end of file diff --git a/fusee_cpp/program/source/fusee_setup_horizon.cpp b/fusee_cpp/program/source/fusee_setup_horizon.cpp index 574d762d5..8526f172f 100644 --- a/fusee_cpp/program/source/fusee_setup_horizon.cpp +++ b/fusee_cpp/program/source/fusee_setup_horizon.cpp @@ -14,9 +14,11 @@ * 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_fatal.hpp" namespace ams::nxboot { @@ -26,14 +28,16 @@ namespace ams::nxboot { constexpr inline const uintptr_t CLKRST = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); constexpr inline const uintptr_t MC = secmon::MemoryRegionPhysicalDeviceMemoryController.GetAddress(); + constinit secmon::EmummcConfiguration g_emummc_cfg = {}; + void DisableArc() { - /* Enable ARC_CLK_OVR_ON. */ + /* Disable ARC_CLK_OVR_ON. */ reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRD, CLK_RST_REG_BITS_ENUM(LVL2_CLK_GATE_OVRD_ARC_CLK_OVR_ON, OFF)); - /* Enable the ARC. */ + /* Disable the ARC. */ reg::ReadWrite(MC + MC_IRAM_REG_CTRL, MC_REG_BITS_ENUM(IRAM_REG_CTRL_IRAM_CFG_WRITE_ACCESS, DISABLED)); - /* Set IRAM BOM/TOP to open up access to all mmio. */ + /* Set IRAM BOM/TOP to close all redirection access. */ reg::Write(MC + MC_IRAM_BOM, 0xFFFFF000); reg::Write(MC + MC_IRAM_TOM, 0x00000000); @@ -61,6 +65,103 @@ namespace ams::nxboot { } } + bool ParseIniSafe(IniSectionList &out_sections, const char *ini_path) { + const auto result = ParseIniFile(out_sections, ini_path); + if (result == ParseIniResult_Success) { + return true; + } else if (result == ParseIniResult_NoFile) { + return false; + } else { + ShowFatalError("Failed to parse %s!\n", ini_path); + } + } + + u32 ParseHexInteger(const char *s) { + u32 x = 0; + if (s[0] == '0' && s[1] == 'x') { + s += 2; + } + + while (true) { + const char c = *(s++); + + if (c == '\x00') { + return x; + } else { + x <<= 4; + + if ('0' <= c && c <= '9') { + x |= c - '0'; + } else if ('a' <= c && c <= 'f') { + x |= c - 'a'; + } else if ('A' <= c && c <= 'F') { + x |= c - 'A'; + } + } + } + } + + bool ConfigureEmummc() { + /* Set magic. */ + g_emummc_cfg.base_cfg.magic = secmon::EmummcBaseConfiguration::Magic; + + /* Parse ini. */ + bool enabled = false; + u32 id = 0; + u32 sector = 0; + const char *path = ""; + const char *n_path = ""; + { + IniSectionList sections; + if (ParseIniSafe(sections, "sdmc:/emummc/emummc.ini")) { + for (const auto §ion : sections) { + /* We only care about the [emummc] section. */ + if (std::strcmp(section.name, "emummc")) { + continue; + } + + /* Handle individual fields. */ + for (const auto &entry : section.kv_list) { + if (std::strcmp(entry.key, "enabled") == 0) { + enabled = entry.value[0] == '1'; + } else if (std::strcmp(entry.key, "id") == 0) { + id = ParseHexInteger(entry.value); + } else if (std::strcmp(entry.key, "sector") == 0) { + sector = ParseHexInteger(entry.value); + } else if (std::strcmp(entry.key, "path") == 0) { + path = entry.value; + } else if (std::strcmp(entry.key, "nintendo_path") == 0) { + n_path = entry.value; + } + } + } + } + } + + /* Set values parsed from config. */ + g_emummc_cfg.base_cfg.id = id; + std::strncpy(g_emummc_cfg.emu_dir_path.str, n_path, sizeof(g_emummc_cfg.emu_dir_path.str)); + g_emummc_cfg.emu_dir_path.str[sizeof(g_emummc_cfg.emu_dir_path.str) - 1] = '\x00'; + + if (enabled) { + 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) { + g_emummc_cfg.base_cfg.type = secmon::EmummcType_File; + + /* TODO */ + AMS_UNUSED(path); + } else { + ShowFatalError("Invalid emummc setting!\n"); + } + } + + return enabled; + } + } void SetupAndStartHorizon() { @@ -71,12 +172,15 @@ namespace ams::nxboot { /* Derive all keys. */ DeriveAllKeys(soc_type); - /* Disable the ARC redirect. */ - /* NOTE: Devices can no longer access IRAM from this point onwards. */ - DisableArc(); + /* Determine whether we're using emummc. */ + const bool emummc_enabled = ConfigureEmummc(); + AMS_UNUSED(emummc_enabled); AMS_UNUSED(hw_type); ShowFatalError("SetupAndStartHorizon not fully implemented\n"); + + /* Disable the ARC. */ + DisableArc(); } } \ No newline at end of file