Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"

This reverts commit 15b7df8ef1.
This commit is contained in:
souldbminersmwc
2025-11-09 16:14:52 -05:00
parent 22ec140738
commit 21a3f953d7
3804 changed files with 435 additions and 570162 deletions

View File

@@ -1,62 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
#include "ldr_argument_store.hpp"
namespace ams::ldr {
int ArgumentStore::FindIndex(Entry *map, ncm::ProgramId program_id) {
for (auto i = 0; i < ArgumentMapCount; ++i) {
if (map[i].program_id == program_id) {
return i;
}
}
return -1;
}
const ArgumentStore::Entry *ArgumentStore::Get(ncm::ProgramId program_id) {
/* Find the matching arguments entry. */
Entry *argument = nullptr;
if (auto idx = FindIndex(m_argument_map, program_id); idx >= 0) {
argument = m_argument_map + idx;
}
return argument;
}
Result ArgumentStore::Set(ncm::ProgramId program_id, const void *argument, size_t size) {
/* Check that the argument size is within bounds. */
R_UNLESS(size < ArgumentBufferSize, ldr::ResultArgumentOverflow());
/* Find either a matching arguments entry, or an empty entry. */
auto idx = FindIndex(m_argument_map, program_id);
if (idx < 0) {
idx = FindIndex(m_argument_map, ncm::InvalidProgramId);
}
R_UNLESS(idx >= 0, ldr::ResultArgumentCountOverflow());
/* Set the arguments in the entry. */
auto &entry = m_argument_map[idx];
entry.program_id = program_id;
entry.argument_size = size;
std::memcpy(entry.argument, argument, entry.argument_size);
R_SUCCEED();
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
namespace ams::ldr {
class ArgumentStore {
public:
static constexpr size_t ArgumentBufferSize = 32_KB;
struct Entry {
ncm::ProgramId program_id;
size_t argument_size;
u8 argument[ArgumentBufferSize];
};
private:
static constexpr int ArgumentMapCount = 10;
private:
Entry m_argument_map[ArgumentMapCount];
public:
constexpr ArgumentStore() : m_argument_map{} {
this->Flush();
}
public:
const Entry *Get(ncm::ProgramId program_id);
Result Set(ncm::ProgramId program_id, const void *argument, size_t size);
constexpr Result Flush() {
for (auto &entry : m_argument_map) {
entry.program_id = ncm::InvalidProgramId;
}
R_SUCCEED();
}
private:
static int FindIndex(Entry *map, ncm::ProgramId program_id);
};
}

View File

@@ -1,453 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
#include "ldr_capabilities.hpp"
namespace ams::ldr {
namespace {
/* Types. */
enum class CapabilityId {
KernelFlags = 3,
SyscallMask = 4,
MapRange = 6,
MapPage = 7,
MapRegion = 10,
InterruptPair = 11,
ApplicationType = 13,
KernelVersion = 14,
HandleTable = 15,
DebugFlags = 16,
Empty = 32,
};
template<size_t Index, size_t Count, typename T = u32>
using CapabilityField = util::BitPack32::Field<Index, Count, T>;
#define DEFINE_CAPABILITY_FIELD(name, prev, ...) \
using name = CapabilityField<prev::Next, __VA_ARGS__>; \
constexpr ALWAYS_INLINE typename name::Type Get##name() const { return this->Get<name>(); }
constexpr ALWAYS_INLINE CapabilityId GetCapabilityId(util::BitPack32 cap) {
return static_cast<CapabilityId>(util::CountTrailingZeros<u32>(~cap.value));
}
constexpr inline util::BitPack32 EmptyCapability = {~u32{}};
static_assert(GetCapabilityId(EmptyCapability) == CapabilityId::Empty);
#define CAPABILITY_CLASS_NAME(id) Capability##id
#define DEFINE_CAPABILITY_CLASS(id, member_functions) \
class CAPABILITY_CLASS_NAME(id) { \
public: \
static constexpr CapabilityId Id = CapabilityId::id; \
using IdBits = CapabilityField<0, static_cast<size_t>(Id) + 1>; \
static constexpr u32 IdBitsValue = (static_cast<u32>(1) << static_cast<size_t>(Id)) - 1; \
private: \
util::BitPack32 m_value; \
private: \
template<typename FieldType> \
constexpr ALWAYS_INLINE typename FieldType::Type Get() const { return m_value.Get<FieldType>(); } \
template<typename FieldType> \
constexpr ALWAYS_INLINE void Set(typename FieldType::Type fv) { m_value.Set<FieldType>(fv); } \
constexpr ALWAYS_INLINE u32 GetValue() const { return m_value.value; } \
public: \
constexpr ALWAYS_INLINE CAPABILITY_CLASS_NAME(id)(util::BitPack32 v) : m_value{v} { /* ... */ } \
\
static constexpr CAPABILITY_CLASS_NAME(id) Decode(util::BitPack32 v) { return CAPABILITY_CLASS_NAME(id)(v); } \
\
member_functions \
}; \
static_assert(std::is_trivially_destructible<CAPABILITY_CLASS_NAME(id)>::value)
/* Class definitions. */
DEFINE_CAPABILITY_CLASS(KernelFlags,
DEFINE_CAPABILITY_FIELD(MaximumThreadPriority, IdBits, 6);
DEFINE_CAPABILITY_FIELD(MinimumThreadPriority, MaximumThreadPriority, 6);
DEFINE_CAPABILITY_FIELD(MinimumCoreId, MinimumThreadPriority, 8);
DEFINE_CAPABILITY_FIELD(MaximumCoreId, MinimumCoreId, 8);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
if (this->GetMinimumThreadPriority() < restriction.GetMinimumThreadPriority() ||
this->GetMaximumThreadPriority() > restriction.GetMaximumThreadPriority() ||
this->GetMinimumThreadPriority() > this->GetMaximumThreadPriority()) {
return false;
}
if (this->GetMinimumCoreId() < restriction.GetMinimumCoreId() ||
this->GetMaximumCoreId() > restriction.GetMaximumCoreId() ||
this->GetMinimumCoreId() > this->GetMaximumCoreId()) {
return false;
}
return true;
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(SyscallMask,
DEFINE_CAPABILITY_FIELD(Mask, IdBits, 24);
DEFINE_CAPABILITY_FIELD(Index, Mask, 3);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
if (this->GetIndex() == restriction.GetIndex() && this->GetMask() == restriction.GetMask()) {
return true;
}
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(MapRange,
DEFINE_CAPABILITY_FIELD(AddressSize, IdBits, 24);
DEFINE_CAPABILITY_FIELD(Flag, AddressSize, 1, bool);
static constexpr size_t SizeMax = 0x100000;
bool IsValid(const util::BitPack32 next_cap, const util::BitPack32 *kac, size_t kac_count) const {
if (GetCapabilityId(next_cap) != Id) {
return false;
}
const auto next = Decode(next_cap);
const u32 start = this->GetAddressSize();
const u32 size = next.GetAddressSize();
const u32 end = start + size;
if (size >= SizeMax) {
return false;
}
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i++]);
if (i >= kac_count || GetCapabilityId(kac[i]) != Id) {
return false;
}
const auto restriction_next = Decode(kac[i]);
const u32 restriction_start = restriction.GetAddressSize();
const u32 restriction_size = restriction_next.GetAddressSize();
const u32 restriction_end = restriction_start + restriction_size;
if (restriction_size >= SizeMax) {
continue;
}
if (this->GetFlag() == restriction.GetFlag() && next.GetFlag() == restriction_next.GetFlag()) {
if (restriction_start <= start && start <= restriction_end && end <= restriction_end) {
return true;
}
}
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(MapPage,
DEFINE_CAPABILITY_FIELD(Address, IdBits, 24);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
if (this->GetValue() == restriction.GetValue()) {
return true;
}
}
}
return false;
}
);
enum class MemoryRegionType : u32 {
NoMapping = 0,
KernelTraceBuffer = 1,
OnMemoryBootImage = 2,
DTB = 3,
};
DEFINE_CAPABILITY_CLASS(MapRegion,
DEFINE_CAPABILITY_FIELD(Region0, IdBits, 6, MemoryRegionType);
DEFINE_CAPABILITY_FIELD(ReadOnly0, Region0, 1, bool);
DEFINE_CAPABILITY_FIELD(Region1, ReadOnly0, 6, MemoryRegionType);
DEFINE_CAPABILITY_FIELD(ReadOnly1, Region1, 1, bool);
DEFINE_CAPABILITY_FIELD(Region2, ReadOnly1, 6, MemoryRegionType);
DEFINE_CAPABILITY_FIELD(ReadOnly2, Region2, 1, bool);
static bool IsValidRegionType(const util::BitPack32 *kac, size_t kac_count, MemoryRegionType region_type, bool is_read_only) {
if (region_type != MemoryRegionType::NoMapping) {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
if ((restriction.GetRegion0() == region_type && (is_read_only || !restriction.GetReadOnly0())) ||
(restriction.GetRegion1() == region_type && (is_read_only || !restriction.GetReadOnly1())) ||
(restriction.GetRegion2() == region_type && (is_read_only || !restriction.GetReadOnly2())))
{
return true;
}
}
}
return false;
} else {
return true;
}
}
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
return IsValidRegionType(kac, kac_count, this->GetRegion0(), this->GetReadOnly0()) &&
IsValidRegionType(kac, kac_count, this->GetRegion1(), this->GetReadOnly1()) &&
IsValidRegionType(kac, kac_count, this->GetRegion2(), this->GetReadOnly2());
}
);
DEFINE_CAPABILITY_CLASS(InterruptPair,
DEFINE_CAPABILITY_FIELD(InterruptId0, IdBits, 10);
DEFINE_CAPABILITY_FIELD(InterruptId1, InterruptId0, 10);
static constexpr u32 EmptyInterruptId = 0x3FF;
bool IsSingleIdValid(const u32 id, const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
if (restriction.GetInterruptId0() == EmptyInterruptId && restriction.GetInterruptId1() == EmptyInterruptId) {
return true;
}
if (restriction.GetInterruptId0() == id || restriction.GetInterruptId1() == id) {
return true;
}
}
}
return false;
}
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
return IsSingleIdValid(this->GetInterruptId0(), kac, kac_count) && IsSingleIdValid(this->GetInterruptId1(), kac, kac_count);
}
);
DEFINE_CAPABILITY_CLASS(ApplicationType,
DEFINE_CAPABILITY_FIELD(ApplicationType, IdBits, 3);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
return restriction.GetValue() == this->GetValue();
}
}
return false;
}
static constexpr util::BitPack32 Encode(u32 app_type) {
util::BitPack32 encoded{IdBitsValue};
encoded.Set<ApplicationType>(app_type);
return encoded;
}
);
DEFINE_CAPABILITY_CLASS(KernelVersion,
DEFINE_CAPABILITY_FIELD(MinorVersion, IdBits, 4);
DEFINE_CAPABILITY_FIELD(MajorVersion, MinorVersion, 13);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
return restriction.GetValue() == this->GetValue();
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(HandleTable,
DEFINE_CAPABILITY_FIELD(Size, IdBits, 10);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
return this->GetSize() <= restriction.GetSize();
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(DebugFlags,
DEFINE_CAPABILITY_FIELD(AllowDebug, IdBits, 1, bool);
DEFINE_CAPABILITY_FIELD(ForceDebugProd, AllowDebug, 1, bool);
DEFINE_CAPABILITY_FIELD(ForceDebug, ForceDebugProd, 1, bool);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
u32 total = 0;
if (this->GetAllowDebug()) { ++total; }
if (this->GetForceDebugProd()) { ++total; }
if (this->GetForceDebug()) { ++total; }
if (total > 1) {
return false;
}
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
return (restriction.GetValue() & this->GetValue()) == this->GetValue();
}
}
return false;
}
static constexpr util::BitPack32 Encode(bool allow_debug, bool force_debug_prod, bool force_debug) {
util::BitPack32 encoded{IdBitsValue};
encoded.Set<AllowDebug>(allow_debug);
encoded.Set<ForceDebugProd>(force_debug_prod);
encoded.Set<ForceDebug>(force_debug);
return encoded;
}
);
}
/* Capabilities API. */
Result TestCapability(const util::BitPack32 *kacd, size_t kacd_count, const util::BitPack32 *kac, size_t kac_count) {
for (size_t i = 0; i < kac_count; i++) {
const auto cap = kac[i];
const auto id = GetCapabilityId(cap);
#define VALIDATE_CASE(id) \
case CapabilityId::id: \
R_UNLESS(Capability##id::Decode(cap).IsValid(kacd, kacd_count), ldr::ResultInvalidCapability##id()); \
break
switch (id) {
VALIDATE_CASE(KernelFlags);
VALIDATE_CASE(SyscallMask);
VALIDATE_CASE(MapPage);
VALIDATE_CASE(MapRegion);
VALIDATE_CASE(InterruptPair);
VALIDATE_CASE(ApplicationType);
VALIDATE_CASE(KernelVersion);
VALIDATE_CASE(HandleTable);
VALIDATE_CASE(DebugFlags);
case CapabilityId::MapRange:
{
/* Map Range needs extra logic because there it involves two sequential caps. */
i++;
R_UNLESS(i < kac_count, ldr::ResultInvalidCapabilityMapRange());
R_UNLESS(CapabilityMapRange::Decode(cap).IsValid(kac[i], kacd, kacd_count), ldr::ResultInvalidCapabilityMapRange());
}
break;
default:
R_UNLESS(id == CapabilityId::Empty, ldr::ResultUnknownCapability());
break;
}
#undef VALIDATE_CASE
}
R_SUCCEED();
}
u16 MakeProgramInfoFlag(const util::BitPack32 *kac, size_t count) {
u16 flags = 0;
for (size_t i = 0; i < count; ++i) {
const auto cap = kac[i];
switch (GetCapabilityId(cap)) {
case CapabilityId::ApplicationType:
{
const auto app_type = CapabilityApplicationType::Decode(cap).GetApplicationType() & ProgramInfoFlag_ApplicationTypeMask;
if (app_type != ProgramInfoFlag_InvalidType) {
flags |= app_type;
}
}
break;
case CapabilityId::DebugFlags:
if (CapabilityDebugFlags::Decode(cap).GetAllowDebug()) {
flags |= ProgramInfoFlag_AllowDebug;
}
break;
default:
break;
}
}
return flags;
}
void UpdateProgramInfoFlag(u16 flags, util::BitPack32 *kac, size_t count) {
for (size_t i = 0; i < count; ++i) {
const auto cap = kac[i];
switch (GetCapabilityId(cap)) {
case CapabilityId::ApplicationType:
kac[i] = CapabilityApplicationType::Encode(flags & ProgramInfoFlag_ApplicationTypeMask);
break;
case CapabilityId::DebugFlags:
kac[i] = CapabilityDebugFlags::Encode((flags & ProgramInfoFlag_AllowDebug) != 0, CapabilityDebugFlags::Decode(cap).GetForceDebugProd(), CapabilityDebugFlags::Decode(cap).GetForceDebug());
break;
default:
break;
}
}
}
void FixDebugCapabilityForHbl(util::BitPack32 *kac, size_t count) {
for (size_t i = 0; i < count; ++i) {
const auto cap = kac[i];
switch (GetCapabilityId(cap)) {
case CapabilityId::DebugFlags:
/* 19.0.0+ disallows more than one flag set; we are always DebugMode for kernel, so ForceDebug is the most powerful/flexible flag to set. */
kac[i] = CapabilityDebugFlags::Encode(false, false, true);
break;
default:
break;
}
}
}
void PreProcessCapability(util::BitPack32 *kac, size_t count) {
for (size_t i = 0; i < count; ++i) {
const auto cap = kac[i];
switch (GetCapabilityId(cap)) {
/* NOTE: Currently, there is no pre-processing necessary. */
default:
break;
}
}
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
namespace ams::ldr {
Result TestCapability(const util::BitPack32 *kacd, size_t kacd_count, const util::BitPack32 *kac, size_t kac_count);
u16 MakeProgramInfoFlag(const util::BitPack32 *kac, size_t count);
void UpdateProgramInfoFlag(u16 flags, util::BitPack32 *kac, size_t count);
void FixDebugCapabilityForHbl(util::BitPack32 *kac, size_t count);
void PreProcessCapability(util::BitPack32 *kac, size_t count);
}

View File

@@ -1,169 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
#include "ldr_content_management.hpp"
namespace ams::ldr {
namespace {
constinit os::SdkMutex g_scoped_code_mount_lock;
}
/* ScopedCodeMount functionality. */
ScopedCodeMount::ScopedCodeMount(const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs) : m_lk(g_scoped_code_mount_lock), m_has_status(false), m_mounted_ams(false), m_mounted_sd_or_code(false), m_mounted_code(false) {
m_result = this->Initialize(loc, attrs);
}
ScopedCodeMount::ScopedCodeMount(const ncm::ProgramLocation &loc, const cfg::OverrideStatus &o, const ldr::ProgramAttributes &attrs) : m_lk(g_scoped_code_mount_lock), m_override_status(o), m_has_status(true), m_mounted_ams(false), m_mounted_sd_or_code(false), m_mounted_code(false) {
m_result = this->Initialize(loc, attrs);
}
ScopedCodeMount::~ScopedCodeMount() {
/* Unmount filesystems. */
if (m_mounted_ams) {
fs::Unmount(AtmosphereCodeMountName);
}
if (m_mounted_sd_or_code) {
fs::Unmount(SdOrCodeMountName);
}
if (m_mounted_code) {
fs::Unmount(CodeMountName);
}
}
Result ScopedCodeMount::Initialize(const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs) {
/* Capture override status, if necessary. */
this->EnsureOverrideStatus(loc);
AMS_ABORT_UNLESS(m_has_status);
/* Get the content path. */
char content_path[fs::EntryNameLengthMax + 1];
R_TRY(GetProgramPath(content_path, sizeof(content_path), loc, attrs));
/* Get the content attributes. */
const auto content_attributes = attrs.content_attributes;
/* Mount the atmosphere code file system. */
R_TRY(fs::MountCodeForAtmosphereWithRedirection(std::addressof(m_ams_code_verification_data), AtmosphereCodeMountName, content_path, content_attributes, loc.program_id, static_cast<ncm::StorageId>(loc.storage_id), m_override_status.IsHbl(), m_override_status.IsProgramSpecific()));
m_mounted_ams = true;
/* Mount the sd or base code file system. */
R_TRY(fs::MountCodeForAtmosphere(std::addressof(m_sd_or_base_code_verification_data), SdOrCodeMountName, content_path, content_attributes, loc.program_id, static_cast<ncm::StorageId>(loc.storage_id)));
m_mounted_sd_or_code = true;
/* Mount the base code file system. */
if (R_SUCCEEDED(fs::MountCode(std::addressof(m_base_code_verification_data), CodeMountName, content_path, content_attributes, loc.program_id, static_cast<ncm::StorageId>(loc.storage_id)))) {
m_mounted_code = true;
}
R_SUCCEED();
}
void ScopedCodeMount::EnsureOverrideStatus(const ncm::ProgramLocation &loc) {
if (m_has_status) {
return;
}
m_override_status = cfg::CaptureOverrideStatus(loc.program_id);
m_has_status = true;
}
/* Redirection API. */
Result GetProgramPath(char *out_path, size_t out_size, const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs) {
/* Check for storage id none. */
if (static_cast<ncm::StorageId>(loc.storage_id) == ncm::StorageId::None) {
std::memset(out_path, 0, out_size);
std::memcpy(out_path, "/", std::min<size_t>(out_size, 2));
R_SUCCEED();
}
/* Get the content attributes. */
const auto content_attributes = attrs.content_attributes;
AMS_UNUSED(content_attributes);
lr::Path path;
/* Check that path registration is allowable. */
if (static_cast<ncm::StorageId>(loc.storage_id) == ncm::StorageId::Host) {
AMS_ABORT_UNLESS(spl::IsDevelopment());
}
/* Try to get the path from the registered resolver. */
lr::RegisteredLocationResolver reg;
R_TRY(lr::OpenRegisteredLocationResolver(std::addressof(reg)));
R_TRY_CATCH(reg.ResolveProgramPath(std::addressof(path), loc.program_id)) {
R_CATCH(lr::ResultProgramNotFound) {
/* Program wasn't found via registered resolver, fall back to the normal resolver. */
lr::LocationResolver lr;
R_TRY(lr::OpenLocationResolver(std::addressof(lr), static_cast<ncm::StorageId>(loc.storage_id)));
R_TRY(lr.ResolveProgramPath(std::addressof(path), loc.program_id));
}
} R_END_TRY_CATCH;
/* Fix directory separators in path. */
fs::Replace(path.str, sizeof(path.str), fs::StringTraits::AlternateDirectorySeparator, fs::StringTraits::DirectorySeparator);
/* Check that the path is valid. */
AMS_ABORT_UNLESS(path.IsValid());
/* Copy the output path. */
std::memset(out_path, 0, out_size);
std::memcpy(out_path, path.str, std::min(out_size, sizeof(path)));
R_SUCCEED();
}
Result RedirectProgramPath(const char *path, size_t size, const ncm::ProgramLocation &loc) {
/* Check for storage id none. */
if (static_cast<ncm::StorageId>(loc.storage_id) == ncm::StorageId::None) {
R_SUCCEED();
}
/* Open location resolver. */
lr::LocationResolver lr;
R_TRY(lr::OpenLocationResolver(std::addressof(lr), static_cast<ncm::StorageId>(loc.storage_id)));
/* Copy in path. */
lr::Path lr_path;
std::memcpy(lr_path.str, path, std::min(size, sizeof(lr_path.str)));
lr_path.str[sizeof(lr_path.str) - 1] = '\x00';
/* Redirect the path. */
lr.RedirectProgramPath(lr_path, loc.program_id);
R_SUCCEED();
}
Result RedirectHtmlDocumentPathForHbl(const ncm::ProgramLocation &loc) {
lr::Path path;
/* Open a location resolver. */
lr::LocationResolver lr;
R_TRY(lr::OpenLocationResolver(std::addressof(lr), static_cast<ncm::StorageId>(loc.storage_id)));
/* If there's already a Html Document path, we don't need to set one. */
R_SUCCEED_IF(R_SUCCEEDED(lr.ResolveApplicationHtmlDocumentPath(std::addressof(path), loc.program_id)));
/* We just need to set this to any valid NCA path. Let's use the executable path. */
R_TRY(lr.ResolveProgramPath(std::addressof(path), loc.program_id));
lr.RedirectApplicationHtmlDocumentPath(path, loc.program_id, loc.program_id);
R_SUCCEED();
}
}

View File

@@ -1,83 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
namespace ams::ldr {
/* Utility reference to make code mounting automatic. */
class ScopedCodeMount {
NON_COPYABLE(ScopedCodeMount);
NON_MOVEABLE(ScopedCodeMount);
private:
std::scoped_lock<os::SdkMutex> m_lk;
cfg::OverrideStatus m_override_status;
fs::CodeVerificationData m_ams_code_verification_data;
fs::CodeVerificationData m_sd_or_base_code_verification_data;
fs::CodeVerificationData m_base_code_verification_data;
Result m_result;
bool m_has_status;
bool m_mounted_ams;
bool m_mounted_sd_or_code;
bool m_mounted_code;
public:
ScopedCodeMount(const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs);
ScopedCodeMount(const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const ldr::ProgramAttributes &attrs);
~ScopedCodeMount();
Result GetResult() const {
return m_result;
}
const cfg::OverrideStatus &GetOverrideStatus() const {
AMS_ABORT_UNLESS(m_has_status);
return m_override_status;
}
const fs::CodeVerificationData &GetAtmosphereCodeVerificationData() const {
return m_ams_code_verification_data;
}
const fs::CodeVerificationData &GetSdOrBaseCodeVerificationData() const {
return m_sd_or_base_code_verification_data;
}
const fs::CodeVerificationData &GetCodeVerificationData() const {
return m_base_code_verification_data;
}
private:
Result Initialize(const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs);
void EnsureOverrideStatus(const ncm::ProgramLocation &loc);
};
constexpr inline const char * const AtmosphereCodeMountName = "ams-code";
constexpr inline const char * const AtmosphereCompatMountName = "ams-cmpt";
constexpr inline const char * const SdOrCodeMountName = "sd-code";
constexpr inline const char * const CodeMountName = "code";
constexpr inline const char * const CompatMountName = "cmpt";
#define ENCODE_ATMOSPHERE_CODE_PATH(relative) "ams-code:" relative
#define ENCODE_ATMOSPHERE_CMPT_PATH(relative) "ams-cmpt:" relative
#define ENCODE_SD_OR_CODE_PATH(relative) "sd-code:" relative
#define ENCODE_CODE_PATH(relative) "code:" relative
#define ENCODE_CMPT_PATH(relative) "cmpt:" relative
/* Redirection API. */
Result GetProgramPath(char *out_path, size_t out_size, const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs);
Result RedirectProgramPath(const char *path, size_t size, const ncm::ProgramLocation &loc);
Result RedirectHtmlDocumentPathForHbl(const ncm::ProgramLocation &loc);
}

View File

@@ -1,64 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
#include "ldr_launch_record.hpp"
namespace ams::ldr {
namespace {
bool g_development_for_acid_production_check = false;
bool g_development_for_anti_downgrade_check = false;
bool g_development_for_acid_signature_check = false;
bool g_enabled_program_verification = true;
}
void SetDevelopmentForAcidProductionCheck(bool development) {
g_development_for_acid_production_check = development;
}
void SetDevelopmentForAntiDowngradeCheck(bool development) {
g_development_for_anti_downgrade_check = development;
}
void SetDevelopmentForAcidSignatureCheck(bool development) {
g_development_for_acid_signature_check = development;
}
void SetEnabledProgramVerification(bool enabled) {
if (g_development_for_acid_signature_check) {
g_enabled_program_verification = enabled;
}
}
bool IsDevelopmentForAcidProductionCheck() {
return g_development_for_acid_production_check;
}
bool IsDevelopmentForAntiDowngradeCheck() {
return g_development_for_anti_downgrade_check;
}
bool IsDevelopmentForAcidSignatureCheck() {
return g_development_for_acid_signature_check;
}
bool IsEnabledProgramVerification() {
return g_enabled_program_verification;
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
namespace ams::ldr {
/* Development Manager API. */
void SetDevelopmentForAcidProductionCheck(bool development);
void SetDevelopmentForAntiDowngradeCheck(bool development);
void SetDevelopmentForAcidSignatureCheck(bool development);
void SetEnabledProgramVerification(bool enabled);
bool IsDevelopmentForAcidProductionCheck();
bool IsDevelopmentForAntiDowngradeCheck();
bool IsDevelopmentForAcidSignatureCheck();
bool IsEnabledProgramVerification();
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright (c) 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/>.
*/
/* Patch fallback case to mov w0, #1 rather than retrieving settings flag. */
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_9_0_0[] = {
{ 0x521C, "\x20\x00\x80\x52", 4 },
};
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_10_0_0[] = {
{ 0x5494, "\x20\x00\x80\x52", 4 },
};
/* Patch getter functions to return 1. */
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_11_0_0[] = {
{ 0x85DC, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
{ 0x866C, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
};
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_12_0_0[] = {
{ 0x840C, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
{ 0x849C, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
};
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_13_0_0[] = {
{ 0x8424, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
{ 0x84B4, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
};
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_14_0_0[] = {
{ 0x7414, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
{ 0x74A4, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
};
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_15_0_0[] = {
{ 0x7230, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
{ 0x72AC, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
};
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_16_0_0[] = {
{ 0x718C, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
{ 0x7208, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
};
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_17_0_0[] = {
{ 0x7170, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
{ 0x71EC, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
};
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_18_0_0[] = {
{ 0x6DCC, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
{ 0x6E48, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
};
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_19_0_0[] = {
{ 0x6D90, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
{ 0x6E10, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
};
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_20_0_0[] = {
{ 0x7010, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
{ 0x7090, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
};
constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_20_1_0[] = {
{ 0x7010, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
{ 0x7090, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 },
};
constexpr inline const EmbeddedPatch Usb30ForceEnablePatches[] = {
{ ParseModuleId("C0D3F4E87E8B0FE9BBE9F1968A20767F3DC08E03"), util::size(Usb30ForceEnablePatches_9_0_0), Usb30ForceEnablePatches_9_0_0 },
{ ParseModuleId("B9C700CA8335F8BAA0D2041D8D09F772890BA988"), util::size(Usb30ForceEnablePatches_10_0_0), Usb30ForceEnablePatches_10_0_0 },
{ ParseModuleId("95BAF06A69650C215A5DD50CF8BD2A603E7AD3C2"), util::size(Usb30ForceEnablePatches_11_0_0), Usb30ForceEnablePatches_11_0_0 },
{ ParseModuleId("F3F88D90EF6B3B6358EDEBAF87D56FA88D3A081C"), util::size(Usb30ForceEnablePatches_12_0_0), Usb30ForceEnablePatches_12_0_0 }, /* 12.0.0 - 12.0.3 */
{ ParseModuleId("A109046278AA99353C20EC38B57C495B74A06D91"), util::size(Usb30ForceEnablePatches_12_0_0), Usb30ForceEnablePatches_12_0_0 }, /* 12.1.0 */
{ ParseModuleId("69384BF4A543C62C4342C94E5E2CED3C5A675251"), util::size(Usb30ForceEnablePatches_13_0_0), Usb30ForceEnablePatches_13_0_0 }, /* 13.0.0 - 13.1.0 */
{ ParseModuleId("1C97AFF30D48AFFEB74B28A530D30ABA0ABB9FFF"), util::size(Usb30ForceEnablePatches_14_0_0), Usb30ForceEnablePatches_14_0_0 }, /* 14.0.0 */
{ ParseModuleId("30B15A83E94D91750E7470795414AD1AE9C6A8DB"), util::size(Usb30ForceEnablePatches_15_0_0), Usb30ForceEnablePatches_15_0_0 }, /* 15.0.0 */
{ ParseModuleId("225865A442B4B66E8BD14B3E9450B901BDF29A40"), util::size(Usb30ForceEnablePatches_16_0_0), Usb30ForceEnablePatches_16_0_0 }, /* 16.0.0 */
{ ParseModuleId("70D4C2ABCD049F16B301186924367F813DA70248"), util::size(Usb30ForceEnablePatches_17_0_0), Usb30ForceEnablePatches_17_0_0 }, /* 17.0.0 */
{ ParseModuleId("4F21AE15E814FA46515C0401BB23D4F7ADCBF3F4"), util::size(Usb30ForceEnablePatches_18_0_0), Usb30ForceEnablePatches_18_0_0 }, /* 18.0.0 */
{ ParseModuleId("54BB9BB32C958E02752DC5E4AE8D016BFE1F5418"), util::size(Usb30ForceEnablePatches_19_0_0), Usb30ForceEnablePatches_19_0_0 }, /* 19.0.0 */
{ ParseModuleId("40E80E7442C0DFC985315E6F9E8C77229818AC0F"), util::size(Usb30ForceEnablePatches_20_0_0), Usb30ForceEnablePatches_20_0_0 }, /* 20.0.0 */
{ ParseModuleId("A5EF8D22EDF8A384E4135270ED596C1D2D524159"), util::size(Usb30ForceEnablePatches_20_1_0), Usb30ForceEnablePatches_20_1_0 }, /* 20.1.0 - 20.1.1 */
};

View File

@@ -1,80 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
#include "ldr_launch_record.hpp"
namespace ams::ldr {
namespace {
static constexpr size_t MaxBootPrograms = 0x50;
constinit std::array<ncm::ProgramId, MaxBootPrograms> g_launched_boot_programs = [] {
std::array<ncm::ProgramId, MaxBootPrograms> arr = {};
for (size_t i = 0; i < MaxBootPrograms; ++i) {
arr[i] = ncm::InvalidProgramId;
}
return arr;
}();
constinit bool g_boot_programs_done = false;
bool HasLaunchedBootProgramImpl(ncm::ProgramId program_id) {
for (const auto &launched : g_launched_boot_programs) {
if (launched == program_id) {
return true;
}
}
return false;
}
void SetLaunchedBootProgramImpl(ncm::ProgramId program_id) {
for (size_t i = 0; i < MaxBootPrograms; ++i) {
if (g_launched_boot_programs[i] == ncm::InvalidProgramId) {
g_launched_boot_programs[i] = program_id;
return;
}
}
AMS_ABORT("Too many boot programs");
}
}
/* Launch Record API. */
bool HasLaunchedBootProgram(ncm::ProgramId program_id) {
return HasLaunchedBootProgramImpl(program_id);
}
void SetLaunchedBootProgram(ncm::ProgramId program_id) {
if (!g_boot_programs_done) {
SetLaunchedBootProgramImpl(program_id);
if (program_id == ncm::SystemAppletId::Qlaunch) {
g_boot_programs_done = true;
}
}
}
}
/* Loader wants to override this libstratosphere function, which is weakly linked. */
/* This is necessary to prevent circular dependencies. */
namespace ams::pm::info {
Result HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id) {
*out = ldr::HasLaunchedBootProgram(program_id);
R_SUCCEED();
}
}

View File

@@ -1,25 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
namespace ams::ldr {
/* Launch Record API. */
bool HasLaunchedBootProgram(ncm::ProgramId program_id);
void SetLaunchedBootProgram(ncm::ProgramId program_id);
}

View File

@@ -1,138 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
#include "ldr_argument_store.hpp"
#include "ldr_content_management.hpp"
#include "ldr_development_manager.hpp"
#include "ldr_process_creation.hpp"
#include "ldr_launch_record.hpp"
#include "ldr_loader_service.hpp"
namespace ams::ldr {
namespace {
constinit ArgumentStore g_argument_store;
bool IsValidProgramAttributes(const ldr::ProgramAttributes &attrs) {
/* Check that the attributes are valid; on NX, this means Platform = NX + no content attrs. */
if (attrs.platform != ncm::ContentMetaPlatform::Nx) {
return false;
}
if (attrs.content_attributes != fs::ContentAttributes_None) {
return false;
}
return true;
}
}
Result LoaderService::CreateProcess(os::NativeHandle *out, PinId pin_id, u32 flags, os::NativeHandle resource_limit, const ProgramAttributes &attrs) {
/* Check that the program attributes are valid. */
R_UNLESS(IsValidProgramAttributes(attrs), ldr::ResultInvalidProgramAttributes());
/* Get the location and override status. */
ncm::ProgramLocation loc;
cfg::OverrideStatus override_status;
R_TRY(ldr::GetProgramLocationAndOverrideStatusFromPinId(std::addressof(loc), std::addressof(override_status), pin_id));
/* Get the program path. */
char path[fs::EntryNameLengthMax];
R_TRY(GetProgramPath(path, sizeof(path), loc, attrs));
path[sizeof(path) - 1] = '\x00';
/* Create the process. */
R_RETURN(ldr::CreateProcess(out, pin_id, loc, override_status, path, g_argument_store.Get(loc.program_id), flags, resource_limit, attrs));
}
Result LoaderService::GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const ProgramAttributes &attrs) {
/* Check that the program attributes are valid. */
R_UNLESS(IsValidProgramAttributes(attrs), ldr::ResultInvalidProgramAttributes());
/* Zero output. */
std::memset(out, 0, sizeof(*out));
/* Get the program path. */
char path[fs::EntryNameLengthMax];
R_TRY(GetProgramPath(path, sizeof(path), loc, attrs));
path[sizeof(path) - 1] = '\x00';
/* Get the program info. */
cfg::OverrideStatus status;
R_TRY(ldr::GetProgramInfo(out, std::addressof(status), loc, path, attrs));
if (loc.program_id != out->program_id) {
/* Redirect the program path. */
const ncm::ProgramLocation new_loc = ncm::ProgramLocation::Make(out->program_id, static_cast<ncm::StorageId>(loc.storage_id));
R_TRY(RedirectProgramPath(path, sizeof(path), new_loc));
/* Update the arguments, as needed. */
if (const auto *entry = g_argument_store.Get(loc.program_id); entry != nullptr) {
R_TRY(g_argument_store.Set(new_loc.program_id, entry->argument, entry->argument_size));
}
}
/* If we should, set the output status. */
if (out_status != nullptr) {
*out_status = status;
}
R_SUCCEED();
}
Result LoaderService::PinProgram(PinId *out, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status) {
*out = {};
R_RETURN(ldr::PinProgram(out, loc, status));
}
Result LoaderService::UnpinProgram(PinId id) {
R_RETURN(ldr::UnpinProgram(id));
}
Result LoaderService::SetProgramArgument(ncm::ProgramId program_id, const void *argument, size_t size) {
R_RETURN(g_argument_store.Set(program_id, argument, size));
}
Result LoaderService::FlushArguments() {
R_RETURN(g_argument_store.Flush());
}
Result LoaderService::GetProcessModuleInfo(u32 *out_count, ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) {
*out_count = 0;
std::memset(out, 0, max_out_count * sizeof(*out));
R_RETURN(ldr::GetProcessModuleInfo(out_count, out, max_out_count, process_id));
}
Result LoaderService::RegisterExternalCode(os::NativeHandle *out, ncm::ProgramId program_id) {
R_RETURN(fssystem::CreateExternalCode(out, program_id));
}
void LoaderService::UnregisterExternalCode(ncm::ProgramId program_id) {
fssystem::DestroyExternalCode(program_id);
}
void LoaderService::HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id) {
*out = ldr::HasLaunchedBootProgram(program_id);
}
Result LoaderService::SetEnabledProgramVerification(bool enabled) {
ldr::SetEnabledProgramVerification(enabled);
R_SUCCEED();
}
}

View File

@@ -1,99 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
namespace ams::ldr {
class LoaderService {
public:
/* Official commands. */
Result CreateProcess(sf::OutMoveHandle proc_h, PinId id, u32 flags, sf::CopyHandle &&reslimit_h, const ProgramAttributes &attrs) {
/* Create a handle to set the output to when done. */
os::NativeHandle handle = os::InvalidNativeHandle;
ON_SCOPE_EXIT { proc_h.SetValue(handle, true); };
R_RETURN(this->CreateProcess(std::addressof(handle), id, flags, reslimit_h.GetOsHandle(), attrs));
}
Result GetProgramInfo(sf::Out<ProgramInfo> out_program_info, const ncm::ProgramLocation &loc, const ProgramAttributes &attrs) {
R_RETURN(this->GetProgramInfo(out_program_info.GetPointer(), nullptr, loc, attrs));
}
Result PinProgram(sf::Out<PinId> out_id, const ncm::ProgramLocation &loc) {
R_RETURN(this->PinProgram(out_id.GetPointer(), loc, cfg::OverrideStatus{}));
}
Result UnpinProgram(PinId id);
Result SetProgramArgumentDeprecated(ncm::ProgramId program_id, const sf::InPointerBuffer &args, u32 args_size) {
AMS_UNUSED(args_size);
R_RETURN(this->SetProgramArgument(program_id, args.GetPointer(), std::min<size_t>(args_size, args.GetSize())));
}
Result SetProgramArgument(ncm::ProgramId program_id, const sf::InPointerBuffer &args) {
R_RETURN(this->SetProgramArgument(program_id, args.GetPointer(), args.GetSize()));
}
Result FlushArguments();
Result GetProcessModuleInfo(sf::Out<u32> count, const sf::OutPointerArray<ModuleInfo> &out, os::ProcessId process_id) {
R_UNLESS(out.GetSize() <= std::numeric_limits<s32>::max(), ldr::ResultInvalidSize());
R_RETURN(this->GetProcessModuleInfo(count.GetPointer(), out.GetPointer(), out.GetSize(), process_id));
}
Result SetEnabledProgramVerification(bool enabled);
/* Atmosphere commands. */
Result AtmosphereRegisterExternalCode(sf::OutMoveHandle out, ncm::ProgramId program_id) {
/* Create a handle to set the output to when done. */
os::NativeHandle handle = os::InvalidNativeHandle;
ON_SCOPE_EXIT { out.SetValue(handle, true); };
R_RETURN(this->RegisterExternalCode(std::addressof(handle), program_id));
}
void AtmosphereUnregisterExternalCode(ncm::ProgramId program_id) {
return this->UnregisterExternalCode(program_id);
}
void AtmosphereHasLaunchedBootProgram(sf::Out<bool> out, ncm::ProgramId program_id) {
return this->HasLaunchedBootProgram(out.GetPointer(), program_id);
}
Result AtmosphereGetProgramInfo(sf::Out<ProgramInfo> out_program_info, sf::Out<cfg::OverrideStatus> out_status, const ncm::ProgramLocation &loc, const ProgramAttributes &attrs) {
R_RETURN(this->GetProgramInfo(out_program_info.GetPointer(), out_status.GetPointer(), loc, attrs));
}
Result AtmospherePinProgram(sf::Out<PinId> out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status) {
R_RETURN(this->PinProgram(out_id.GetPointer(), loc, override_status));
}
private:
Result CreateProcess(os::NativeHandle *out, PinId pin_id, u32 flags, os::NativeHandle resource_limit, const ProgramAttributes &attrs);
Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const ProgramAttributes &attrs);
Result PinProgram(PinId *out, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status);
Result SetProgramArgument(ncm::ProgramId program_id, const void *argument, size_t size);
Result GetProcessModuleInfo(u32 *out_count, ModuleInfo *out, size_t max_out_count, os::ProcessId process_id);
Result RegisterExternalCode(os::NativeHandle *out, ncm::ProgramId program_id);
void UnregisterExternalCode(ncm::ProgramId program_id);
void HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id);
};
static_assert(ams::ldr::impl::IsIProcessManagerInterface<LoaderService>);
static_assert(ams::ldr::impl::IsIDebugMonitorInterface<LoaderService>);
static_assert(ams::ldr::impl::IsIShellInterface<LoaderService>);
}

View File

@@ -1,170 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
#include "ldr_development_manager.hpp"
#include "ldr_loader_service.hpp"
namespace ams {
namespace ldr {
namespace {
constinit u8 g_heap_memory[16_KB];
lmem::HeapHandle g_server_heap_handle;
constinit ams::sf::ExpHeapAllocator g_server_allocator;
void *Allocate(size_t size) {
return lmem::AllocateFromExpHeap(g_server_heap_handle, size);
}
void Deallocate(void *p, size_t size) {
AMS_UNUSED(size);
return lmem::FreeToExpHeap(g_server_heap_handle, p);
}
void InitializeHeap() {
g_server_heap_handle = lmem::CreateExpHeap(g_heap_memory, sizeof(g_heap_memory), lmem::CreateOption_None);
g_server_allocator.Attach(g_server_heap_handle);
}
}
namespace {
struct ServerOptions {
static constexpr size_t PointerBufferSize = 0x420;
static constexpr size_t MaxDomains = 0;
static constexpr size_t MaxDomainObjects = 0;
static constexpr bool CanDeferInvokeRequest = false;
static constexpr bool CanManageMitmServers = false;
};
/* ldr:pm, ldr:shel, ldr:dmnt. */
enum PortIndex {
PortIndex_ProcessManager,
PortIndex_Shell,
PortIndex_DebugMonitor,
PortIndex_Count,
};
constexpr sm::ServiceName ProcessManagerServiceName = sm::ServiceName::Encode("ldr:pm");
constexpr size_t ProcessManagerMaxSessions = 1;
constexpr sm::ServiceName ShellServiceName = sm::ServiceName::Encode("ldr:shel");
constexpr size_t ShellMaxSessions = 3;
constexpr sm::ServiceName DebugMonitorServiceName = sm::ServiceName::Encode("ldr:dmnt");
constexpr size_t DebugMonitorMaxSessions = 3;
constinit sf::UnmanagedServiceObject<impl::IProcessManagerInterface, LoaderService> g_pm_service;
constinit sf::UnmanagedServiceObject<impl::IShellInterface, LoaderService> g_shell_service;
constinit sf::UnmanagedServiceObject<impl::IDebugMonitorInterface, LoaderService> g_dmnt_service;
constexpr size_t MaxSessions = ProcessManagerMaxSessions + ShellMaxSessions + DebugMonitorMaxSessions + 1;
using ServerManager = ams::sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions>;
ServerManager g_server_manager;
void RegisterServiceSessions() {
R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_pm_service.GetShared(), ProcessManagerServiceName, ProcessManagerMaxSessions));
R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_shell_service.GetShared(), ShellServiceName, ShellMaxSessions));
R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_dmnt_service.GetShared(), DebugMonitorServiceName, DebugMonitorMaxSessions));
}
void LoopProcess() {
g_server_manager.LoopProcess();
}
}
}
namespace init {
void InitializeSystemModule() {
/* Initialize heap. */
ldr::InitializeHeap();
/* Set fs allocator. */
fs::SetAllocator(ldr::Allocate, ldr::Deallocate);
/* Initialize services we need. */
R_ABORT_UNLESS(sm::Initialize());
fs::InitializeForSystem();
lr::Initialize();
R_ABORT_UNLESS(fsldrInitialize());
spl::Initialize();
/* Verify that we can sanely execute. */
ams::CheckApiVersion();
}
void FinalizeSystemModule() { /* ... */ }
void Startup() { /* ... */ }
}
void NORETURN Exit(int rc) {
AMS_UNUSED(rc);
AMS_ABORT("Exit called by immortal process");
}
void Main() {
/* Disable auto-abort in fs operations. */
fs::SetEnabledAutoAbort(false);
/* Set thread name. */
os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(ldr, Main));
AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(ldr, Main));
/* Configure development. */
/* NOTE: Nintendo really does call the getter function three times instead of caching the value. */
ldr::SetDevelopmentForAcidProductionCheck(spl::IsDevelopment());
ldr::SetDevelopmentForAntiDowngradeCheck(spl::IsDevelopment());
ldr::SetDevelopmentForAcidSignatureCheck(spl::IsDevelopment());
/* Register the loader services. */
ldr::RegisterServiceSessions();
/* Loop forever, servicing our services. */
ldr::LoopProcess();
/* This can never be reached. */
AMS_ASSUME(false);
}
}
/* Override operator new. */
void *operator new(size_t size) {
return ams::ldr::Allocate(size);
}
void *operator new(size_t size, const std::nothrow_t &) {
return ams::ldr::Allocate(size);
}
void operator delete(void *p) {
return ams::ldr::Deallocate(p, 0);
}
void operator delete(void *p, size_t size) {
return ams::ldr::Deallocate(p, size);
}

View File

@@ -1,298 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
#include "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
#include "ldr_development_manager.hpp"
#include "ldr_meta.hpp"
namespace ams::ldr {
namespace {
/* Convenience definitions. */
constexpr size_t MetaCacheBufferSize = 0x8000;
constexpr inline const char AtmosphereMetaPath[] = ENCODE_ATMOSPHERE_CODE_PATH("/main.npdm");
constexpr inline const char SdOrBaseMetaPath[] = ENCODE_SD_OR_CODE_PATH("/main.npdm");
constexpr inline const char BaseMetaPath[] = ENCODE_CODE_PATH("/main.npdm");
/* Types. */
struct MetaCache {
Meta meta;
u8 buffer[MetaCacheBufferSize];
};
/* Global storage. */
ncm::ProgramId g_cached_program_id;
cfg::OverrideStatus g_cached_override_status;
MetaCache g_meta_cache;
MetaCache g_original_meta_cache;
/* Helpers. */
Result ValidateSubregion(size_t allowed_start, size_t allowed_end, size_t start, size_t size, size_t min_size = 0) {
R_UNLESS(size >= min_size, ldr::ResultInvalidMeta());
R_UNLESS(allowed_start <= start, ldr::ResultInvalidMeta());
R_UNLESS(start <= allowed_end, ldr::ResultInvalidMeta());
R_UNLESS(start + size <= allowed_end, ldr::ResultInvalidMeta());
R_SUCCEED();
}
Result ValidateNpdm(const Npdm *npdm, size_t size) {
/* Validate magic. */
R_UNLESS(npdm->magic == Npdm::Magic, ldr::ResultInvalidMeta());
/* Validate flags. */
constexpr u32 InvalidMetaFlagMask = 0x80000000;
R_UNLESS(!(npdm->flags & InvalidMetaFlagMask), ldr::ResultInvalidMeta());
/* Validate Acid extents. */
R_TRY(ValidateSubregion(sizeof(Npdm), size, npdm->acid_offset, npdm->acid_size, sizeof(Acid)));
/* Validate Aci extends. */
R_TRY(ValidateSubregion(sizeof(Npdm), size, npdm->aci_offset, npdm->aci_size, sizeof(Aci)));
R_SUCCEED();
}
Result ValidateAcid(const Acid *acid, size_t size) {
/* Validate magic. */
R_UNLESS(acid->magic == Acid::Magic, ldr::ResultInvalidMeta());
/* Validate that the acid is for production if not development. */
if (!IsDevelopmentForAcidProductionCheck()) {
R_UNLESS((acid->flags & Acid::AcidFlag_Production) != 0, ldr::ResultInvalidMeta());
}
/* Validate that the acid version is correct. */
constexpr u8 SupportedSdkMajorVersion = ams::svc::ConvertToSdkMajorVersion(ams::svc::SupportedKernelMajorVersion);
if (acid->unknown_209 < SupportedSdkMajorVersion) {
R_UNLESS(acid->version == 0, ldr::ResultInvalidMeta());
R_UNLESS(acid->unknown_209 == 0, ldr::ResultInvalidMeta());
}
/* Validate Fac, Sac, Kac. */
R_TRY(ValidateSubregion(sizeof(Acid), size, acid->fac_offset, acid->fac_size));
R_TRY(ValidateSubregion(sizeof(Acid), size, acid->sac_offset, acid->sac_size));
R_TRY(ValidateSubregion(sizeof(Acid), size, acid->kac_offset, acid->kac_size));
R_SUCCEED();
}
Result ValidateAci(const Aci *aci, size_t size) {
/* Validate magic. */
R_UNLESS(aci->magic == Aci::Magic, ldr::ResultInvalidMeta());
/* Validate Fah, Sac, Kac. */
R_TRY(ValidateSubregion(sizeof(Aci), size, aci->fah_offset, aci->fah_size));
R_TRY(ValidateSubregion(sizeof(Aci), size, aci->sac_offset, aci->sac_size));
R_TRY(ValidateSubregion(sizeof(Aci), size, aci->kac_offset, aci->kac_size));
R_SUCCEED();
}
const u8 *GetAcidSignatureModulus(ncm::ContentMetaPlatform platform, u8 key_generation, bool unk_unused) {
return fssystem::GetAcidSignatureKeyModulus(platform, !IsDevelopmentForAcidSignatureCheck(), key_generation, unk_unused);
}
size_t GetAcidSignatureModulusSize(ncm::ContentMetaPlatform platform, bool unk_unused) {
return fssystem::GetAcidSignatureKeyModulusSize(platform, unk_unused);
}
Result ValidateAcidSignature(Meta *meta, ncm::ContentMetaPlatform platform, bool unk_unused) {
/* Loader did not check signatures prior to 10.0.0. */
if (hos::GetVersion() < hos::Version_10_0_0) {
meta->check_verification_data = false;
R_SUCCEED();
}
/* Get the signature key generation. */
const auto signature_key_generation = meta->npdm->signature_key_generation;
R_UNLESS(fssystem::IsValidSignatureKeyGeneration(platform, signature_key_generation), ldr::ResultInvalidMeta());
/* Verify the signature. */
const u8 *sig = meta->acid->signature;
const size_t sig_size = sizeof(meta->acid->signature);
const u8 *mod = GetAcidSignatureModulus(platform, signature_key_generation, unk_unused);
const size_t mod_size = GetAcidSignatureModulusSize(platform, unk_unused);
const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent();
const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize;
const u8 *msg = meta->acid->modulus;
const size_t msg_size = meta->acid->size;
const bool is_signature_valid = crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size);
R_UNLESS(is_signature_valid || !IsEnabledProgramVerification(), ldr::ResultInvalidAcidSignature());
meta->check_verification_data = is_signature_valid;
R_SUCCEED();
}
Result LoadMetaFromFile(fs::FileHandle file, MetaCache *cache) {
/* Reset cache. */
cache->meta = {};
/* Read from file. */
s64 npdm_size = 0;
{
/* Get file size. */
R_TRY(fs::GetFileSize(std::addressof(npdm_size), file));
/* Read data into cache buffer. */
R_UNLESS(npdm_size <= static_cast<s64>(MetaCacheBufferSize), ldr::ResultMetaOverflow());
R_TRY(fs::ReadFile(file, 0, cache->buffer, npdm_size));
}
/* Ensure size is big enough. */
R_UNLESS(npdm_size >= static_cast<s64>(sizeof(Npdm)), ldr::ResultInvalidMeta());
/* Validate the meta. */
{
Meta *meta = std::addressof(cache->meta);
Npdm *npdm = reinterpret_cast<Npdm *>(cache->buffer);
R_TRY(ValidateNpdm(npdm, npdm_size));
Acid *acid = reinterpret_cast<Acid *>(cache->buffer + npdm->acid_offset);
Aci *aci = reinterpret_cast<Aci *>(cache->buffer + npdm->aci_offset);
R_TRY(ValidateAcid(acid, npdm->acid_size));
R_TRY(ValidateAci(aci, npdm->aci_size));
/* Set Meta members. */
meta->npdm = npdm;
meta->acid = acid;
meta->aci = aci;
meta->acid_fac = reinterpret_cast<u8 *>(acid) + acid->fac_offset;
meta->acid_sac = reinterpret_cast<u8 *>(acid) + acid->sac_offset;
meta->acid_kac = reinterpret_cast<u8 *>(acid) + acid->kac_offset;
meta->aci_fah = reinterpret_cast<u8 *>(aci) + aci->fah_offset;
meta->aci_sac = reinterpret_cast<u8 *>(aci) + aci->sac_offset;
meta->aci_kac = reinterpret_cast<u8 *>(aci) + aci->kac_offset;
meta->modulus = acid->modulus;
}
R_SUCCEED();
}
}
/* API. */
Result LoadMeta(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform, bool unk_unused) {
/* Set the cached program id back to zero. */
g_cached_program_id = {};
/* Try to load meta from file. */
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), AtmosphereMetaPath, fs::OpenMode_Read));
{
ON_SCOPE_EXIT { fs::CloseFile(file); };
R_TRY(LoadMetaFromFile(file, std::addressof(g_meta_cache)));
}
/* Patch meta. Start by setting all program ids to the current program id. */
Meta *meta = std::addressof(g_meta_cache.meta);
meta->acid->program_id_min = loc.program_id;
meta->acid->program_id_max = loc.program_id;
meta->aci->program_id = loc.program_id;
/* For HBL, we need to copy some information from the base meta. */
if (status.IsHbl()) {
if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), SdOrBaseMetaPath, fs::OpenMode_Read))) {
ON_SCOPE_EXIT { fs::CloseFile(file); };
if (R_SUCCEEDED(LoadMetaFromFile(file, std::addressof(g_original_meta_cache)))) {
Meta *o_meta = std::addressof(g_original_meta_cache.meta);
/* Fix pool partition. */
if (hos::GetVersion() >= hos::Version_5_0_0) {
meta->acid->flags = (meta->acid->flags & 0xFFFFFFC3) | (o_meta->acid->flags & 0x0000003C);
}
/* Fix flags. */
const u16 program_info_flags = MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(o_meta->aci_kac), o_meta->aci->kac_size / sizeof(util::BitPack32));
UpdateProgramInfoFlag(program_info_flags, static_cast<util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32));
UpdateProgramInfoFlag(program_info_flags, static_cast<util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
}
}
/* Perform address space override. */
if (status.HasOverrideAddressSpace()) {
/* Clear the existing address space. */
meta->npdm->flags &= ~Npdm::MetaFlag_AddressSpaceTypeMask;
/* Set the new address space flag. */
switch (status.GetOverrideAddressSpaceFlags()) {
case cfg::impl::OverrideStatusFlag_AddressSpace32Bit: meta->npdm->flags |= (Npdm::AddressSpaceType_32Bit) << Npdm::MetaFlag_AddressSpaceTypeShift; break;
case cfg::impl::OverrideStatusFlag_AddressSpace64BitDeprecated: meta->npdm->flags |= (Npdm::AddressSpaceType_64BitDeprecated) << Npdm::MetaFlag_AddressSpaceTypeShift; break;
case cfg::impl::OverrideStatusFlag_AddressSpace32BitWithoutAlias: meta->npdm->flags |= (Npdm::AddressSpaceType_32BitWithoutAlias) << Npdm::MetaFlag_AddressSpaceTypeShift; break;
case cfg::impl::OverrideStatusFlag_AddressSpace64Bit: meta->npdm->flags |= (Npdm::AddressSpaceType_64Bit) << Npdm::MetaFlag_AddressSpaceTypeShift; break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
/* When hbl is applet, adjust main thread priority. */
if ((MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet) {
constexpr auto HblMainThreadPriorityApplication = 44;
constexpr auto HblMainThreadPriorityApplet = 40;
if (meta->npdm->main_thread_priority == HblMainThreadPriorityApplication) {
meta->npdm->main_thread_priority = HblMainThreadPriorityApplet;
}
}
/* Fix the debug capabilities, to prevent needing a hbl recompilation. */
FixDebugCapabilityForHbl(static_cast<util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32));
FixDebugCapabilityForHbl(static_cast<util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
} else if (hos::GetVersion() >= hos::Version_10_0_0) {
/* If storage id is none, there is no base code filesystem, and thus it is impossible for us to validate. */
/* However, if we're an application, we are guaranteed a base code filesystem. */
if (static_cast<ncm::StorageId>(loc.storage_id) != ncm::StorageId::None || ncm::IsApplicationId(loc.program_id)) {
R_TRY(fs::OpenFile(std::addressof(file), BaseMetaPath, fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
R_TRY(LoadMetaFromFile(file, std::addressof(g_original_meta_cache)));
R_TRY(ValidateAcidSignature(std::addressof(g_original_meta_cache.meta), platform, unk_unused));
meta->modulus = g_original_meta_cache.meta.modulus;
meta->check_verification_data = g_original_meta_cache.meta.check_verification_data;
}
}
/* Pre-process the capabilities. */
/* This is used to e.g. avoid passing memory region descriptor to older kernels. */
PreProcessCapability(static_cast<util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32));
PreProcessCapability(static_cast<util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
/* Set output. */
g_cached_program_id = loc.program_id;
g_cached_override_status = status;
*out_meta = *meta;
R_SUCCEED();
}
Result LoadMetaFromCache(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform) {
if (g_cached_program_id != loc.program_id || g_cached_override_status != status) {
R_RETURN(LoadMeta(out_meta, loc, status, platform, false));
}
*out_meta = g_meta_cache.meta;
R_SUCCEED();
}
void InvalidateMetaCache() {
/* Set the cached program id back to zero. */
g_cached_program_id = {};
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
namespace ams::ldr {
struct Meta {
Npdm *npdm;
Acid *acid;
Aci *aci;
void *acid_fac;
void *acid_sac;
void *acid_kac;
void *aci_fah;
void *aci_sac;
void *aci_kac;
void *modulus;
bool check_verification_data;
};
/* Meta API. */
Result LoadMeta(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform, bool unk_unused);
Result LoadMetaFromCache(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform);
void InvalidateMetaCache();
}

View File

@@ -1,145 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
#include "ldr_patcher.hpp"
namespace ams::ldr {
namespace {
constexpr const char *NsoPatchesDirectory = "exefs_patches";
/* Exefs patches want to prevent modification of header, */
/* and also want to adjust offset relative to mapped location. */
constexpr size_t NsoPatchesProtectedSize = sizeof(NsoHeader);
constexpr size_t NsoPatchesProtectedOffset = sizeof(NsoHeader);
constexpr const char * const LoaderSdMountName = "#amsldr-sdpatch";
static_assert(sizeof(LoaderSdMountName) <= fs::MountNameLengthMax);
constinit os::SdkMutex g_ldr_sd_lock;
constinit bool g_mounted_sd;
constinit os::SdkMutex g_embedded_patch_lock;
constinit bool g_got_embedded_patch_settings;
constinit bool g_force_enable_usb30;
bool EnsureSdCardMounted() {
std::scoped_lock lk(g_ldr_sd_lock);
if (g_mounted_sd) {
return true;
}
if (!cfg::IsSdCardInitialized()) {
return false;
}
if (R_FAILED(fs::MountSdCard(LoaderSdMountName))) {
return false;
}
return (g_mounted_sd = true);
}
bool IsUsb30ForceEnabled() {
std::scoped_lock lk(g_embedded_patch_lock);
if (!g_got_embedded_patch_settings) {
g_force_enable_usb30 = spl::IsUsb30ForceEnabled();
g_got_embedded_patch_settings = true;
}
return g_force_enable_usb30;
}
consteval u8 ParseNybble(char c) {
AMS_ASSUME(('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f'));
if ('0' <= c && c <= '9') {
return c - '0' + 0x0;
} else if ('A' <= c && c <= 'F') {
return c - 'A' + 0xA;
} else /* if ('a' <= c && c <= 'f') */ {
return c - 'a' + 0xa;
}
}
consteval ro::ModuleId ParseModuleId(const char *str) {
/* Parse a static module id. */
ro::ModuleId module_id = {};
size_t ofs = 0;
while (str[0] != 0) {
AMS_ASSUME(ofs < sizeof(module_id));
AMS_ASSUME(str[1] != 0);
module_id.data[ofs] = (ParseNybble(str[0]) << 4) | (ParseNybble(str[1]) << 0);
str += 2;
ofs++;
}
return module_id;
}
struct EmbeddedPatchEntry {
uintptr_t offset;
const void * const data;
size_t size;
};
struct EmbeddedPatch {
ro::ModuleId module_id;
size_t num_entries;
const EmbeddedPatchEntry *entries;
};
#include "ldr_embedded_usb_patches.inc"
}
/* Apply IPS patches. */
void LocateAndApplyIpsPatchesToModule(const u8 *module_id_data, uintptr_t mapped_nso, size_t mapped_size) {
if (!EnsureSdCardMounted()) {
return;
}
ro::ModuleId module_id;
std::memcpy(std::addressof(module_id.data), module_id_data, sizeof(module_id.data));
ams::patcher::LocateAndApplyIpsPatchesToModule(LoaderSdMountName, NsoPatchesDirectory, NsoPatchesProtectedSize, NsoPatchesProtectedOffset, std::addressof(module_id), reinterpret_cast<u8 *>(mapped_nso), mapped_size);
}
/* Apply embedded patches. */
void ApplyEmbeddedPatchesToModule(const u8 *module_id_data, uintptr_t mapped_nso, size_t mapped_size) {
/* Make module id. */
ro::ModuleId module_id;
std::memcpy(std::addressof(module_id.data), module_id_data, sizeof(module_id.data));
if (IsUsb30ForceEnabled()) {
for (const auto &patch : Usb30ForceEnablePatches) {
if (std::memcmp(std::addressof(patch.module_id), std::addressof(module_id), sizeof(module_id)) == 0) {
for (size_t i = 0; i < patch.num_entries; ++i) {
const auto &entry = patch.entries[i];
if (entry.offset + entry.size <= mapped_size) {
std::memcpy(reinterpret_cast<void *>(mapped_nso + entry.offset), entry.data, entry.size);
}
}
}
}
}
}
}

View File

@@ -1,27 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
namespace ams::ldr {
/* Apply IPS patches. */
void LocateAndApplyIpsPatchesToModule(const u8 *module_id_data, uintptr_t mapped_nso, size_t mapped_size);
/* Apply embedded patches. */
void ApplyEmbeddedPatchesToModule(const u8 *module_id_data, uintptr_t mapped_nso, size_t mapped_size);
}

View File

@@ -1,761 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
#include "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
#include "ldr_development_manager.hpp"
#include "ldr_launch_record.hpp"
#include "ldr_meta.hpp"
#include "ldr_patcher.hpp"
#include "ldr_process_creation.hpp"
#include "ldr_ro_manager.hpp"
namespace ams::ldr {
namespace {
/* Convenience defines. */
constexpr size_t SystemResourceSizeMax = 0x1FE00000;
/* Types. */
enum NsoIndex {
Nso_Rtld = 0,
Nso_Main = 1,
Nso_Compat0 = 2,
Nso_Compat1 = 3,
Nso_Compat2 = 4,
Nso_Compat3 = 5,
Nso_Compat4 = 6,
Nso_Compat5 = 7,
Nso_Compat6 = 8,
Nso_Compat7 = 9,
Nso_Compat8 = 10,
Nso_Compat9 = 11,
Nso_SubSdk0 = 12,
Nso_SubSdk1 = 13,
Nso_SubSdk2 = 14,
Nso_SubSdk3 = 15,
Nso_SubSdk4 = 16,
Nso_SubSdk5 = 17,
Nso_SubSdk6 = 18,
Nso_SubSdk7 = 19,
Nso_SubSdk8 = 20,
Nso_SubSdk9 = 21,
Nso_Sdk = 22,
Nso_Count,
};
constexpr inline const char *NsoPaths[Nso_Count] = {
ENCODE_ATMOSPHERE_CODE_PATH("/rtld"),
ENCODE_ATMOSPHERE_CODE_PATH("/main"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat0"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat1"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat2"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat3"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat4"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat5"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat6"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat7"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat8"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat9"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk0"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk1"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk2"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk3"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk4"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk5"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk6"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk7"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk8"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk9"),
ENCODE_ATMOSPHERE_CODE_PATH("/sdk"),
};
constexpr const char *GetNsoPath(size_t idx) {
AMS_ABORT_UNLESS(idx < Nso_Count);
return NsoPaths[idx];
}
struct ProcessInfo {
os::NativeHandle process_handle;
uintptr_t args_address;
size_t args_size;
uintptr_t nso_address[Nso_Count];
size_t nso_size[Nso_Count];
};
/* Global NSO header cache. */
bool g_has_nso[Nso_Count];
NsoHeader g_nso_headers[Nso_Count];
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {
/* No version verification is done before 8.1.0. */
R_SUCCEED_IF(hos::GetVersion() < hos::Version_8_1_0);
/* No verification is done if development. */
R_SUCCEED_IF(IsDevelopmentForAntiDowngradeCheck());
/* TODO: Anti-downgrade checking does not make very much sense for us. Should we do anything? */
AMS_UNUSED(program_id, version);
R_SUCCEED();
}
/* Helpers. */
Result GetProgramInfoFromMeta(ProgramInfo *out, const Meta *meta) {
/* Copy basic info. */
out->main_thread_priority = meta->npdm->main_thread_priority;
out->default_cpu_id = meta->npdm->default_cpu_id;
out->main_thread_stack_size = meta->npdm->main_thread_stack_size;
out->program_id = meta->aci->program_id;
/* Copy access controls. */
size_t offset = 0;
#define COPY_ACCESS_CONTROL(source, which) \
({ \
const size_t size = meta->source->which##_size; \
R_UNLESS(offset + size <= sizeof(out->ac_buffer), ldr::ResultInternalError()); \
out->source##_##which##_size = size; \
std::memcpy(out->ac_buffer + offset, meta->source##_##which, size); \
offset += size; \
})
/* Copy all access controls to buffer. */
COPY_ACCESS_CONTROL(acid, sac);
COPY_ACCESS_CONTROL(aci, sac);
COPY_ACCESS_CONTROL(acid, fac);
COPY_ACCESS_CONTROL(aci, fah);
#undef COPY_ACCESS_CONTROL
/* Copy flags. */
out->flags = MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
R_SUCCEED();
}
bool IsApplet(const Meta *meta) {
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet;
}
bool IsApplication(const Meta *meta) {
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
}
Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) {
return static_cast<Npdm::AddressSpaceType>((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift);
}
Acid::PoolPartition GetPoolPartition(const Meta *meta) {
return static_cast<Acid::PoolPartition>((meta->acid->flags & Acid::AcidFlag_PoolPartitionMask) >> Acid::AcidFlag_PoolPartitionShift);
}
Result LoadAutoLoadHeaders(NsoHeader *nso_headers, bool *has_nso) {
/* Clear NSOs. */
std::memset(nso_headers, 0, sizeof(*nso_headers) * Nso_Count);
std::memset(has_nso, 0, sizeof(*has_nso) * Nso_Count);
for (size_t i = 0; i < Nso_Count; i++) {
fs::FileHandle file;
if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read))) {
ON_SCOPE_EXIT { fs::CloseFile(file); };
/* Read NSO header. */
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, nso_headers + i, sizeof(*nso_headers)));
R_UNLESS(read_size == sizeof(*nso_headers), ldr::ResultInvalidNso());
has_nso[i] = true;
}
}
R_SUCCEED();
}
Result CheckAutoLoad(const NsoHeader *nso_headers, const bool *has_nso) {
/* We must always have a main. */
R_UNLESS(has_nso[Nso_Main], ldr::ResultInvalidNso());
/* If we don't have an RTLD, we must only have a main. */
if (!has_nso[Nso_Rtld]) {
for (size_t i = Nso_Main + 1; i < Nso_Count; i++) {
R_UNLESS(!has_nso[i], ldr::ResultInvalidNso());
}
}
/* All NSOs must have zero text offset. */
for (size_t i = 0; i < Nso_Count; i++) {
R_UNLESS(nso_headers[i].text_dst_offset == 0, ldr::ResultInvalidNso());
}
R_SUCCEED();
}
constexpr const ncm::ProgramId UnqualifiedApprovalProgramIds[] = {
{ 0x010003F003A34000 }, /* Pokemon: Let's Go, Pikachu! */
{ 0x0100152000022000 }, /* Mario Kart 8 Deluxe */
{ 0x0100165003504000 }, /* Nintendo Labo Toy-Con 04: VR Kit */
{ 0x0100187003A36000 }, /* Pokemon: Let's Go, Eevee! */
{ 0x01002E5008C56000 }, /* Pokemon Sword [Live Tournament] */
{ 0x01002FF008C24000 }, /* Ring Fit Adventure */
{ 0x010049900F546001 }, /* Super Mario 3D All-Stars: Super Mario 64 */
{ 0x010057D00ECE4000 }, /* Nintendo Switch Online (Nintendo 64) [for Japan] */
{ 0x01006F8002326000 }, /* Animal Crossing: New Horizons */
{ 0x01006FB00F50E000 }, /* [???] */
{ 0x010070300F50C000 }, /* [???] */
{ 0x010075100E8EC000 }, /* 马力欧卡丁车8 豪华版 [Mario Kart 8 Deluxe for China] */
{ 0x01008DB008C2C000 }, /* Pokemon Shield */
{ 0x01009AD008C4C000 }, /* Pokemon: Let's Go, Pikachu! [Kiosk] */
{ 0x0100A66003384000 }, /* Hulu */
{ 0x0100ABF008968000 }, /* Pokemon Sword */
{ 0x0100C9A00ECE6000 }, /* Nintendo Switch Online (Nintendo 64) [for America] */
{ 0x0100ED100BA3A000 }, /* Mario Kart Live: Home Circuit */
{ 0x0100F38011CFE000 }, /* Animal Crossing: New Horizons Island Transfer Tool */
{ 0x0100F6B011028000 }, /* 健身环大冒险 [Ring Fit Adventure for China] */
};
/* Check that the unqualified approval programs are sorted. */
static_assert([]() -> bool {
for (size_t i = 0; i < util::size(UnqualifiedApprovalProgramIds) - 1; ++i) {
if (UnqualifiedApprovalProgramIds[i].value >= UnqualifiedApprovalProgramIds[i + 1].value) {
return false;
}
}
return true;
}());
bool IsUnqualifiedApprovalProgramId(ncm::ProgramId program_id) {
/* Check if the program id is one with unqualified approval. */
return std::binary_search(std::begin(UnqualifiedApprovalProgramIds), std::end(UnqualifiedApprovalProgramIds), program_id);
}
bool IsUnqualifiedApproval(const Meta *meta) {
/* If the meta has unqualified approval flag, it's unqualified approval. */
if (meta->acid->flags & ldr::Acid::AcidFlag_UnqualifiedApproval) {
return true;
}
/* If the unqualified approval flag is not set, the program must be an application. */
if (!IsApplication(meta)) {
return false;
}
/* The program id must be a force unqualified approval program id. */
return IsUnqualifiedApprovalProgramId(meta->acid->program_id_min) && meta->acid->program_id_min == meta->acid->program_id_max;
}
Result ValidateMeta(const Meta *meta, const ncm::ProgramLocation &loc, const fs::CodeVerificationData &code_verification_data) {
/* Validate version. */
R_TRY(ValidateProgramVersion(loc.program_id, meta->npdm->version));
/* Validate program id. */
R_UNLESS(meta->aci->program_id >= meta->acid->program_id_min, ldr::ResultInvalidProgramId());
R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId());
/* Validate the kernel capabilities. */
R_TRY(TestCapability(static_cast<const util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32), static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)));
/* If we have data to validate, validate it. */
if (meta->check_verification_data) {
const u8 *sig = code_verification_data.signature;
const size_t sig_size = sizeof(code_verification_data.signature);
const u8 *mod = static_cast<u8 *>(meta->modulus);
const size_t mod_size = crypto::Rsa2048PssSha256Verifier::ModulusSize;
const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent();
const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize;
const u8 *hsh = code_verification_data.target_hash;
const size_t hsh_size = sizeof(code_verification_data.target_hash);
const bool is_signature_valid = crypto::VerifyRsa2048PssSha256WithHash(sig, sig_size, mod, mod_size, exp, exp_size, hsh, hsh_size);
/* If the signature check fails, we need to check if this is allowable. */
if (!is_signature_valid) {
/* We have to enforce signature checks on prod and when we have a signature to check on dev. */
R_UNLESS(IsDevelopmentForAcidProductionCheck(), ldr::ResultInvalidNcaSignature());
R_UNLESS(!code_verification_data.has_data, ldr::ResultInvalidNcaSignature());
/* There was no signature to check on dev. Check if this is acceptable. */
R_UNLESS(IsUnqualifiedApproval(meta), ldr::ResultInvalidNcaSignature());
}
}
/* All good. */
R_SUCCEED();
}
Result GetCreateProcessFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
const u8 meta_flags = meta->npdm->flags;
u32 flags = 0;
/* Set Is64Bit. */
if (meta_flags & Npdm::MetaFlag_Is64Bit) {
flags |= svc::CreateProcessFlag_Is64Bit;
}
/* Set AddressSpaceType. */
switch (GetAddressSpaceType(meta)) {
case Npdm::AddressSpaceType_32Bit:
flags |= svc::CreateProcessFlag_AddressSpace32Bit;
break;
case Npdm::AddressSpaceType_64BitDeprecated:
flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
break;
case Npdm::AddressSpaceType_32BitWithoutAlias:
flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
break;
case Npdm::AddressSpaceType_64Bit:
flags |= svc::CreateProcessFlag_AddressSpace64Bit;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
/* Set Enable Debug. */
if (ldr_flags & CreateProcessFlag_EnableDebug) {
flags |= svc::CreateProcessFlag_EnableDebug;
}
/* Set Enable ASLR. */
if (!(ldr_flags & CreateProcessFlag_DisableAslr)) {
flags |= svc::CreateProcessFlag_EnableAslr;
}
/* Set Is Application. */
if (IsApplication(meta)) {
flags |= svc::CreateProcessFlag_IsApplication;
/* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */
if (hos::GetVersion() >= hos::Version_7_0_0) {
if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) {
flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation;
}
}
}
/* 5.0.0+ Set Pool Partition. */
if (hos::GetVersion() >= hos::Version_5_0_0) {
/* TODO: Nintendo no longer accepts Applet when pool partition == application. Would this break hbl/anything else in the hb ecosystem? */
/* TODO: Nintendo uses a helper bool MakeSvcPoolPartitionFlag(u32 *out, Acid::PoolPartition partition); */
switch (GetPoolPartition(meta)) {
case Acid::PoolPartition_Application:
if (IsApplet(meta)) {
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
} else {
flags |= svc::CreateProcessFlag_PoolPartitionApplication;
}
break;
case Acid::PoolPartition_Applet:
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
break;
case Acid::PoolPartition_System:
flags |= svc::CreateProcessFlag_PoolPartitionSystem;
break;
case Acid::PoolPartition_SystemNonSecure:
flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
} else if (hos::GetVersion() >= hos::Version_4_0_0) {
/* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */
if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) {
flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory;
}
}
/* 11.0.0+/meso Set Disable DAS merge. */
if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) {
flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge;
}
/* 18.0.0+/meso Set Alias region extra size. */
if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) {
flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize;
}
*out = flags;
R_SUCCEED();
}
Result GetCreateProcessParameter(svc::CreateProcessParameter *out, const Meta *meta, u32 flags, os::NativeHandle resource_limit) {
/* Clear output. */
std::memset(out, 0, sizeof(*out));
/* Set name, version, program id, resource limit handle. */
std::memcpy(out->name, meta->npdm->program_name, sizeof(out->name) - 1);
out->version = meta->npdm->version;
out->program_id = meta->aci->program_id.value;
out->reslimit = resource_limit;
/* Set flags. */
R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags));
/* 3.0.0+ System Resource Size. */
if (hos::GetVersion() >= hos::Version_3_0_0) {
/* Validate size is aligned. */
R_UNLESS(util::IsAligned(meta->npdm->system_resource_size, os::MemoryBlockUnitSize), ldr::ResultInvalidSize());
/* Validate system resource usage. */
if (meta->npdm->system_resource_size) {
/* Process must be 64-bit. */
R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta());
/* Process must be application or applet. */
R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta());
/* Size must be less than or equal to max. */
R_UNLESS(meta->npdm->system_resource_size <= SystemResourceSizeMax, ldr::ResultInvalidMeta());
}
out->system_resource_num_pages = meta->npdm->system_resource_size >> 12;
}
R_SUCCEED();
}
u64 GenerateSecureRandom(u64 max) {
/* Generate a cryptographically random number. */
u64 rand;
crypto::GenerateCryptographicallyRandomBytes(std::addressof(rand), sizeof(rand));
/* Coerce into range. */
return rand % (max + 1);
}
Result DecideAddressSpaceLayout(ProcessInfo *out, svc::CreateProcessParameter *out_param, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument) {
/* Clear output. */
out->args_address = 0;
out->args_size = 0;
std::memset(out->nso_address, 0, sizeof(out->nso_address));
std::memset(out->nso_size, 0, sizeof(out->nso_size));
size_t total_size = 0;
bool argument_allocated = false;
/* Calculate base offsets. */
for (size_t i = 0; i < Nso_Count; i++) {
if (has_nso[i]) {
out->nso_address[i] = total_size;
const size_t text_end = nso_headers[i].text_dst_offset + nso_headers[i].text_size;
const size_t ro_end = nso_headers[i].ro_dst_offset + nso_headers[i].ro_size;
const size_t rw_end = nso_headers[i].rw_dst_offset + nso_headers[i].rw_size + nso_headers[i].bss_size;
out->nso_size[i] = text_end;
out->nso_size[i] = std::max(out->nso_size[i], ro_end);
out->nso_size[i] = std::max(out->nso_size[i], rw_end);
out->nso_size[i] = util::AlignUp(out->nso_size[i], os::MemoryPageSize);
total_size += out->nso_size[i];
if (!argument_allocated && argument != nullptr) {
out->args_address = total_size;
out->args_size = util::AlignUp(2 * sizeof(u32) + argument->argument_size * 2 + ArgumentStore::ArgumentBufferSize, os::MemoryPageSize);
total_size += out->args_size;
argument_allocated = true;
}
}
}
/* Calculate ASLR. */
uintptr_t aslr_start = 0;
size_t aslr_size = 0;
if (hos::GetVersion() >= hos::Version_2_0_0) {
switch (out_param->flags & svc::CreateProcessFlag_AddressSpaceMask) {
case svc::CreateProcessFlag_AddressSpace32Bit:
case svc::CreateProcessFlag_AddressSpace32BitWithoutAlias:
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
break;
case svc::CreateProcessFlag_AddressSpace64BitDeprecated:
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
break;
case svc::CreateProcessFlag_AddressSpace64Bit:
aslr_start = svc::AddressMap39Start;
aslr_size = svc::AddressMap39Size;
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
} else {
/* On 1.0.0, only 2 address space types existed. */
if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) {
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
} else {
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
}
}
R_UNLESS(total_size <= aslr_size, svc::ResultOutOfMemory());
/* Set Create Process output. */
uintptr_t aslr_slide = 0;
size_t free_size = (aslr_size - total_size);
if (out_param->flags & svc::CreateProcessFlag_EnableAslr) {
aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
}
/* Set out. */
aslr_start += aslr_slide;
for (size_t i = 0; i < Nso_Count; i++) {
if (has_nso[i]) {
out->nso_address[i] += aslr_start;
}
}
if (out->args_address) {
out->args_address += aslr_start;
}
out_param->code_address = aslr_start;
out_param->code_num_pages = total_size >> 12;
R_SUCCEED();
}
Result LoadAutoLoadModuleSegment(fs::FileHandle file, const NsoHeader::SegmentInfo *segment, size_t file_size, const u8 *file_hash, bool is_compressed, bool check_hash, uintptr_t map_base, uintptr_t map_end) {
/* Select read size based on compression. */
if (!is_compressed) {
file_size = segment->size;
}
/* Validate size. */
R_UNLESS(file_size <= segment->size, ldr::ResultInvalidNso());
R_UNLESS(segment->size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
/* Load data from file. */
uintptr_t load_address = is_compressed ? map_end - file_size : map_base;
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, segment->file_offset, reinterpret_cast<void *>(load_address), file_size));
R_UNLESS(read_size == file_size, ldr::ResultInvalidNso());
/* Uncompress if necessary. */
if (is_compressed) {
bool decompressed = (util::DecompressLZ4(reinterpret_cast<void *>(map_base), segment->size, reinterpret_cast<const void *>(load_address), file_size) == static_cast<int>(segment->size));
R_UNLESS(decompressed, ldr::ResultInvalidNso());
}
/* Check hash if necessary. */
if (check_hash) {
u8 hash[crypto::Sha256Generator::HashSize];
crypto::GenerateSha256(hash, sizeof(hash), reinterpret_cast<void *>(map_base), segment->size);
R_UNLESS(std::memcmp(hash, file_hash, sizeof(hash)) == 0, ldr::ResultInvalidNso());
}
R_SUCCEED();
}
Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, bool prevent_code_reads) {
/* Map and read data from file. */
{
/* Map the process memory. */
void *mapped_memory = nullptr;
R_TRY(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, nso_address, nso_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, process_handle, nso_address, nso_size); };
const uintptr_t map_address = reinterpret_cast<uintptr_t>(mapped_memory);
/* Load NSO segments. */
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Text]), nso_header->text_compressed_size, nso_header->text_hash, (nso_header->flags & NsoHeader::Flag_CompressedText) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashText) != 0, map_address + nso_header->text_dst_offset, map_address + nso_size));
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Ro]), nso_header->ro_compressed_size, nso_header->ro_hash, (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashRo) != 0, map_address + nso_header->ro_dst_offset, map_address + nso_size));
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Rw]), nso_header->rw_compressed_size, nso_header->rw_hash, (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashRw) != 0, map_address + nso_header->rw_dst_offset, map_address + nso_size));
/* Clear unused space to zero. */
const size_t text_end = nso_header->text_dst_offset + nso_header->text_size;
const size_t ro_end = nso_header->ro_dst_offset + nso_header->ro_size;
const size_t rw_end = nso_header->rw_dst_offset + nso_header->rw_size;
std::memset(reinterpret_cast<void *>(map_address + 0), 0, nso_header->text_dst_offset);
std::memset(reinterpret_cast<void *>(map_address + text_end), 0, nso_header->ro_dst_offset - text_end);
std::memset(reinterpret_cast<void *>(map_address + ro_end), 0, nso_header->rw_dst_offset - ro_end);
std::memset(reinterpret_cast<void *>(map_address + rw_end), 0, nso_header->bss_size);
/* Apply embedded patches. */
ApplyEmbeddedPatchesToModule(nso_header->module_id, map_address, nso_size);
/* Apply IPS patches. */
LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size);
}
/* Set permissions. */
const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize);
const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize);
const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
if (text_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute));
}
if (ro_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, os::MemoryPermission_ReadOnly));
}
if (rw_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, os::MemoryPermission_ReadWrite));
}
R_SUCCEED();
}
Result LoadAutoLoadModules(const ProcessInfo *process_info, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument, bool prevent_code_reads) {
/* Load each NSO. */
for (size_t i = 0; i < Nso_Count; i++) {
if (has_nso[i]) {
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
R_TRY(LoadAutoLoadModule(process_info->process_handle, file, nso_headers + i, process_info->nso_address[i], process_info->nso_size[i], prevent_code_reads));
}
}
/* Load arguments, if present. */
if (argument != nullptr) {
/* Write argument data into memory. */
{
void *map_address = nullptr;
R_TRY(os::MapProcessMemory(std::addressof(map_address), process_info->process_handle, process_info->args_address, process_info->args_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(map_address, process_info->process_handle, process_info->args_address, process_info->args_size); };
ProgramArguments *args = static_cast<ProgramArguments *>(map_address);
std::memset(args, 0, sizeof(*args));
args->allocated_size = process_info->args_size;
args->arguments_size = argument->argument_size;
std::memcpy(args->arguments, argument->argument, argument->argument_size);
}
/* Set argument region permissions. */
/* NOTE: Nintendo uses svc::SetProcessMemoryPermission directly here. */
R_TRY(os::SetProcessMemoryPermission(process_info->process_handle, process_info->args_address, process_info->args_size, os::MemoryPermission_ReadWrite));
}
R_SUCCEED();
}
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
/* Get CreateProcessParameter. */
svc::CreateProcessParameter param;
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
/* Decide on an NSO layout. */
R_TRY(DecideAddressSpaceLayout(out, std::addressof(param), nso_headers, has_nso, argument));
/* Actually create process. */
svc::Handle process_handle;
R_TRY(svc::CreateProcess(std::addressof(process_handle), std::addressof(param), static_cast<const u32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(u32)));
/* Set the output handle, and ensure that if we fail after this point we clean it up. */
out->process_handle = process_handle;
ON_RESULT_FAILURE { svc::CloseHandle(process_handle); };
/* Load all auto load modules. */
R_RETURN(LoadAutoLoadModules(out, nso_headers, has_nso, argument, (meta->npdm->flags & ldr::Npdm::MetaFlag_PreventCodeReads) != 0));
}
}
/* Process Creation API. */
Result CreateProcess(os::NativeHandle *out, PinId pin_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const char *path, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit, const ldr::ProgramAttributes &attrs) {
/* Mount code. */
AMS_UNUSED(path);
ScopedCodeMount mount(loc, override_status, attrs);
R_TRY(mount.GetResult());
/* Load meta, possibly from cache. */
Meta meta;
R_TRY(LoadMetaFromCache(std::addressof(meta), loc, override_status, attrs.platform));
/* Validate meta. */
R_TRY(ValidateMeta(std::addressof(meta), loc, mount.GetCodeVerificationData()));
/* Load, validate NSO headers. */
R_TRY(LoadAutoLoadHeaders(g_nso_headers, g_has_nso));
R_TRY(CheckAutoLoad(g_nso_headers, g_has_nso));
/* Actually create the process and load NSOs into process memory. */
ProcessInfo info;
R_TRY(CreateProcessAndLoadAutoLoadModules(std::addressof(info), std::addressof(meta), g_nso_headers, g_has_nso, argument, flags, resource_limit));
/* Register NSOs with the RoManager. */
{
/* Nintendo doesn't validate this get, but we do. */
os::ProcessId process_id = os::GetProcessId(info.process_handle);
/* Register new process. */
const auto as_type = GetAddressSpaceType(std::addressof(meta));
RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::AddressSpaceType_64Bit || as_type == Npdm::AddressSpaceType_64BitDeprecated);
/* Register all NSOs. */
for (size_t i = 0; i < Nso_Count; i++) {
if (g_has_nso[i]) {
RoManager::GetInstance().AddNso(pin_id, g_nso_headers[i].module_id, info.nso_address[i], info.nso_size[i]);
}
}
}
/* If we're overriding for HBL, perform HTML document redirection. */
if (override_status.IsHbl()) {
/* Don't validate result, failure is okay. */
RedirectHtmlDocumentPathForHbl(loc);
}
/* Clear the external code for the program. */
fssystem::DestroyExternalCode(loc.program_id);
/* Note that we've created the program. */
SetLaunchedBootProgram(loc.program_id);
/* Move the process handle to output. */
*out = info.process_handle;
R_SUCCEED();
}
Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const char *path, const ldr::ProgramAttributes &attrs) {
Meta meta;
/* Load Meta. */
{
AMS_UNUSED(path);
ScopedCodeMount mount(loc, attrs);
R_TRY(mount.GetResult());
R_TRY(LoadMeta(std::addressof(meta), loc, mount.GetOverrideStatus(), attrs.platform, false));
if (out_status != nullptr) {
*out_status = mount.GetOverrideStatus();
}
}
return GetProgramInfoFromMeta(out, std::addressof(meta));
}
Result PinProgram(PinId *out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status) {
R_UNLESS(RoManager::GetInstance().Allocate(out_id, loc, override_status), ldr::ResultMaxProcess());
R_SUCCEED();
}
Result UnpinProgram(PinId id) {
R_UNLESS(RoManager::GetInstance().Free(id), ldr::ResultNotPinned());
R_SUCCEED();
}
Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) {
R_UNLESS(RoManager::GetInstance().GetProcessModuleInfo(out_count, out, max_out_count, process_id), ldr::ResultNotPinned());
R_SUCCEED();
}
Result GetProgramLocationAndOverrideStatusFromPinId(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id) {
R_UNLESS(RoManager::GetInstance().GetProgramLocationAndStatus(out, out_status, pin_id), ldr::ResultNotPinned());
R_SUCCEED();
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright (c) 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 "ldr_argument_store.hpp"
namespace ams::ldr {
/* Process Creation API. */
Result CreateProcess(os::NativeHandle *out, PinId pin_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const char *path, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit, const ldr::ProgramAttributes &attrs);
Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const char *path, const ldr::ProgramAttributes &attrs);
Result PinProgram(PinId *out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status);
Result UnpinProgram(PinId id);
Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, os::ProcessId process_id);
Result GetProgramLocationAndOverrideStatusFromPinId(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id);
}

