From dca06f2b4901960a876e5e74f2c8a1a05563f5bb Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Fri, 19 Jun 2020 21:14:40 +0100 Subject: [PATCH] Rework loader abstractions and the NRO loader to use the vfs APIs This will make it easier for us to implement more executable formats in the future. --- app/src/main/cpp/skyline/common.h | 10 ---- app/src/main/cpp/skyline/loader/loader.h | 53 +++++++++++-------- app/src/main/cpp/skyline/loader/nro.cpp | 67 +++++++++++++++++------- app/src/main/cpp/skyline/loader/nro.h | 60 +++++++++++++++------ app/src/main/cpp/skyline/os.cpp | 10 ++-- app/src/main/cpp/skyline/os.h | 3 +- app/src/main/cpp/skyline/vfs/nacp.h | 3 ++ 7 files changed, 135 insertions(+), 71 deletions(-) diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index 9792dba0..a4d95bb0 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -57,16 +57,6 @@ namespace skyline { } }; - /** - * @brief This enumerates the types of the ROM - * @note This needs to be synchronized with emu.skyline.loader.BaseLoader.TitleFormat - */ - enum class TitleFormat { - NRO, //!< The NRO format: https://switchbrew.org/wiki/NRO - XCI, //!< The XCI format: https://switchbrew.org/wiki/XCI - NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws - }; - namespace util { /** * @brief Returns the current time in nanoseconds diff --git a/app/src/main/cpp/skyline/loader/loader.h b/app/src/main/cpp/skyline/loader/loader.h index 942803d9..f45ff635 100644 --- a/app/src/main/cpp/skyline/loader/loader.h +++ b/app/src/main/cpp/skyline/loader/loader.h @@ -3,35 +3,46 @@ #pragma once -#include -#include -#include +#include +#include namespace skyline::loader { + /** + * @brief This enumerates the types of ROM files + * @note This needs to be synchronized with emu.skyline.loader.BaseLoader.RomFormat + */ + enum class RomFormat { + NRO, //!< The NRO format: https://switchbrew.org/wiki/NRO + XCI, //!< The XCI format: https://switchbrew.org/wiki/XCI + NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws + }; + + /** + * @brief The Loader class provides an abstract interface for ROM loaders + */ class Loader { protected: - int fd; //!< An FD to the ROM file - - /** - * @brief Read the file at a particular offset - * @tparam T The type of object to write to - * @param output The object to write to - * @param offset The offset to read the file at - * @param size The amount to read in bytes - */ - template - inline void ReadOffset(T *output, u64 offset, size_t size) { - pread64(fd, output, size, offset); - } + std::shared_ptr backing; //!< The backing of the loader public: - /** - * @param filePath The path to the ROM file - */ - Loader(int fd) : fd(fd) {} + std::shared_ptr nacp; //!< The NACP of the current application /** - * This loads in the data of the main process + * @param backing The backing for the NRO + */ + Loader(const std::shared_ptr &backing) : backing(backing) {} + + virtual ~Loader() = default; + + /** + * @return The icon of the loaded application + */ + virtual std::vector GetIcon() { + return std::vector(); + } + + /** + * @brief This loads in the data of the main process * @param process The process to load in the data * @param state The state of the device */ diff --git a/app/src/main/cpp/skyline/loader/nro.cpp b/app/src/main/cpp/skyline/loader/nro.cpp index 493b6fca..e1edf9bf 100644 --- a/app/src/main/cpp/skyline/loader/nro.cpp +++ b/app/src/main/cpp/skyline/loader/nro.cpp @@ -1,38 +1,65 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) -#include +#include +#include #include +#include +#include #include "nro.h" namespace skyline::loader { - NroLoader::NroLoader(int fd) : Loader(fd) { - ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader)); + NroLoader::NroLoader(const std::shared_ptr &backing) : Loader(backing) { + backing->Read(&header); if (header.magic != util::MakeMagic("NRO0")) throw exception("Invalid NRO magic! 0x{0:X}", header.magic); + + // The homebrew asset section is appended to the end of an NRO file + if (backing->size > header.size) { + backing->Read(&assetHeader, header.size); + + if (assetHeader.magic != util::MakeMagic("ASET")) + throw exception("Invalid ASET magic! 0x{0:X}", assetHeader.magic); + + NroAssetSection &nacpHeader = assetHeader.nacp; + nacp = std::make_shared(std::make_shared(backing, header.size + nacpHeader.offset, nacpHeader.size)); + } + } + + std::vector NroLoader::GetIcon() { + NroAssetSection &segmentHeader = assetHeader.icon; + std::vector buffer(segmentHeader.size); + + backing->Read(buffer.data(), header.size + segmentHeader.offset, segmentHeader.size); + return buffer; + } + + std::vector NroLoader::GetSegment(NroSegmentType segment) { + NroSegmentHeader &segmentHeader = header.segments[static_cast(segment)]; + std::vector buffer(segmentHeader.size); + + backing->Read(buffer.data(), segmentHeader.offset, segmentHeader.size); + return buffer; } void NroLoader::LoadProcessData(const std::shared_ptr process, const DeviceState &state) { - std::vector text(header.text.size); - std::vector rodata(header.ro.size); - std::vector data(header.data.size); - - ReadOffset(text.data(), header.text.offset, header.text.size); - ReadOffset(rodata.data(), header.ro.offset, header.ro.size); - ReadOffset(data.data(), header.data.offset, header.data.size); - - std::vector patch = state.nce->PatchCode(text, constant::BaseAddress, header.text.size + header.ro.size + header.data.size + header.bssSize); + std::vector text = GetSegment(loader::NroLoader::NroSegmentType::Text); + std::vector rodata = GetSegment(loader::NroLoader::NroSegmentType::RO); + std::vector data = GetSegment(loader::NroLoader::NroSegmentType::Data); u64 textSize = text.size(); u64 rodataSize = rodata.size(); u64 dataSize = data.size(); + u64 bssSize = header.bssSize; + + std::vector patch = state.nce->PatchCode(text, constant::BaseAddress, textSize + rodataSize + dataSize + bssSize); if (!util::IsAligned(textSize, PAGE_SIZE) || !util::IsAligned(rodataSize, PAGE_SIZE) || !util::IsAligned(dataSize, PAGE_SIZE)) throw exception("LoadProcessData: Sections are not aligned with page size: 0x{:X}, 0x{:X}, 0x{:X}", textSize, rodataSize, dataSize); u64 patchSize = patch.size() * sizeof(u32); - u64 padding = util::AlignUp(textSize + rodataSize + dataSize + header.bssSize + patchSize, PAGE_SIZE) - (textSize + rodataSize + dataSize + header.bssSize + patchSize); + u64 padding = util::AlignUp(textSize + rodataSize + dataSize + bssSize + patchSize, PAGE_SIZE) - (textSize + rodataSize + dataSize + bssSize + patchSize); process->NewHandle(constant::BaseAddress, textSize, memory::Permission{true, true, true}, memory::states::CodeStatic); // R-X state.logger->Debug("Successfully mapped section .text @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress, textSize); @@ -43,17 +70,17 @@ namespace skyline::loader { process->NewHandle(constant::BaseAddress + textSize + rodataSize, dataSize, memory::Permission{true, true, false}, memory::states::CodeStatic); // RW- state.logger->Debug("Successfully mapped section .data @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize, dataSize); - process->NewHandle(constant::BaseAddress + textSize + rodataSize + dataSize, header.bssSize, memory::Permission{true, true, true}, memory::states::CodeMutable); // RWX - state.logger->Debug("Successfully mapped section .bss @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize + dataSize, header.bssSize); + process->NewHandle(constant::BaseAddress + textSize + rodataSize + dataSize, bssSize, memory::Permission{true, true, true}, memory::states::CodeMutable); // RWX + state.logger->Debug("Successfully mapped section .bss @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize + dataSize, bssSize); - process->NewHandle(constant::BaseAddress + textSize + rodataSize + dataSize + header.bssSize, patchSize + padding, memory::Permission{true, true, true}, memory::states::CodeStatic); // RWX - state.logger->Debug("Successfully mapped section .patch @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize + dataSize + header.bssSize, patchSize); + process->NewHandle(constant::BaseAddress + textSize + rodataSize + dataSize + bssSize, patchSize + padding, memory::Permission{true, true, true}, memory::states::CodeStatic); // RWX + state.logger->Debug("Successfully mapped section .patch @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize + dataSize + bssSize, patchSize); process->WriteMemory(text.data(), constant::BaseAddress, textSize); process->WriteMemory(rodata.data(), constant::BaseAddress + textSize, rodataSize); process->WriteMemory(data.data(), constant::BaseAddress + textSize + rodataSize, dataSize); - process->WriteMemory(patch.data(), constant::BaseAddress + textSize + rodataSize + dataSize + header.bssSize, patchSize); + process->WriteMemory(patch.data(), constant::BaseAddress + textSize + rodataSize + dataSize + bssSize, patchSize); - state.os->memory.InitializeRegions(constant::BaseAddress, textSize + rodataSize + dataSize + header.bssSize + patchSize + padding, memory::AddressSpaceType::AddressSpace39Bit); + state.os->memory.InitializeRegions(constant::BaseAddress, textSize + rodataSize + dataSize + bssSize + patchSize + padding, memory::AddressSpaceType::AddressSpace39Bit); } -} +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/loader/nro.h b/app/src/main/cpp/skyline/loader/nro.h index 9e56f718..1b06551d 100644 --- a/app/src/main/cpp/skyline/loader/nro.h +++ b/app/src/main/cpp/skyline/loader/nro.h @@ -3,10 +3,13 @@ #pragma once -#include +#include #include "loader.h" namespace skyline::loader { + /** + * @brief The NroLoader class abstracts access to an NRO file through the Loader interface (https://switchbrew.org/wiki/NRO) + */ class NroLoader : public Loader { private: /** @@ -21,40 +24,67 @@ namespace skyline::loader { * @brief This holds the header of an NRO file */ struct NroHeader { - u32 : 32; + u32 _pad0_; u32 modOffset; //!< The offset of the MOD metadata - u64 : 64; + u64 _pad1_; u32 magic; //!< The NRO magic "NRO0" u32 version; //!< The version of the application u32 size; //!< The size of the NRO u32 flags; //!< The flags used with the NRO - NroSegmentHeader text; //!< The .text segment header - NroSegmentHeader ro; //!< The .ro segment header - NroSegmentHeader data; //!< The .data segment header + NroSegmentHeader segments[3]; //!< The .text segment header u32 bssSize; //!< The size of the bss segment - u32 : 32; + u32 _pad2_; u64 buildId[4]; //!< The build ID of the NRO - u64 : 64; + u64 _pad3_; NroSegmentHeader apiInfo; //!< The .apiInfo segment header NroSegmentHeader dynstr; //!< The .dynstr segment header NroSegmentHeader dynsym; //!< The .dynsym segment header } header{}; - public: /** - * @param fd A file descriptor to the ROM + * @brief This holds a single asset sections's offset and size */ - NroLoader(int fd); + struct NroAssetSection { + u64 offset; //!< The offset of the region + u64 size; //!< The size of the region + }; /** - * @brief This loads in the data of the main process - * @param process The process to load in the data - * @param state The state of the device + * @brief This holds various metadata about an NRO, it is only used by homebrew + */ + struct NroAssetHeader { + u32 magic; //!< The asset section magic "ASET" + u32 version; //!< The format version + NroAssetSection icon; //!< The header describing the location of the icon + NroAssetSection nacp; //!< The header describing the location of the nacp + NroAssetSection romfs; //!< The header describing the location of the romfs + } assetHeader{}; + + /** + * @brief This enumerates the segments withing an NRO file */ + enum class NroSegmentType : int { + Text = 0, //!< The .text section + RO = 1, //!< The .rodata section + Data = 2 //!< The .data section + }; + + /** + * @brief This reads the data of the specified segment + * @param segment The type of segment to read + * @return A buffer containing the data of the requested segment + */ + std::vector GetSegment(NroSegmentType segment); + + public: + NroLoader(const std::shared_ptr &backing); + + std::vector GetIcon(); + void LoadProcessData(const std::shared_ptr process, const DeviceState &state); }; -} +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/os.cpp b/app/src/main/cpp/skyline/os.cpp index 944dc341..872cdbbd 100644 --- a/app/src/main/cpp/skyline/os.cpp +++ b/app/src/main/cpp/skyline/os.cpp @@ -1,18 +1,20 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) -#include "os.h" +#include "vfs/os_backing.h" #include "loader/nro.h" #include "nce/guest.h" +#include "os.h" namespace skyline::kernel { OS::OS(std::shared_ptr &jvmManager, std::shared_ptr &logger, std::shared_ptr &settings) : state(this, process, jvmManager, settings, logger), memory(state), serviceManager(state) {} - void OS::Execute(int romFd, TitleFormat romType) { + void OS::Execute(int romFd, loader::RomFormat romType) { std::shared_ptr loader; + auto romFile = std::make_shared(romFd); - if (romType == TitleFormat::NRO) { - loader = std::make_shared(romFd); + if (romType == loader::RomFormat::NRO) { + loader = std::make_shared(romFile); } else throw exception("Unsupported ROM extension."); diff --git a/app/src/main/cpp/skyline/os.h b/app/src/main/cpp/skyline/os.h index 298e1c0e..7983c660 100644 --- a/app/src/main/cpp/skyline/os.h +++ b/app/src/main/cpp/skyline/os.h @@ -6,6 +6,7 @@ #include #include #include "common.h" +#include "loader/loader.h" #include "kernel/ipc.h" #include "kernel/types/KProcess.h" #include "kernel/types/KThread.h" @@ -37,7 +38,7 @@ namespace skyline::kernel { * @param romFd A FD to the ROM file to execute * @param romType The type of the ROM file */ - void Execute(int romFd, TitleFormat romType); + void Execute(int romFd, loader::RomFormat romType); /** * @brief Creates a new process diff --git a/app/src/main/cpp/skyline/vfs/nacp.h b/app/src/main/cpp/skyline/vfs/nacp.h index e6297e1f..fe0db52e 100644 --- a/app/src/main/cpp/skyline/vfs/nacp.h +++ b/app/src/main/cpp/skyline/vfs/nacp.h @@ -32,6 +32,9 @@ namespace skyline::vfs { static_assert(sizeof(NacpData) == 0x4000); public: + /** + * @param backing The backing for the NACP + */ NACP(const std::shared_ptr &backing); std::string applicationName; //!< The name of the application in the currently selected language