mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-29 23:54:16 +01:00
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.
This commit is contained in:
parent
4950dd5638
commit
dca06f2b49
@ -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 {
|
namespace util {
|
||||||
/**
|
/**
|
||||||
* @brief Returns the current time in nanoseconds
|
* @brief Returns the current time in nanoseconds
|
||||||
|
@ -3,35 +3,46 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <vfs/backing.h>
|
||||||
#include <os.h>
|
#include <vfs/nacp.h>
|
||||||
#include <kernel/types/KProcess.h>
|
|
||||||
|
|
||||||
namespace skyline::loader {
|
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 {
|
class Loader {
|
||||||
protected:
|
protected:
|
||||||
int fd; //!< An FD to the ROM file
|
std::shared_ptr<vfs::Backing> backing; //!< The backing of the loader
|
||||||
|
|
||||||
/**
|
|
||||||
* @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<typename T>
|
|
||||||
inline void ReadOffset(T *output, u64 offset, size_t size) {
|
|
||||||
pread64(fd, output, size, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
std::shared_ptr<vfs::NACP> nacp; //!< The NACP of the current application
|
||||||
* @param filePath The path to the ROM file
|
|
||||||
*/
|
|
||||||
Loader(int fd) : fd(fd) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This loads in the data of the main process
|
* @param backing The backing for the NRO
|
||||||
|
*/
|
||||||
|
Loader(const std::shared_ptr<vfs::Backing> &backing) : backing(backing) {}
|
||||||
|
|
||||||
|
virtual ~Loader() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The icon of the loaded application
|
||||||
|
*/
|
||||||
|
virtual std::vector<u8> GetIcon() {
|
||||||
|
return std::vector<u8>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This loads in the data of the main process
|
||||||
* @param process The process to load in the data
|
* @param process The process to load in the data
|
||||||
* @param state The state of the device
|
* @param state The state of the device
|
||||||
*/
|
*/
|
||||||
|
@ -1,38 +1,65 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
#include <vector>
|
#include <nce.h>
|
||||||
|
#include <os.h>
|
||||||
#include <kernel/memory.h>
|
#include <kernel/memory.h>
|
||||||
|
#include <vfs/nacp.h>
|
||||||
|
#include <vfs/region_backing.h>
|
||||||
#include "nro.h"
|
#include "nro.h"
|
||||||
|
|
||||||
namespace skyline::loader {
|
namespace skyline::loader {
|
||||||
NroLoader::NroLoader(int fd) : Loader(fd) {
|
NroLoader::NroLoader(const std::shared_ptr<vfs::Backing> &backing) : Loader(backing) {
|
||||||
ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader));
|
backing->Read(&header);
|
||||||
|
|
||||||
if (header.magic != util::MakeMagic<u32>("NRO0"))
|
if (header.magic != util::MakeMagic<u32>("NRO0"))
|
||||||
throw exception("Invalid NRO magic! 0x{0:X}", header.magic);
|
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<u32>("ASET"))
|
||||||
|
throw exception("Invalid ASET magic! 0x{0:X}", assetHeader.magic);
|
||||||
|
|
||||||
|
NroAssetSection &nacpHeader = assetHeader.nacp;
|
||||||
|
nacp = std::make_shared<vfs::NACP>(std::make_shared<vfs::RegionBacking>(backing, header.size + nacpHeader.offset, nacpHeader.size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> NroLoader::GetIcon() {
|
||||||
|
NroAssetSection &segmentHeader = assetHeader.icon;
|
||||||
|
std::vector<u8> buffer(segmentHeader.size);
|
||||||
|
|
||||||
|
backing->Read(buffer.data(), header.size + segmentHeader.offset, segmentHeader.size);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> NroLoader::GetSegment(NroSegmentType segment) {
|
||||||
|
NroSegmentHeader &segmentHeader = header.segments[static_cast<int>(segment)];
|
||||||
|
std::vector<u8> buffer(segmentHeader.size);
|
||||||
|
|
||||||
|
backing->Read(buffer.data(), segmentHeader.offset, segmentHeader.size);
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
|
void NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
|
||||||
std::vector<u8> text(header.text.size);
|
std::vector<u8> text = GetSegment(loader::NroLoader::NroSegmentType::Text);
|
||||||
std::vector<u8> rodata(header.ro.size);
|
std::vector<u8> rodata = GetSegment(loader::NroLoader::NroSegmentType::RO);
|
||||||
std::vector<u8> data(header.data.size);
|
std::vector<u8> data = GetSegment(loader::NroLoader::NroSegmentType::Data);
|
||||||
|
|
||||||
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<u32> patch = state.nce->PatchCode(text, constant::BaseAddress, header.text.size + header.ro.size + header.data.size + header.bssSize);
|
|
||||||
|
|
||||||
u64 textSize = text.size();
|
u64 textSize = text.size();
|
||||||
u64 rodataSize = rodata.size();
|
u64 rodataSize = rodata.size();
|
||||||
u64 dataSize = data.size();
|
u64 dataSize = data.size();
|
||||||
|
u64 bssSize = header.bssSize;
|
||||||
|
|
||||||
|
std::vector<u32> 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))
|
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);
|
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 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<kernel::type::KPrivateMemory>(constant::BaseAddress, textSize, memory::Permission{true, true, true}, memory::states::CodeStatic); // R-X
|
process->NewHandle<kernel::type::KPrivateMemory>(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);
|
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<kernel::type::KPrivateMemory>(constant::BaseAddress + textSize + rodataSize, dataSize, memory::Permission{true, true, false}, memory::states::CodeStatic); // RW-
|
process->NewHandle<kernel::type::KPrivateMemory>(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);
|
state.logger->Debug("Successfully mapped section .data @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize, dataSize);
|
||||||
|
|
||||||
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress + textSize + rodataSize + dataSize, header.bssSize, memory::Permission{true, true, true}, memory::states::CodeMutable); // RWX
|
process->NewHandle<kernel::type::KPrivateMemory>(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, header.bssSize);
|
state.logger->Debug("Successfully mapped section .bss @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize + dataSize, bssSize);
|
||||||
|
|
||||||
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress + textSize + rodataSize + dataSize + header.bssSize, patchSize + padding, memory::Permission{true, true, true}, memory::states::CodeStatic); // RWX
|
process->NewHandle<kernel::type::KPrivateMemory>(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 + header.bssSize, patchSize);
|
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(text.data(), constant::BaseAddress, textSize);
|
||||||
process->WriteMemory(rodata.data(), constant::BaseAddress + textSize, rodataSize);
|
process->WriteMemory(rodata.data(), constant::BaseAddress + textSize, rodataSize);
|
||||||
process->WriteMemory(data.data(), constant::BaseAddress + textSize + rodataSize, dataSize);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,10 +3,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <common.h>
|
||||||
#include "loader.h"
|
#include "loader.h"
|
||||||
|
|
||||||
namespace skyline::loader {
|
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 {
|
class NroLoader : public Loader {
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
@ -21,40 +24,67 @@ namespace skyline::loader {
|
|||||||
* @brief This holds the header of an NRO file
|
* @brief This holds the header of an NRO file
|
||||||
*/
|
*/
|
||||||
struct NroHeader {
|
struct NroHeader {
|
||||||
u32 : 32;
|
u32 _pad0_;
|
||||||
u32 modOffset; //!< The offset of the MOD metadata
|
u32 modOffset; //!< The offset of the MOD metadata
|
||||||
u64 : 64;
|
u64 _pad1_;
|
||||||
|
|
||||||
u32 magic; //!< The NRO magic "NRO0"
|
u32 magic; //!< The NRO magic "NRO0"
|
||||||
u32 version; //!< The version of the application
|
u32 version; //!< The version of the application
|
||||||
u32 size; //!< The size of the NRO
|
u32 size; //!< The size of the NRO
|
||||||
u32 flags; //!< The flags used with the NRO
|
u32 flags; //!< The flags used with the NRO
|
||||||
|
|
||||||
NroSegmentHeader text; //!< The .text segment header
|
NroSegmentHeader segments[3]; //!< The .text segment header
|
||||||
NroSegmentHeader ro; //!< The .ro segment header
|
|
||||||
NroSegmentHeader data; //!< The .data segment header
|
|
||||||
|
|
||||||
u32 bssSize; //!< The size of the bss segment
|
u32 bssSize; //!< The size of the bss segment
|
||||||
u32 : 32;
|
u32 _pad2_;
|
||||||
u64 buildId[4]; //!< The build ID of the NRO
|
u64 buildId[4]; //!< The build ID of the NRO
|
||||||
u64 : 64;
|
u64 _pad3_;
|
||||||
|
|
||||||
NroSegmentHeader apiInfo; //!< The .apiInfo segment header
|
NroSegmentHeader apiInfo; //!< The .apiInfo segment header
|
||||||
NroSegmentHeader dynstr; //!< The .dynstr segment header
|
NroSegmentHeader dynstr; //!< The .dynstr segment header
|
||||||
NroSegmentHeader dynsym; //!< The .dynsym segment header
|
NroSegmentHeader dynsym; //!< The .dynsym segment header
|
||||||
} 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
|
* @brief This holds various metadata about an NRO, it is only used by homebrew
|
||||||
* @param process The process to load in the data
|
|
||||||
* @param state The state of the device
|
|
||||||
*/
|
*/
|
||||||
|
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<u8> GetSegment(NroSegmentType segment);
|
||||||
|
|
||||||
|
public:
|
||||||
|
NroLoader(const std::shared_ptr<vfs::Backing> &backing);
|
||||||
|
|
||||||
|
std::vector<u8> GetIcon();
|
||||||
|
|
||||||
void LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
|
void LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -1,18 +1,20 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
#include "os.h"
|
#include "vfs/os_backing.h"
|
||||||
#include "loader/nro.h"
|
#include "loader/nro.h"
|
||||||
#include "nce/guest.h"
|
#include "nce/guest.h"
|
||||||
|
#include "os.h"
|
||||||
|
|
||||||
namespace skyline::kernel {
|
namespace skyline::kernel {
|
||||||
OS::OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, process, jvmManager, settings, logger), memory(state), serviceManager(state) {}
|
OS::OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &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::Loader> loader;
|
std::shared_ptr<loader::Loader> loader;
|
||||||
|
auto romFile = std::make_shared<vfs::OsBacking>(romFd);
|
||||||
|
|
||||||
if (romType == TitleFormat::NRO) {
|
if (romType == loader::RomFormat::NRO) {
|
||||||
loader = std::make_shared<loader::NroLoader>(romFd);
|
loader = std::make_shared<loader::NroLoader>(romFile);
|
||||||
} else
|
} else
|
||||||
throw exception("Unsupported ROM extension.");
|
throw exception("Unsupported ROM extension.");
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "loader/loader.h"
|
||||||
#include "kernel/ipc.h"
|
#include "kernel/ipc.h"
|
||||||
#include "kernel/types/KProcess.h"
|
#include "kernel/types/KProcess.h"
|
||||||
#include "kernel/types/KThread.h"
|
#include "kernel/types/KThread.h"
|
||||||
@ -37,7 +38,7 @@ namespace skyline::kernel {
|
|||||||
* @param romFd A FD to the ROM file to execute
|
* @param romFd A FD to the ROM file to execute
|
||||||
* @param romType The type of the ROM file
|
* @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
|
* @brief Creates a new process
|
||||||
|
@ -32,6 +32,9 @@ namespace skyline::vfs {
|
|||||||
static_assert(sizeof(NacpData) == 0x4000);
|
static_assert(sizeof(NacpData) == 0x4000);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @param backing The backing for the NACP
|
||||||
|
*/
|
||||||
NACP(const std::shared_ptr<vfs::Backing> &backing);
|
NACP(const std::shared_ptr<vfs::Backing> &backing);
|
||||||
|
|
||||||
std::string applicationName; //!< The name of the application in the currently selected language
|
std::string applicationName; //!< The name of the application in the currently selected language
|
||||||
|
Loading…
Reference in New Issue
Block a user