diff --git a/app/src/main/cpp/skyline/common/utils.h b/app/src/main/cpp/skyline/common/utils.h index 43b31aba..c187d672 100644 --- a/app/src/main/cpp/skyline/common/utils.h +++ b/app/src/main/cpp/skyline/common/utils.h @@ -264,6 +264,12 @@ namespace skyline::util { FillRandomBytes(std::span(reinterpret_cast::Type *>(&object), IntegerFor::Count)); } + template + T RandomNumber(T min, T max) { + std::uniform_int_distribution dist(PointerValue(min), PointerValue(max)); + return ValuePointer(dist(detail::generator)); + } + /** * @brief A temporary shim for C++ 20's bit_cast to make transitioning to it easier */ @@ -353,4 +359,8 @@ namespace skyline::util { std::array MergeInto(TSrcs &&... srcs) { return MergeInto(std::make_index_sequence(), std::forward(srcs)...); } + + inline std::string HexDump(std::span data) { + return std::accumulate(data.begin(), data.end(), std::string{}, [](std::string str, u8 el) { return std::move(str) + fmt::format("{:02X}", el); }); + } } diff --git a/app/src/main/cpp/skyline/loader/nro.h b/app/src/main/cpp/skyline/loader/nro.h index a3a431dc..72616890 100644 --- a/app/src/main/cpp/skyline/loader/nro.h +++ b/app/src/main/cpp/skyline/loader/nro.h @@ -6,40 +6,42 @@ #include "loader.h" namespace skyline::loader { + struct NroSegmentHeader { + u32 offset; + u32 size; + }; + + struct NroHeader { + u32 _pad0_; + u32 modOffset; //!< The offset of the MOD metadata + 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 .rodata segment header + NroSegmentHeader data; //!< The .data segment header + + u32 bssSize; //!< The size of the bss segment + u32 _pad2_; + std::array buildId; //!< The build ID of the NRO + u64 _pad3_; + + NroSegmentHeader apiInfo; //!< The .apiInfo segment header + NroSegmentHeader dynstr; //!< The .dynstr segment header + NroSegmentHeader dynsym; //!< The .dynsym segment header + }; + /** * @brief The NroLoader class abstracts access to an NRO file through the Loader interface * @url https://switchbrew.org/wiki/NRO */ class NroLoader : public Loader { private: - struct NroSegmentHeader { - u32 offset; - u32 size; - }; - - struct NroHeader { - u32 _pad0_; - u32 modOffset; //!< The offset of the MOD metadata - 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 .rodata segment header - NroSegmentHeader data; //!< The .data segment header - - u32 bssSize; //!< The size of the bss segment - u32 _pad2_; - std::array buildId; //!< The build ID of the NRO - u64 _pad3_; - - NroSegmentHeader apiInfo; //!< The .apiInfo segment header - NroSegmentHeader dynstr; //!< The .dynstr segment header - NroSegmentHeader dynsym; //!< The .dynsym segment header - } header{}; + NroHeader header{}; struct NroAssetSection { u64 offset; diff --git a/app/src/main/cpp/skyline/services/ro/IRoInterface.cpp b/app/src/main/cpp/skyline/services/ro/IRoInterface.cpp index 2badc12d..a981e0e8 100644 --- a/app/src/main/cpp/skyline/services/ro/IRoInterface.cpp +++ b/app/src/main/cpp/skyline/services/ro/IRoInterface.cpp @@ -1,8 +1,110 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include +#include +#include +#include #include "IRoInterface.h" namespace skyline::service::ro { IRoInterface::IRoInterface(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {} + + Result IRoInterface::LoadModule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + u64 pid{request.Pop()}; + u64 nroAddress{request.Pop()}; + u64 nroSize{request.Pop()}; + u64 bssAddress{request.Pop()}; + u64 bssSize{request.Pop()}; + + if (!util::IsPageAligned(nroAddress) || !util::IsPageAligned(bssAddress)) + return result::InvalidAddress; + + if (!util::IsPageAligned(nroSize) || !nroSize || !util::IsPageAligned(bssSize)) + return result::InvalidSize; + + auto data{span{reinterpret_cast(nroAddress), nroSize}}; + auto &header{data.as()}; + + std::array hash{}; + mbedtls_sha256_ret(data.data(), data.size(), hash.data(), 0); + + if (!loadedNros.emplace(hash).second) + return result::AlreadyLoaded; + + // We don't handle NRRs here since they're purely used for signature verification which we will never do + if (bssSize != header.bssSize) + return result::InvalidNro; + + loader::Executable executable{}; + + executable.text.offset = 0; + executable.text.contents.resize(header.text.size); + span(executable.text.contents).copy_from(data.subspan(header.text.offset, header.text.size)); + + executable.ro.offset = header.text.size; + executable.ro.contents.resize(header.ro.size); + span(executable.ro.contents).copy_from(data.subspan(header.ro.offset, header.ro.size)); + + executable.data.offset = header.text.size + header.ro.size; + executable.data.contents.resize(header.data.size); + span(executable.data.contents).copy_from(data.subspan(header.data.offset, header.data.size)); + + executable.bssSize = header.bssSize; + + if (header.dynsym.offset > header.ro.offset && header.dynsym.offset + header.dynsym.size < header.ro.offset + header.ro.size && header.dynstr.offset > header.ro.offset && header.dynstr.offset + header.dynstr.size < header.ro.offset + header.ro.size) { + executable.dynsym = {header.dynsym.offset, header.dynsym.size}; + executable.dynstr = {header.dynstr.offset, header.dynstr.size}; + } + + u64 textSize{executable.text.contents.size()}; + u64 roSize{executable.ro.contents.size()}; + u64 dataSize{executable.data.contents.size() + executable.bssSize}; + + auto patch{state.nce->GetPatchData(executable.text.contents)}; + auto size{patch.size + textSize + roSize + dataSize}; + + u8 *ptr{}; + do { + ptr = util::AlignDown(util::RandomNumber(state.process->memory.base.data(), std::prev(state.process->memory.base.end()).base()), constant::PageSize) - size; + + if (state.process->memory.heap.contains(ptr) || state.process->memory.alias.contains(ptr)) + continue; + + auto desc{state.process->memory.Get(ptr)}; + if (!desc || desc->state != memory::states::Unmapped || (static_cast(ptr - desc->ptr) + size) < desc->size) + continue; + } while (!ptr); + + auto loadInfo{state.loader->LoadExecutable(state.process, state, executable, static_cast(ptr - state.process->memory.base.data()), util::HexDump(hash) + ".nro")}; + + response.Push(loadInfo.entry); + return {}; + } + + Result IRoInterface::UnloadModule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + Logger::Error("Module unloading is unimplemented!"); + return {}; + + } + + Result IRoInterface::RegisterModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return {}; + + } + + Result IRoInterface::UnregisterModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return {}; + + } + + Result IRoInterface::RegisterProcessHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return {}; + + } + + Result IRoInterface::RegisterProcessModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return {}; + + } } diff --git a/app/src/main/cpp/skyline/services/ro/IRoInterface.h b/app/src/main/cpp/skyline/services/ro/IRoInterface.h index 76618178..6fa586ad 100644 --- a/app/src/main/cpp/skyline/services/ro/IRoInterface.h +++ b/app/src/main/cpp/skyline/services/ro/IRoInterface.h @@ -3,15 +3,73 @@ #pragma once +#include #include namespace skyline::service::ro { + namespace result { + constexpr Result AlreadyLoaded{22, 3}; + constexpr Result InvalidNro{22, 4}; + constexpr Result InvalidNrr{22, 6}; + constexpr Result InvalidAddress{22, 1025}; + constexpr Result InvalidSize{22, 1026}; + } + /** * @brief IRoInterface or ldr:ro is used by applications to dynamically load nros * @url https://switchbrew.org/wiki/RO_services#LoadModule */ class IRoInterface : public BaseService { + private: + enum class NrrKind : u8 { + User = 0, + JitPlugin = 1 + }; + + std::unordered_set, util::ObjectHash>> loadedNros{}; + + Result RegisterModuleInfoImpl(u64 nrrAddress, u64 nrrSize, NrrKind nrrKind); + public: IRoInterface(const DeviceState &state, ServiceManager &manager); + + /** + * @url https://switchbrew.org/wiki/RO_services#LoadModule + */ + Result LoadModule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @url https://switchbrew.org/wiki/RO_services#UnloadModule + */ + Result UnloadModule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @url https://switchbrew.org/wiki/RO_services#RegisterModuleInfo + */ + Result RegisterModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @url https://switchbrew.org/wiki/RO_services#UnregisterModuleInfo + */ + Result UnregisterModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @url https://switchbrew.org/wiki/RO_services#Initialize + */ + Result RegisterProcessHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @url https://switchbrew.org/wiki/RO_services#RegisterProcessModuleInfo + */ + Result RegisterProcessModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + SERVICE_DECL( + SFUNC(0x0, IRoInterface, LoadModule), + SFUNC(0x1, IRoInterface, UnloadModule), + SFUNC(0x2, IRoInterface, RegisterModuleInfo), + SFUNC(0x3, IRoInterface, UnregisterModuleInfo), + SFUNC(0x4, IRoInterface, RegisterProcessHandle), + SFUNC(0xA, IRoInterface, RegisterProcessModuleInfo) + ) }; }