View File

@@ -1,181 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
#include "ldr_ro_manager.hpp"
namespace ams::ldr {
bool RoManager::Allocate(PinId *out, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status) {
/* Ensure that output pin id is set. */
*out = InvalidPinId;
/* Allocate a process info. */
auto *found = this->AllocateProcessInfo();
if (found == nullptr) {
return false;
}
/* Setup the process info. */
std::memset(found, 0, sizeof(*found));
found->pin_id = { ++m_pin_id };
found->program_location = loc;
found->override_status = status;
found->in_use = true;
/* Set the output pin id. */
*out = found->pin_id;
return true;
}
bool RoManager::Free(PinId pin_id) {
/* Find the process. */
auto *found = this->FindProcessInfo(pin_id);
if (found == nullptr) {
return false;
}
/* Set the process as not in use. */
found->in_use = false;
/* Set all the process's nsos as not in use. */
for (auto i = 0; i < NsoCount; ++i) {
found->nso_infos[i].in_use = false;
}
return true;
}
void RoManager::RegisterProcess(PinId pin_id, os::ProcessId process_id, ncm::ProgramId program_id, bool is_64_bit_address_space) {
/* Find the process. */
auto *found = this->FindProcessInfo(pin_id);
if (found == nullptr) {
return;
}
/* Set the process id and program id. */
found->process_id = process_id;
found->program_id = program_id;
AMS_UNUSED(is_64_bit_address_space);
}
bool RoManager::GetProgramLocationAndStatus(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id) {
/* Find the process. */
auto *found = this->FindProcessInfo(pin_id);
if (found == nullptr) {
return false;
}
/* Set the output location/status. */
*out = found->program_location;
*out_status = found->override_status;
return true;
}
void RoManager::AddNso(PinId pin_id, const u8 *module_id, u64 address, u64 size) {
/* Find the process. */
auto *found = this->FindProcessInfo(pin_id);
if (found == nullptr) {
return;
}
/* Allocate an nso. */
auto *info = this->AllocateNsoInfo(found);
if (info == nullptr) {
return;
}
/* Copy the information into the nso info. */
std::memcpy(info->module_info.module_id, module_id, sizeof(info->module_info.module_id));
info->module_info.address = address;
info->module_info.size = size;
info->in_use = true;
}
bool RoManager::GetProcessModuleInfo(u32 *out_count, ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) {
/* Find the process. */
auto *found = this->FindProcessInfo(process_id);
if (found == nullptr) {
return false;
}
/* Copy allocated nso module infos. */
size_t count = 0;
for (auto i = 0; i < NsoCount && count < max_out_count; ++i) {
/* Skip unallocated nsos. */
if (!found->nso_infos[i].in_use) {
continue;
}
/* Copy out the module info. */
out[count++] = found->nso_infos[i].module_info;
}
/* Set the output count. */
*out_count = count;
return true;
}
RoManager::ProcessInfo *RoManager::AllocateProcessInfo() {
for (auto i = 0; i < ProcessCount; ++i) {
if (!m_processes[i].in_use) {
return m_processes + i;
}
}
return nullptr;
}
RoManager::ProcessInfo *RoManager::FindProcessInfo(PinId pin_id) {
for (auto i = 0; i < ProcessCount; ++i) {
if (m_processes[i].in_use && m_processes[i].pin_id == pin_id) {
return m_processes + i;
}
}
return nullptr;
}
RoManager::ProcessInfo *RoManager::FindProcessInfo(os::ProcessId process_id) {
for (auto i = 0; i < ProcessCount; ++i) {
if (m_processes[i].in_use && m_processes[i].process_id == process_id) {
return m_processes + i;
}
}
return nullptr;
}
RoManager::ProcessInfo *RoManager::FindProcessInfo(ncm::ProgramId program_id) {
for (auto i = 0; i < ProcessCount; ++i) {
if (m_processes[i].in_use && m_processes[i].program_id == program_id) {
return m_processes + i;
}
}
return nullptr;
}
RoManager::NsoInfo *RoManager::AllocateNsoInfo(ProcessInfo *info) {
for (auto i = 0; i < NsoCount; ++i) {
if (!info->nso_infos[i].in_use) {
return info->nso_infos + i;
}
}
return nullptr;
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright (c) 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 <stratosphere.hpp>
namespace ams::ldr {
class RoManager {
AMS_CONSTINIT_SINGLETON_TRAITS(RoManager);
public:
static constexpr PinId InvalidPinId = {};
static constexpr int ProcessCount = 0x40;
static constexpr int NsoCount = 0x20;
private:
struct NsoInfo {
bool in_use;
ldr::ModuleInfo module_info;
};
struct ProcessInfo {
bool in_use;
PinId pin_id;
os::ProcessId process_id;
ncm::ProgramId program_id;
cfg::OverrideStatus override_status;
ncm::ProgramLocation program_location;
NsoInfo nso_infos[NsoCount];
};
private:
ProcessInfo m_processes[ProcessCount];
u64 m_pin_id;
public:
bool Allocate(PinId *out, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status);
bool Free(PinId pin_id);
void RegisterProcess(PinId pin_id, os::ProcessId process_id, ncm::ProgramId program_id, bool is_64_bit_address_space);
bool GetProgramLocationAndStatus(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id);
void AddNso(PinId pin_id, const u8 *module_id, u64 address, u64 size);
bool GetProcessModuleInfo(u32 *out_count, ModuleInfo *out, size_t max_out_count, os::ProcessId process_id);
private:
ProcessInfo *AllocateProcessInfo();
ProcessInfo *FindProcessInfo(PinId pin_id);
ProcessInfo *FindProcessInfo(os::ProcessId process_id);
ProcessInfo *FindProcessInfo(ncm::ProgramId program_id);
NsoInfo *AllocateNsoInfo(ProcessInfo *info);
};
}