Merge pull request #603 from Atmosphere-NX/ldr_rewrite

loader: completely rewrite.
This commit is contained in:
SciresM 2019-06-27 23:28:00 -07:00 committed by GitHub
commit 67c0f4527e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 2510 additions and 5818 deletions

View file

@ -37,7 +37,7 @@ static std::atomic_bool g_has_hid_session = false;
/* Content override support variables/types */
static OverrideKey g_default_override_key = {
.key_combination = KEY_R,
.key_combination = KEY_L,
.override_by_default = true
};
@ -49,11 +49,11 @@ struct HblOverrideConfig {
static HblOverrideConfig g_hbl_override_config = {
.override_key = {
.key_combination = KEY_L,
.override_by_default = true
.key_combination = KEY_R,
.override_by_default = false
},
.title_id = TitleId_AppletPhotoViewer,
.override_any_app = false
.override_any_app = true
};
/* Static buffer for loader.ini contents at runtime. */

@ -1 +1 @@
Subproject commit cf5c6cdad9ec4066d763c3317e98f7027d3172a6
Subproject commit 403f1c7a014c47bf7e907da04df9f9b591b9f89f

View file

@ -1,269 +0,0 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "ini.h"
#if !INI_USE_STACK
#include <stdlib.h>
#endif
#define MAX_SECTION 50
#define MAX_NAME 50
/* Used by ini_parse_string() to keep track of string parsing state. */
typedef struct {
const char* ptr;
size_t num_left;
} ini_parse_string_ctx;
/* Strip whitespace chars off end of given string, in place. Return s. */
static char* rstrip(char* s)
{
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p)))
*p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
static char* lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to null at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
static char* find_chars_or_comment(const char* s, const char* chars)
{
#if INI_ALLOW_INLINE_COMMENTS
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
#endif
return (char*)s;
}
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
static char* strncpy0(char* dest, const char* src, size_t size)
{
strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
return dest;
}
/* See documentation in header file. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
int max_line = INI_MAX_LINE;
#else
char* line;
int max_line = INI_INITIAL_ALLOC;
#endif
#if INI_ALLOW_REALLOC
char* new_line;
int offset;
#endif
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
#if !INI_USE_STACK
line = (char*)malloc(INI_INITIAL_ALLOC);
if (!line) {
return -2;
}
#endif
#if INI_HANDLER_LINENO
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
#else
#define HANDLER(u, s, n, v) handler(u, s, n, v)
#endif
/* Scan through stream line by line */
while (reader(line, max_line, stream) != NULL) {
#if INI_ALLOW_REALLOC
offset = strlen(line);
while (offset == max_line - 1 && line[offset - 1] != '\n') {
max_line *= 2;
if (max_line > INI_MAX_LINE)
max_line = INI_MAX_LINE;
new_line = realloc(line, max_line);
if (!new_line) {
free(line);
return -2;
}
line = new_line;
if (reader(line + offset, max_line - offset, stream) == NULL)
break;
if (max_line >= INI_MAX_LINE)
break;
offset += strlen(line + offset);
}
#endif
lineno++;
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = lskip(rstrip(start));
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
/* Start-of-line comment */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
/* Non-blank line with leading whitespace, treat as continuation
of previous name's value (as per Python configparser). */
if (!HANDLER(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = end + 1;
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(value, NULL);
if (*end)
*end = '\0';
#endif
value = lskip(value);
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!HANDLER(user, section, name, value) && !error)
error = lineno;
}
else if (!error) {
/* No '=' or ':' found on name[=:]value line */
error = lineno;
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
free(line);
#endif
return error;
}
/* See documentation in header file. */
int ini_parse_file(FILE* file, ini_handler handler, void* user)
{
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
/* See documentation in header file. */
int ini_parse(const char* filename, ini_handler handler, void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}
/* An ini_reader function to read the next line from a string buffer. This
is the fgets() equivalent used by ini_parse_string(). */
static char* ini_reader_string(char* str, int num, void* stream) {
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
const char* ctx_ptr = ctx->ptr;
size_t ctx_num_left = ctx->num_left;
char* strp = str;
char c;
if (ctx_num_left == 0 || num < 2)
return NULL;
while (num > 1 && ctx_num_left != 0) {
c = *ctx_ptr++;
ctx_num_left--;
*strp++ = c;
if (c == '\n')
break;
num--;
}
*strp = '\0';
ctx->ptr = ctx_ptr;
ctx->num_left = ctx_num_left;
return str;
}
/* See documentation in header file. */
int ini_parse_string(const char* string, ini_handler handler, void* user) {
ini_parse_string_ctx ctx;
ctx.ptr = string;
ctx.num_left = strlen(string);
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
user);
}

View file

@ -1,130 +0,0 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef __INI_H__
#define __INI_H__
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Nonzero if ini_handler callback should accept lineno parameter. */
#ifndef INI_HANDLER_LINENO
#define INI_HANDLER_LINENO 0
#endif
/* Typedef for prototype of handler function. */
#if INI_HANDLER_LINENO
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value,
int lineno);
#else
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value);
#endif
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char* filename, ini_handler handler, void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE* file, ini_handler handler, void* user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O (see also
ini_parse_string). */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user);
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
instead of a file. Useful for parsing INI data from a network socket or
already in memory. */
int ini_parse_string(const char* string, ini_handler handler, void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Chars that begin a start-of-line comment. Per Python configparser, allow
both ; and # comments at the start of a line by default. */
#ifndef INI_START_COMMENT_PREFIXES
#define INI_START_COMMENT_PREFIXES ";#"
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Maximum line length for any line in INI file (stack or heap). Note that
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
zero. */
#ifndef INI_ALLOW_REALLOC
#define INI_ALLOW_REALLOC 0
#endif
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
is zero. */
#ifndef INI_INITIAL_ALLOC
#define INI_INITIAL_ALLOC 200
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
#ifdef __cplusplus
}
#endif
#endif /* __INI_H__ */

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2018-2019 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 "ldr_arguments.hpp"
namespace sts::ldr::args {
namespace {
/* Convenience definitions. */
constexpr ncm::TitleId FreeTitleId = {};
constexpr size_t MaxArgumentInfos = 10;
/* Global storage. */
ArgumentInfo g_argument_infos[MaxArgumentInfos];
/* Helpers. */
ArgumentInfo *FindArgumentInfo(ncm::TitleId title_id) {
for (size_t i = 0; i < MaxArgumentInfos; i++) {
if (g_argument_infos[i].title_id == title_id) {
return &g_argument_infos[i];
}
}
return nullptr;
}
ArgumentInfo *FindFreeArgumentInfo() {
return FindArgumentInfo(FreeTitleId);
}
}
/* API. */
const ArgumentInfo *Get(ncm::TitleId title_id) {
return FindArgumentInfo(title_id);
}
Result Set(ncm::TitleId title_id, const void *args, size_t args_size) {
if (args_size >= ArgumentSizeMax) {
return ResultLoaderTooLongArgument;
}
ArgumentInfo *arg_info = FindArgumentInfo(title_id);
if (arg_info == nullptr) {
arg_info = FindFreeArgumentInfo();
}
if (arg_info == nullptr) {
return ResultLoaderTooManyArguments;
}
arg_info->title_id = title_id;
arg_info->args_size = args_size;
std::memcpy(arg_info->args, args, args_size);
return ResultSuccess;
}
Result Clear() {
for (size_t i = 0; i < MaxArgumentInfos; i++) {
g_argument_infos[i].title_id = FreeTitleId;
}
return ResultSuccess;
}
}

View file

@ -16,27 +16,22 @@
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include <stratosphere/ldr.hpp>
#define LAUNCH_QUEUE_SIZE (10)
#define LAUNCH_QUEUE_FULL (-1)
namespace sts::ldr::args {
#define LAUNCH_QUEUE_ARG_SIZE_MAX (0x8000)
constexpr size_t ArgumentSizeMax = 0x8000;
class LaunchQueue {
public:
struct LaunchItem {
u64 tid;
u64 arg_size;
char args[LAUNCH_QUEUE_ARG_SIZE_MAX];
struct ArgumentInfo {
ncm::TitleId title_id;
size_t args_size;
u8 args[ArgumentSizeMax];
};
static LaunchQueue::LaunchItem *GetItem(u64 tid);
/* API. */
const ArgumentInfo *Get(ncm::TitleId title_id);
Result Set(ncm::TitleId title_id, const void *args, size_t args_size);
Result Clear();
static Result Add(u64 tid, const char *args, u64 arg_size);
static Result AddItem(const LaunchItem *item);
static Result AddCopy(u64 tid_base, u64 new_tid);
static int GetIndex(u64 tid);
static int GetFreeIndex(u64 tid);
static bool Contains(u64 tid);
static void Clear();
};
}

View file

@ -0,0 +1,425 @@
/*
* Copyright (c) 2018-2019 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 "ldr_capabilities.hpp"
namespace sts::ldr::caps {
namespace {
/* Types. */
enum class CapabilityId {
KernelFlags = 3,
SyscallMask = 4,
MapRange = 6,
MapPage = 7,
InterruptPair = 11,
ApplicationType = 13,
KernelVersion = 14,
HandleTable = 15,
DebugFlags = 16,
Empty = 32,
};
constexpr CapabilityId GetCapabilityId(u32 cap) {
return static_cast<CapabilityId>(__builtin_ctz(~cap));
}
template<CapabilityId Id>
class Capability {
public:
static constexpr u32 ValueShift = static_cast<u32>(Id) + 1;
static constexpr u32 IdMask = (1u << (ValueShift - 1)) - 1;
private:
u32 value;
public:
Capability(u32 v) : value(v) { /* ... */ }
CapabilityId GetId() const {
return Id;
}
u32 GetValue() const {
return this->value >> ValueShift;
}
};
#define CAPABILITY_CLASS_NAME(id) Capability##id
#define CAPABILITY_BASE_CLASS(id) Capability<CapabilityId::id>
#define DEFINE_CAPABILITY_CLASS(id, member_functions) \
class CAPABILITY_CLASS_NAME(id) : public CAPABILITY_BASE_CLASS(id) { \
public: \
CAPABILITY_CLASS_NAME(id)(u32 v) : CAPABILITY_BASE_CLASS(id)(v) { /* ... */ } \
\
static CAPABILITY_CLASS_NAME(id) Decode(u32 v) { return CAPABILITY_CLASS_NAME(id)(v); } \
\
member_functions \
}
/* Class definitions. */
DEFINE_CAPABILITY_CLASS(KernelFlags,
u32 GetMaximumThreadPriority() const {
return (this->GetValue() >> 0) & 0x3F;
}
u32 GetMinimumThreadPriority() const {
return (this->GetValue() >> 6) & 0x3F;
}
u32 GetMinimumCoreId() const {
return (this->GetValue() >> 12) & 0xFF;
}
u32 GetMaximumCoreId() const {
return (this->GetValue() >> 20) & 0xFF;
}
bool IsValid(const u32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == this->GetId()) {
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,
u32 GetMask() const {
return (this->GetValue() >> 0) & 0xFFFFFF;
}
u32 GetIndex() const {
return (this->GetValue() >> 24) & 0x7;
}
bool IsValid(const u32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == this->GetId()) {
const auto restriction = Decode(kac[i]);
if (this->GetIndex() == restriction.GetIndex() && this->GetMask() == restriction.GetMask()) {
return true;
}
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(MapRange,
static constexpr size_t SizeMax = 0x100000;
u32 GetAddressSize() const {
return (this->GetValue() >> 0) & 0xFFFFFF;
}
u32 GetFlag() const {
return (this->GetValue() >> 24) & 0x1;
}
bool IsValid(const u32 next_cap, const u32 *kac, size_t kac_count) const {
if (GetCapabilityId(next_cap) != this->GetId()) {
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]) == this->GetId()) {
const auto restriction = Decode(kac[i++]);
if (i >= kac_count || GetCapabilityId(kac[i]) != this->GetId()) {
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,
u32 GetAddress() const {
return (this->GetValue() >> 0) & 0xFFFFFF;
}
bool IsValid(const u32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == this->GetId()) {
const auto restriction = Decode(kac[i]);
if (this->GetValue() == restriction.GetValue()) {
return true;
}
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(InterruptPair,
static constexpr u32 EmptyInterruptId = 0x3FF;
u32 GetInterruptId0() const {
return (this->GetValue() >> 0) & 0x3FF;
}
u32 GetInterruptId1() const {
return (this->GetValue() >> 10) & 0x3FF;
}
bool IsSingleIdValid(const u32 id, const u32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == this->GetId()) {
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 u32 *kac, size_t kac_count) const {
return IsSingleIdValid(this->GetInterruptId0(), kac, kac_count) && IsSingleIdValid(this->GetInterruptId1(), kac, kac_count);
}
);
DEFINE_CAPABILITY_CLASS(ApplicationType,
u32 GetApplicationType() const {
return (this->GetValue() >> 0) & 0x3;
}
bool IsValid(const u32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == this->GetId()) {
const auto restriction = Decode(kac[i]);
return restriction.GetValue() == this->GetValue();
}
}
return false;
}
static constexpr u32 Encode(u32 app_type) {
return ((app_type & 3) << ValueShift) | IdMask;
}
);
DEFINE_CAPABILITY_CLASS(KernelVersion,
u32 GetMinorVersion() const {
return (this->GetValue() >> 0) & 0xF;
}
u32 GetMajorVersion() const {
/* TODO: Are upper bits unused? */
return (this->GetValue() >> 4) & 0x1FFF;
}
bool IsValid(const u32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == this->GetId()) {
const auto restriction = Decode(kac[i]);
return restriction.GetValue() == this->GetValue();
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(HandleTable,
u32 GetSize() const {
return (this->GetValue() >> 0) & 0x3FF;
}
bool IsValid(const u32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == this->GetId()) {
const auto restriction = Decode(kac[i]);
return this->GetSize() <= restriction.GetSize();
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(DebugFlags,
bool GetAllowDebug() const {
return (this->GetValue() >> 0) & 1;
}
bool GetForceDebug() const {
return (this->GetValue() >> 1) & 1;
}
bool IsValid(const u32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == this->GetId()) {
const auto restriction = Decode(kac[i]);
return (restriction.GetValue() & this->GetValue()) == this->GetValue();
}
}
return false;
}
static constexpr u32 Encode(bool allow_debug, bool force_debug) {
const u32 desc = (static_cast<u32>(force_debug) << 1) | (static_cast<u32>(allow_debug) << 0);
return (desc << ValueShift) | IdMask;
}
);
}
/* Capabilities API. */
Result ValidateCapabilities(const void *acid_kac, size_t acid_kac_size, const void *aci_kac, size_t aci_kac_size) {
const u32 *acid_caps = reinterpret_cast<const u32 *>(acid_kac);
const u32 *aci_caps = reinterpret_cast<const u32 *>(aci_kac);
const size_t num_acid_caps = acid_kac_size / sizeof(*acid_caps);
const size_t num_aci_caps = aci_kac_size / sizeof(*aci_caps);
for (size_t i = 0; i < num_aci_caps; i++) {
const u32 cur_cap = aci_caps[i];
const auto id = GetCapabilityId(cur_cap);
#define VALIDATE_CASE(id) \
case CapabilityId::id: \
if (!Capability##id::Decode(cur_cap).IsValid(acid_caps, num_acid_caps)) { \
return ResultLoaderInvalidCapability##id; \
} \
break
switch (id) {
VALIDATE_CASE(KernelFlags);
VALIDATE_CASE(SyscallMask);
VALIDATE_CASE(MapPage);
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++;
if (i >= num_aci_caps || !CapabilityMapRange::Decode(cur_cap).IsValid(aci_caps[i], acid_caps, num_acid_caps)) {
return ResultLoaderInvalidCapabilityMapRange;
}
}
break;
default:
if (id != CapabilityId::Empty) {
return ResultLoaderUnknownCapability;
}
break;
}
#undef VALIDATE_CASE
}
return ResultSuccess;
}
u16 GetProgramInfoFlags(const void *kac, size_t kac_size) {
const u32 *caps = reinterpret_cast<const u32 *>(kac);
const size_t num_caps = kac_size / sizeof(*caps);
u16 flags = 0;
for (size_t i = 0; i < num_caps; i++) {
const u32 cur_cap = caps[i];
switch (GetCapabilityId(cur_cap)) {
case CapabilityId::ApplicationType:
{
const auto app_type = CapabilityApplicationType::Decode(cur_cap).GetApplicationType() & ProgramInfoFlag_ApplicationTypeMask;
if (app_type != ProgramInfoFlag_InvalidType) {
flags |= app_type;
}
}
break;
case CapabilityId::DebugFlags:
if (CapabilityDebugFlags::Decode(cur_cap).GetAllowDebug()) {
flags |= ProgramInfoFlag_AllowDebug;
}
break;
default:
break;
}
}
return flags;
}
void SetProgramInfoFlags(u16 flags, void *kac, size_t kac_size) {
u32 *caps = reinterpret_cast<u32 *>(kac);
const size_t num_caps = kac_size / sizeof(*caps);
for (size_t i = 0; i < num_caps; i++) {
const u32 cur_cap = caps[i];
switch (GetCapabilityId(cur_cap)) {
case CapabilityId::ApplicationType:
caps[i] = CapabilityApplicationType::Encode(flags & ProgramInfoFlag_ApplicationTypeMask);
break;
case CapabilityId::DebugFlags:
caps[i] = CapabilityDebugFlags::Encode((flags & ProgramInfoFlag_AllowDebug) != 0, CapabilityDebugFlags::Decode(cur_cap).GetForceDebug());
break;
default:
break;
}
}
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
#include <stratosphere/ldr.hpp>
namespace sts::ldr::caps {
/* Capabilities API. */
Result ValidateCapabilities(const void *acid_kac, size_t acid_kac_size, const void *aci_kac, size_t aci_kac_size);
u16 GetProgramInfoFlags(const void *kac, size_t kac_size);
void SetProgramInfoFlags(u16 flags, void *kac, size_t kac_size);
}

View file

@ -13,459 +13,330 @@
* 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 <dirent.h>
#include <stratosphere/cfg.hpp>
#include <cstring>
#include <switch.h>
#include <stratosphere.hpp>
#include <strings.h>
#include <vector>
#include <algorithm>
#include <map>
#include "ldr_registration.hpp"
#include "ldr_content_management.hpp"
#include "ldr_hid.hpp"
#include "ldr_npdm.hpp"
#include "ldr_ecs.hpp"
#include "ini.h"
namespace sts::ldr {
static FsFileSystem g_CodeFileSystem = {};
static FsFileSystem g_HblFileSystem = {};
namespace {
static std::vector<u64> g_created_titles;
static bool g_has_initialized_fs_dev = false;
/* DeviceNames. */
constexpr const char *CodeFileSystemDeviceName = "code";
constexpr const char *HblFileSystemDeviceName = "hbl";
constexpr const char *SdCardFileSystemDeviceName = "sdmc";
/* Default to Key R, hold disables override, HBL at atmosphere/hbl.nsp. */
static bool g_mounted_hbl_nsp = false;
static char g_hbl_sd_path[FS_MAX_PATH+1] = "@Sdcard:/atmosphere/hbl.nsp\x00";
constexpr const char *SdCardStorageMountPoint = "@Sdcard";
static OverrideKey g_default_override_key = {
.key_combination = KEY_L,
.override_by_default = true
};
/* Globals. */
bool g_has_mounted_sd_card = false;
struct HblOverrideConfig {
OverrideKey override_key;
u64 title_id;
bool override_any_app;
};
ncm::TitleId g_should_override_title_id;
bool g_should_override_hbl = false;
bool g_should_override_sd = false;
static HblOverrideConfig g_hbl_override_config = {
.override_key = {
.key_combination = KEY_R,
.override_by_default = true
},
.title_id = TitleId_AppletPhotoViewer,
.override_any_app = false
};
/* Static buffer for loader.ini contents at runtime. */
static char g_config_ini_data[0x800];
/* SetExternalContentSource extension */
static std::map<u64, ContentManagement::ExternalContentSource> g_external_content_sources;
Result ContentManagement::MountCode(u64 tid, FsStorageId sid) {
char path[FS_MAX_PATH] = {0};
/* We defer SD card mounting, so if relevant ensure it is mounted. */
if (!g_has_initialized_fs_dev) {
TryMountSdCard();
}
if (g_has_initialized_fs_dev) {
RefreshConfigurationData();
}
if (ShouldOverrideContentsWithSD(tid) && R_SUCCEEDED(MountCodeNspOnSd(tid))) {
return ResultSuccess;
}
R_TRY(ResolveContentPath(path, tid, sid));
/* Fix up path. */
for (unsigned int i = 0; i < FS_MAX_PATH && path[i] != '\x00'; i++) {
/* Helpers. */
inline void FixFileSystemPath(char *path) {
/* Paths will fail when passed to FS if they use the wrong kinds of slashes. */
for (size_t i = 0; i < FS_MAX_PATH && path[i]; i++) {
if (path[i] == '\\') {
path[i] = '/';
}
}
}
/* Always re-initialize fsp-ldr, in case it's closed */
DoWithSmSession([&]() {
R_ASSERT(fsldrInitialize());
});
ON_SCOPE_EXIT { fsldrExit(); };
inline const char *GetRelativePathStart(const char *relative_path) {
/* We assume filenames don't start with slashes when formatting. */
while (*relative_path == '/' || *relative_path == '\\') {
relative_path++;
}
return relative_path;
}
R_TRY(fsldrOpenCodeFileSystem(tid, path, &g_CodeFileSystem));
void UpdateShouldOverrideCache(ncm::TitleId title_id) {
if (g_should_override_title_id != title_id) {
cfg::GetOverrideKeyHeldStatus(&g_should_override_hbl, &g_should_override_sd, title_id);
}
g_should_override_title_id = title_id;
}
fsdevMountDevice("code", g_CodeFileSystem);
TryMountHblNspOnSd();
void InvalidateShouldOverrideCache() {
g_should_override_title_id = {};
}
bool ShouldOverrideWithHbl(ncm::TitleId title_id) {
UpdateShouldOverrideCache(title_id);
return g_should_override_hbl;
}
bool ShouldOverrideWithSd(ncm::TitleId title_id) {
UpdateShouldOverrideCache(title_id);
return g_should_override_sd;
}
Result MountSdCardFileSystem() {
return fsdevMountSdmc();
}
Result MountNspFileSystem(const char *device_name, const char *path) {
FsFileSystem fs;
R_TRY(fsOpenFileSystemWithId(&fs, 0, FsFileSystemType_ApplicationPackage, path));
if(fsdevMountDevice(device_name, fs) < 0) {
std::abort();
}
return ResultSuccess;
}
Result ContentManagement::UnmountCode() {
if (g_mounted_hbl_nsp) {
fsdevUnmountDevice("hbl");
g_mounted_hbl_nsp = false;
FILE *OpenFile(const char *device_name, const char *relative_path) {
/* Allow nullptr device_name/relative path -- those are simply not openable. */
if (device_name == nullptr || relative_path == nullptr) {
return nullptr;
}
fsdevUnmountDevice("code");
char path[FS_MAX_PATH];
std::snprintf(path, FS_MAX_PATH, "%s:/%s", device_name, GetRelativePathStart(relative_path));
FixFileSystemPath(path);
return fopen(path, "rb");
}
FILE *OpenLooseSdFile(ncm::TitleId title_id, const char *relative_path) {
/* Allow nullptr relative path -- those are simply not openable. */
if (relative_path == nullptr) {
return nullptr;
}
char path[FS_MAX_PATH];
std::snprintf(path, FS_MAX_PATH, "/atmosphere/titles/%016lx/exefs/%s", static_cast<u64>(title_id), GetRelativePathStart(relative_path));
FixFileSystemPath(path);
return OpenFile(SdCardFileSystemDeviceName, path);
}
bool IsFileStubbed(ncm::TitleId title_id, const char *relative_path) {
/* Allow nullptr relative path -- those are simply not openable. */
if (relative_path == nullptr) {
return true;
}
/* Only allow stubbing in the case where we're considering SD card content. */
if (!ShouldOverrideWithSd(title_id)) {
return false;
}
char path[FS_MAX_PATH];
std::snprintf(path, FS_MAX_PATH, "/atmosphere/titles/%016lx/exefs/%s.stub", static_cast<u64>(title_id), GetRelativePathStart(relative_path));
FixFileSystemPath(path);
FILE *f = OpenFile(SdCardFileSystemDeviceName, path);
if (f == nullptr) {
return false;
}
fclose(f);
return true;
}
FILE *OpenBaseExefsFile(ncm::TitleId title_id, const char *relative_path) {
/* Allow nullptr relative path -- those are simply not openable. */
if (relative_path == nullptr) {
return nullptr;
}
/* Check if stubbed. */
if (IsFileStubbed(title_id, relative_path)) {
return nullptr;
}
return OpenFile(CodeFileSystemDeviceName, relative_path);
}
}
/* ScopedCodeMount functionality. */
ScopedCodeMount::ScopedCodeMount(const ncm::TitleLocation &loc) : is_code_mounted(false), is_hbl_mounted(false) {
this->result = this->Initialize(loc);
}
ScopedCodeMount::~ScopedCodeMount() {
/* Unmount devices. */
if (this->is_code_mounted) {
fsdevUnmountDevice(CodeFileSystemDeviceName);
}
if (this->is_hbl_mounted) {
fsdevUnmountDevice(HblFileSystemDeviceName);
}
/* Unmounting code means we should invalidate our configuration cache. */
InvalidateShouldOverrideCache();
}
Result ScopedCodeMount::MountCodeFileSystem(const ncm::TitleLocation &loc) {
char path[FS_MAX_PATH];
/* Try to get the content path. */
R_TRY(ResolveContentPath(path, loc));
/* Try to mount the content path. */
FsFileSystem fs;
R_TRY(fsldrOpenCodeFileSystem(static_cast<u64>(loc.title_id), path, &fs));
if(fsdevMountDevice(CodeFileSystemDeviceName, fs) == -1) {
std::abort();
}
/* Note that we mounted code. */
this->is_code_mounted = true;
return ResultSuccess;
}
Result ScopedCodeMount::MountSdCardCodeFileSystem(const ncm::TitleLocation &loc) {
char path[FS_MAX_PATH];
/* Print and fix path. */
std::snprintf(path, FS_MAX_PATH, "%s:/atmosphere/titles/%016lx/exefs.nsp", SdCardStorageMountPoint, static_cast<u64>(loc.title_id));
FixFileSystemPath(path);
R_TRY(MountNspFileSystem(CodeFileSystemDeviceName, path));
/* Note that we mounted code. */
this->is_code_mounted = true;
return ResultSuccess;
}
Result ScopedCodeMount::MountHblFileSystem() {
char path[FS_MAX_PATH];
/* Print and fix path. */
std::snprintf(path, FS_MAX_PATH, "%s:/%s", SdCardStorageMountPoint, GetRelativePathStart(cfg::GetHblPath()));
FixFileSystemPath(path);
R_TRY(MountNspFileSystem(HblFileSystemDeviceName, path));
/* Note that we mounted HBL. */
this->is_hbl_mounted = true;
return ResultSuccess;
}
void ContentManagement::TryMountHblNspOnSd() {
char path[FS_MAX_PATH + 1];
strncpy(path, g_hbl_sd_path, FS_MAX_PATH);
path[FS_MAX_PATH] = 0;
for (unsigned int i = 0; i < FS_MAX_PATH && path[i] != '\x00'; i++) {
if (path[i] == '\\') {
path[i] = '/';
}
}
if (g_has_initialized_fs_dev && !g_mounted_hbl_nsp && R_SUCCEEDED(fsOpenFileSystemWithId(&g_HblFileSystem, 0, FsFileSystemType_ApplicationPackage, path))) {
fsdevMountDevice("hbl", g_HblFileSystem);
g_mounted_hbl_nsp = true;
Result ScopedCodeMount::Initialize(const ncm::TitleLocation &loc) {
bool is_sd_initialized = cfg::IsSdCardInitialized();
/* Check if we're ready to mount the SD card. */
if (!g_has_mounted_sd_card) {
if (is_sd_initialized) {
R_ASSERT(MountSdCardFileSystem());
g_has_mounted_sd_card = true;
}
}
Result ContentManagement::MountCodeNspOnSd(u64 tid) {
char path[FS_MAX_PATH+1] = {0};
snprintf(path, FS_MAX_PATH, "@Sdcard:/atmosphere/titles/%016lx/exefs.nsp", tid);
/* Check if we should override contents. */
if (ShouldOverrideWithHbl(loc.title_id)) {
/* Try to mount HBL. */
this->MountHblFileSystem();
}
if (ShouldOverrideWithSd(loc.title_id)) {
/* Try to mount Code NSP on SD. */
this->MountSdCardCodeFileSystem(loc);
}
R_TRY(fsOpenFileSystemWithId(&g_CodeFileSystem, 0, FsFileSystemType_ApplicationPackage, path));
fsdevMountDevice("code", g_CodeFileSystem);
TryMountHblNspOnSd();
/* If we haven't already mounted code, mount it. */
if (!this->IsCodeMounted()) {
R_TRY(this->MountCodeFileSystem(loc));
}
return ResultSuccess;
}
Result ContentManagement::MountCodeForTidSid(Registration::TidSid *tid_sid) {
return MountCode(tid_sid->title_id, tid_sid->storage_id);
Result OpenCodeFile(FILE *&out, ncm::TitleId title_id, const char *relative_path) {
FILE *f = nullptr;
const char *ecs_device_name = ecs::Get(title_id);
if (ecs_device_name != nullptr) {
/* First priority: Open from external content. */
f = OpenFile(ecs_device_name, relative_path);
} else if (ShouldOverrideWithHbl(title_id)) {
/* Next, try to open from HBL. */
f = OpenFile(HblFileSystemDeviceName, relative_path);
} else {
/* If not ECS or HBL, try a loose file on the SD. */
if (ShouldOverrideWithSd(title_id)) {
f = OpenLooseSdFile(title_id, relative_path);
}
Result ContentManagement::ResolveContentPath(char *out_path, u64 tid, FsStorageId sid) {
char path[FS_MAX_PATH] = {0};
/* If we fail, try the original exefs. */
if (f == nullptr) {
f = OpenBaseExefsFile(title_id, relative_path);
}
}
/* If nothing worked, we failed to find the path. */
if (f == nullptr) {
return ResultFsPathNotFound;
}
out = f;
return ResultSuccess;
}
Result OpenCodeFileFromBaseExefs(FILE *&out, ncm::TitleId title_id, const char *relative_path) {
/* Open the file. */
FILE *f = OpenBaseExefsFile(title_id, relative_path);
if (f == nullptr) {
return ResultFsPathNotFound;
}
out = f;
return ResultSuccess;
}
/* Redirection API. */
Result ResolveContentPath(char *out_path, const ncm::TitleLocation &loc) {
char path[FS_MAX_PATH];
/* Try to get the path from the registered resolver. */
LrRegisteredLocationResolver reg;
R_TRY(lrOpenRegisteredLocationResolver(&reg));
ON_SCOPE_EXIT { serviceClose(&reg.s); };
R_TRY_CATCH(lrRegLrResolveProgramPath(&reg, tid, path)) {
R_TRY_CATCH(lrRegLrResolveProgramPath(&reg, static_cast<u64>(loc.title_id), path)) {
R_CATCH(ResultLrProgramNotFound) {
/* Program wasn't found via registered resolver, fall back to the normal resolver. */
LrLocationResolver lr;
R_TRY(lrOpenLocationResolver(sid, &lr));
R_TRY(lrOpenLocationResolver(static_cast<FsStorageId>(loc.storage_id), &lr));
ON_SCOPE_EXIT { serviceClose(&lr.s); };
R_TRY(lrLrResolveProgramPath(&lr, tid, path));
strncpy(out_path, path, FS_MAX_PATH);
return ResultSuccess;
R_TRY(lrLrResolveProgramPath(&lr, static_cast<u64>(loc.title_id), path));
}
} R_END_TRY_CATCH;
strncpy(out_path, path, FS_MAX_PATH);
std::strncpy(out_path, path, FS_MAX_PATH);
out_path[FS_MAX_PATH - 1] = '\0';
FixFileSystemPath(out_path);
return ResultSuccess;
}
Result ContentManagement::ResolveContentPathForTidSid(char *out_path, Registration::TidSid *tid_sid) {
return ResolveContentPath(out_path, tid_sid->title_id, tid_sid->storage_id);
}
Result ContentManagement::RedirectContentPath(const char *path, u64 tid, FsStorageId sid) {
Result RedirectContentPath(const char *path, const ncm::TitleLocation &loc) {
LrLocationResolver lr;
R_TRY(lrOpenLocationResolver(sid, &lr));
R_TRY(lrOpenLocationResolver(static_cast<FsStorageId>(loc.storage_id), &lr));
ON_SCOPE_EXIT { serviceClose(&lr.s); };
return lrLrRedirectProgramPath(&lr, tid, path);
return lrLrRedirectProgramPath(&lr, static_cast<u64>(loc.title_id), path);
}
Result ContentManagement::RedirectContentPathForTidSid(const char *path, Registration::TidSid *tid_sid) {
return RedirectContentPath(path, tid_sid->title_id, tid_sid->storage_id);
}
Result RedirectHtmlDocumentPathForHbl(const ncm::TitleLocation &loc) {
char path[FS_MAX_PATH];
void ContentManagement::RedirectHtmlDocumentPathForHbl(u64 tid, FsStorageId sid) {
/* Open a location resolver. */
LrLocationResolver lr;
char path[FS_MAX_PATH] = {0};
/* Open resolver. */
if (R_FAILED(lrOpenLocationResolver(sid, &lr))) {
return;
}
/* Ensure close on exit. */
R_TRY(lrOpenLocationResolver(static_cast<FsStorageId>(loc.storage_id), &lr));
ON_SCOPE_EXIT { serviceClose(&lr.s); };
/* Only redirect the HTML document path if there is not one already. */
if (R_SUCCEEDED(lrLrResolveApplicationHtmlDocumentPath(&lr, tid, path))) {
return;
/* If there's already a Html Document path, we don't need to set one. */
if (R_SUCCEEDED(lrLrResolveApplicationHtmlDocumentPath(&lr, static_cast<u64>(loc.title_id), path))) {
return ResultSuccess;
}
/* We just need to set this to any valid NCA path. Let's use the executable path. */
if (R_FAILED(lrLrResolveProgramPath(&lr, tid, path))) {
return;
}
lrLrRedirectApplicationHtmlDocumentPath(&lr, tid, path);
}
bool ContentManagement::HasCreatedTitle(u64 tid) {
return std::find(g_created_titles.begin(), g_created_titles.end(), tid) != g_created_titles.end();
}
void ContentManagement::SetCreatedTitle(u64 tid) {
if (!HasCreatedTitle(tid)) {
g_created_titles.push_back(tid);
}
}
static OverrideKey ParseOverrideKey(const char *value) {
OverrideKey cfg;
/* Parse on by default. */
if (value[0] == '!') {
cfg.override_by_default = true;
value++;
} else {
cfg.override_by_default = false;
}
/* Parse key combination. */
if (strcasecmp(value, "A") == 0) {
cfg.key_combination = KEY_A;
} else if (strcasecmp(value, "B") == 0) {
cfg.key_combination = KEY_B;
} else if (strcasecmp(value, "X") == 0) {
cfg.key_combination = KEY_X;
} else if (strcasecmp(value, "Y") == 0) {
cfg.key_combination = KEY_Y;
} else if (strcasecmp(value, "LS") == 0) {
cfg.key_combination = KEY_LSTICK;
} else if (strcasecmp(value, "RS") == 0) {
cfg.key_combination = KEY_RSTICK;
} else if (strcasecmp(value, "L") == 0) {
cfg.key_combination = KEY_L;
} else if (strcasecmp(value, "R") == 0) {
cfg.key_combination = KEY_R;
} else if (strcasecmp(value, "ZL") == 0) {
cfg.key_combination = KEY_ZL;
} else if (strcasecmp(value, "ZR") == 0) {
cfg.key_combination = KEY_ZR;
} else if (strcasecmp(value, "PLUS") == 0) {
cfg.key_combination = KEY_PLUS;
} else if (strcasecmp(value, "MINUS") == 0) {
cfg.key_combination = KEY_MINUS;
} else if (strcasecmp(value, "DLEFT") == 0) {
cfg.key_combination = KEY_DLEFT;
} else if (strcasecmp(value, "DUP") == 0) {
cfg.key_combination = KEY_DUP;
} else if (strcasecmp(value, "DRIGHT") == 0) {
cfg.key_combination = KEY_DRIGHT;
} else if (strcasecmp(value, "DDOWN") == 0) {
cfg.key_combination = KEY_DDOWN;
} else if (strcasecmp(value, "SL") == 0) {
cfg.key_combination = KEY_SL;
} else if (strcasecmp(value, "SR") == 0) {
cfg.key_combination = KEY_SR;
} else {
cfg.key_combination = 0;
}
return cfg;
}
static int LoaderIniHandler(void *user, const char *section, const char *name, const char *value) {
/* Taken and modified, with love, from Rajkosto's implementation. */
if (strcasecmp(section, "hbl_config") == 0) {
if (strcasecmp(name, "title_id") == 0) {
if (strcasecmp(value, "app") == 0) {
/* DEPRECATED */
g_hbl_override_config.override_any_app = true;
g_hbl_override_config.title_id = 0;
} else {
u64 override_tid = strtoul(value, NULL, 16);
if (override_tid != 0) {
g_hbl_override_config.title_id = override_tid;
}
}
} else if (strcasecmp(name, "path") == 0) {
while (*value == '/' || *value == '\\') {
value++;
}
snprintf(g_hbl_sd_path, FS_MAX_PATH, "@Sdcard:/%s", value);
g_hbl_sd_path[FS_MAX_PATH] = 0;
} else if (strcasecmp(name, "override_key") == 0) {
g_hbl_override_config.override_key = ParseOverrideKey(value);
} else if (strcasecmp(name, "override_any_app") == 0) {
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) {
g_hbl_override_config.override_any_app = true;
} else if (strcasecmp(value, "false") == 0 || strcasecmp(value, "0") == 0) {
g_hbl_override_config.override_any_app = false;
} else {
/* I guess we default to not changing the value? */
}
}
} else if (strcasecmp(section, "default_config") == 0) {
if (strcasecmp(name, "override_key") == 0) {
g_default_override_key = ParseOverrideKey(value);
}
} else {
return 0;
}
return 1;
}
static int LoaderTitleSpecificIniHandler(void *user, const char *section, const char *name, const char *value) {
/* We'll output an override key when relevant. */
OverrideKey *user_cfg = reinterpret_cast<OverrideKey *>(user);
if (strcasecmp(section, "override_config") == 0) {
if (strcasecmp(name, "override_key") == 0) {
*user_cfg = ParseOverrideKey(value);
}
} else {
return 0;
}
return 1;
}
void ContentManagement::RefreshConfigurationData() {
FILE *config = fopen("sdmc:/atmosphere/loader.ini", "r");
if (config == NULL) {
return;
}
std::fill(g_config_ini_data, g_config_ini_data + 0x800, 0);
fread(g_config_ini_data, 1, 0x7FF, config);
fclose(config);
ini_parse_string(g_config_ini_data, LoaderIniHandler, NULL);
}
void ContentManagement::TryMountSdCard() {
/* Mount SD card, if psc, bus, and pcv have been created. */
if (!g_has_initialized_fs_dev && HasCreatedTitle(TitleId_Psc) && HasCreatedTitle(TitleId_Bus) && HasCreatedTitle(TitleId_Pcv)) {
bool can_mount = true;
DoWithSmSession([&]() {
Handle tmp_hnd = 0;
static const char * const required_active_services[] = {"pcv", "gpio", "pinmux", "psc:c"};
for (unsigned int i = 0; i < sizeof(required_active_services) / sizeof(required_active_services[0]); i++) {
if (R_FAILED(smGetServiceOriginal(&tmp_hnd, smEncodeName(required_active_services[i])))) {
can_mount = false;
break;
} else {
svcCloseHandle(tmp_hnd);
}
}
});
if (can_mount && R_SUCCEEDED(fsdevMountSdmc())) {
g_has_initialized_fs_dev = true;
}
}
}
static bool IsHBLTitleId(u64 tid) {
return ((g_hbl_override_config.override_any_app && TitleIdIsApplication(tid)) || (tid == g_hbl_override_config.title_id));
}
OverrideKey ContentManagement::GetTitleOverrideKey(u64 tid) {
OverrideKey cfg = g_default_override_key;
char path[FS_MAX_PATH+1] = {0};
snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/config.ini", tid);
FILE *config = fopen(path, "r");
if (config != NULL) {
ON_SCOPE_EXIT { fclose(config); };
/* Parse current title ini. */
ini_parse_file(config, LoaderTitleSpecificIniHandler, &cfg);
}
return cfg;
}
static bool ShouldOverrideContents(OverrideKey *cfg) {
u64 kDown = 0;
bool keys_triggered = (R_SUCCEEDED(HidManagement::GetKeysHeld(&kDown)) && ((kDown & cfg->key_combination) != 0));
return g_has_initialized_fs_dev && (cfg->override_by_default ^ keys_triggered);
}
bool ContentManagement::ShouldOverrideContentsWithHBL(u64 tid) {
if (g_mounted_hbl_nsp && tid >= TitleId_AppletStart && HasCreatedTitle(TitleId_AppletQlaunch)) {
/* Return whether we should override contents with HBL. */
return IsHBLTitleId(tid) && ShouldOverrideContents(&g_hbl_override_config.override_key);
} else {
/* Don't override if we failed to mount HBL or haven't launched qlaunch. */
return false;
}
}
bool ContentManagement::ShouldOverrideContentsWithSD(u64 tid) {
if (g_has_initialized_fs_dev) {
if (tid >= TitleId_AppletStart && HasCreatedTitle(TitleId_AppletQlaunch)) {
/* Check whether we should override with non-HBL. */
OverrideKey title_cfg = GetTitleOverrideKey(tid);
return ShouldOverrideContents(&title_cfg);
} else {
/* Always redirect before qlaunch. */
return true;
}
} else {
/* Never redirect before we can do so. */
return false;
}
}
/* SetExternalContentSource extension */
ContentManagement::ExternalContentSource *ContentManagement::GetExternalContentSource(u64 tid) {
auto i = g_external_content_sources.find(tid);
if (i == g_external_content_sources.end()) {
return nullptr;
} else {
return &i->second;
}
}
Result ContentManagement::SetExternalContentSource(u64 tid, FsFileSystem filesystem) {
if (g_external_content_sources.size() >= 16) {
return ResultLoaderTooManyArguments; /* TODO: Is this an appropriate error? */
}
/* Remove any existing ECS for this title. */
ClearExternalContentSource(tid);
char mountpoint[32];
ExternalContentSource::GenerateMountpointName(tid, mountpoint, sizeof(mountpoint));
if (fsdevMountDevice(mountpoint, filesystem) == -1) {
return ResultFsMountNameAlreadyExists;
}
g_external_content_sources.emplace(
std::piecewise_construct,
std::make_tuple(tid),
std::make_tuple(tid, mountpoint));
R_TRY(lrLrResolveProgramPath(&lr, static_cast<u64>(loc.title_id), path));
R_TRY(lrLrRedirectApplicationHtmlDocumentPath(&lr, static_cast<u64>(loc.title_id), path));
return ResultSuccess;
}
void ContentManagement::ClearExternalContentSource(u64 tid) {
auto i = g_external_content_sources.find(tid);
if (i != g_external_content_sources.end()) {
g_external_content_sources.erase(i);
}
}
void ContentManagement::ExternalContentSource::GenerateMountpointName(u64 tid, char *out, size_t max_length) {
snprintf(out, max_length, "ecs-%016lx", tid);
}
ContentManagement::ExternalContentSource::ExternalContentSource(u64 tid, const char *mountpoint) : tid(tid) {
strncpy(this->mountpoint, mountpoint, sizeof(this->mountpoint));
NpdmUtils::InvalidateCache(tid);
}
ContentManagement::ExternalContentSource::~ExternalContentSource() {
fsdevUnmountDevice(mountpoint);
}

View file

@ -16,55 +16,49 @@
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include <stratosphere/ldr.hpp>
#include "ldr_registration.hpp"
namespace sts::ldr {
struct OverrideKey {
u64 key_combination;
bool override_by_default;
};
class ContentManagement {
/* Utility reference to make code mounting automatic. */
class ScopedCodeMount {
NON_COPYABLE(ScopedCodeMount);
private:
Result result;
bool is_code_mounted;
bool is_hbl_mounted;
public:
static Result MountCode(u64 tid, FsStorageId sid);
static Result MountCodeNspOnSd(u64 tid);
static void TryMountHblNspOnSd();
static Result UnmountCode();
static Result MountCodeForTidSid(Registration::TidSid *tid_sid);
ScopedCodeMount(const ncm::TitleLocation &loc);
~ScopedCodeMount();
static Result ResolveContentPath(char *out_path, u64 tid, FsStorageId sid);
static Result RedirectContentPath(const char *path, u64 tid, FsStorageId sid);
static Result ResolveContentPathForTidSid(char *out_path, Registration::TidSid *tid_sid);
static Result RedirectContentPathForTidSid(const char *path, Registration::TidSid *tid_sid);
Result GetResult() const {
return this->result;
}
static void RedirectHtmlDocumentPathForHbl(u64 tid, FsStorageId sid);
bool IsCodeMounted() const {
return this->is_code_mounted;
}
static bool HasCreatedTitle(u64 tid);
static void SetCreatedTitle(u64 tid);
static void RefreshConfigurationData();
static void TryMountSdCard();
bool IsHblMounted() const {
return this->is_hbl_mounted;
}
static OverrideKey GetTitleOverrideKey(u64 tid);
static bool ShouldOverrideContentsWithSD(u64 tid);
static bool ShouldOverrideContentsWithHBL(u64 tid);
private:
Result Initialize(const ncm::TitleLocation &loc);
/* SetExternalContentSource extension */
class ExternalContentSource {
public:
static void GenerateMountpointName(u64 tid, char *out, size_t max_length);
ExternalContentSource(u64 tid, const char *mountpoint);
~ExternalContentSource();
ExternalContentSource(const ExternalContentSource &other) = delete;
ExternalContentSource(ExternalContentSource &&other) = delete;
ExternalContentSource &operator=(const ExternalContentSource &other) = delete;
ExternalContentSource &operator=(ExternalContentSource &&other) = delete;
const u64 tid;
char mountpoint[32];
};
static ExternalContentSource *GetExternalContentSource(u64 tid); /* returns nullptr if no ECS is set */
static Result SetExternalContentSource(u64 tid, FsFileSystem filesystem); /* takes ownership of filesystem */
static void ClearExternalContentSource(u64 tid);
Result MountCodeFileSystem(const ncm::TitleLocation &loc);
Result MountSdCardCodeFileSystem(const ncm::TitleLocation &loc);
Result MountHblFileSystem();
};
/* Content Management API. */
Result OpenCodeFile(FILE *&out, ncm::TitleId title_id, const char *relative_path);
Result OpenCodeFileFromBaseExefs(FILE *&out, ncm::TitleId title_id, const char *relative_path);
/* Redirection API. */
Result ResolveContentPath(char *out_path, const ncm::TitleLocation &loc);
Result RedirectContentPath(const char *path, const ncm::TitleLocation &loc);
Result RedirectHtmlDocumentPathForHbl(const ncm::TitleLocation &loc);
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <cstdio>
#include <algorithm>
#include <stratosphere.hpp>
#include "ldr_debug_monitor.hpp"
#include "ldr_launch_queue.hpp"
#include "ldr_registration.hpp"
Result DebugMonitorService::AddTitleToLaunchQueue(u64 tid, InPointer<char> args, u32 args_size) {
if (args.num_elements < args_size) args_size = args.num_elements;
return LaunchQueue::Add(tid, args.pointer, args_size);
}
void DebugMonitorService::ClearLaunchQueue() {
LaunchQueue::Clear();
}
Result DebugMonitorService::GetProcessModuleInfo(Out<u32> count, OutPointerWithClientSize<LoaderModuleInfo> out, u64 pid) {
/* Zero out the output memory. */
std::memset(out.pointer, 0, out.num_elements * sizeof(LoaderModuleInfo));
/* Actually return the nso infos. */
return Registration::GetProcessModuleInfo(out.pointer, out.num_elements, pid, count.GetPointer());
}

View file

@ -1,40 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
enum DebugMonitorServiceCmd {
Dmnt_Cmd_AddTitleToLaunchQueue = 0,
Dmnt_Cmd_ClearLaunchQueue = 1,
Dmnt_Cmd_GetProcessModuleInfo = 2
};
class DebugMonitorService final : public IServiceObject {
private:
/* Actual commands. */
Result AddTitleToLaunchQueue(u64 tid, InPointer<char> args, u32 args_size);
void ClearLaunchQueue();
Result GetProcessModuleInfo(Out<u32> count, OutPointerWithClientSize<LoaderModuleInfo> out, u64 pid);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<Dmnt_Cmd_AddTitleToLaunchQueue, &DebugMonitorService::AddTitleToLaunchQueue>(),
MakeServiceCommandMeta<Dmnt_Cmd_ClearLaunchQueue, &DebugMonitorService::ClearLaunchQueue>(),
MakeServiceCommandMeta<Dmnt_Cmd_GetProcessModuleInfo, &DebugMonitorService::GetProcessModuleInfo>(),
};
};

View file

@ -0,0 +1,102 @@
/*
* Copyright (c) 2018-2019 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 <unordered_map>
#include "ldr_ecs.hpp"
namespace sts::ldr::ecs {
namespace {
/* Convenience definition. */
constexpr size_t DeviceNameSizeMax = 0x20;
constexpr size_t MaxExternalContentSourceCount = 0x10;
/* Types. */
class ExternalContentSource {
NON_COPYABLE(ExternalContentSource);
NON_MOVEABLE(ExternalContentSource);
private:
char device_name[DeviceNameSizeMax];
public:
ExternalContentSource(const char *dn){
std::strncpy(this->device_name, dn, sizeof(this->device_name) - 1);
this->device_name[sizeof(this->device_name) - 1] = '\0';
}
~ExternalContentSource() {
fsdevUnmountDevice(this->device_name);
}
const char *GetDeviceName() const {
return this->device_name;
}
};
/* Global storage. */
std::unordered_map<u64, ExternalContentSource> g_map;
}
/* API. */
const char *Get(ncm::TitleId title_id) {
auto it = g_map.find(static_cast<u64>(title_id));
if (it == g_map.end()) {
return nullptr;
}
return it->second.GetDeviceName();
}
Result Set(Handle *out, ncm::TitleId title_id) {
if (g_map.size() >= MaxExternalContentSourceCount) {
/* TODO: Is this an appropriate error? */
return ResultLoaderTooManyArguments;
}
/* Clear any sources. */
R_ASSERT(Clear(title_id));
/* Generate mountpoint. */
char device_name[DeviceNameSizeMax];
std::snprintf(device_name, DeviceNameSizeMax, "ecs-%016lx", static_cast<u64>(title_id));
/* Create session. */
AutoHandle server, client;
R_TRY(svcCreateSession(server.GetPointer(), client.GetPointer(), 0, 0));
/* Create service. */
Service srv;
serviceCreate(&srv, client.Move());
FsFileSystem fs = { srv };
/* Try to mount. */
if (fsdevMountDevice(device_name, fs) == -1) {
serviceClose(&srv);
return ResultFsMountNameAlreadyExists;
}
/* Add to map. */
g_map.emplace(static_cast<u64>(title_id), device_name);
*out = server.Move();
return ResultSuccess;
}
Result Clear(ncm::TitleId title_id) {
/* Delete if present. */
g_map.erase(static_cast<u64>(title_id));
return ResultSuccess;
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
#include <stratosphere/ldr.hpp>
namespace sts::ldr::ecs {
/* External Content Source API. */
const char *Get(ncm::TitleId title_id);
Result Set(Handle *out, ncm::TitleId title_id);
Result Clear(ncm::TitleId title_id);
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <string.h>
#include "ldr_content_management.hpp"
#include "ldr_hid.hpp"
Result HidManagement::GetKeysHeld(u64 *keys) {
if (!ContentManagement::HasCreatedTitle(TitleId_Hid)) {
return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
}
if (!serviceIsActive(hidGetSessionService())) {
bool initialized = false;
DoWithSmSession([&]() {
if (R_SUCCEEDED(hidInitialize())) {
initialized = true;
}
});
if (!initialized) {
return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
}
}
hidScanInput();
*keys = 0;
for (int controller = 0; controller < 10; controller++) {
*keys |= hidKeysHeld((HidControllerID) controller);
}
return ResultSuccess;
}

View file

@ -1,102 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
#include <algorithm>
#include <array>
#include <cstdio>
#include "ldr_launch_queue.hpp"
#include "meta_tools.hpp"
static std::array<LaunchQueue::LaunchItem, LAUNCH_QUEUE_SIZE> g_launch_queue = {};
Result LaunchQueue::Add(u64 tid, const char *args, u64 arg_size) {
if (arg_size > LAUNCH_QUEUE_ARG_SIZE_MAX) {
return ResultLoaderTooLongArgument;
}
int idx = GetFreeIndex(tid);
if (idx == LAUNCH_QUEUE_FULL) {
return ResultLoaderTooManyArguments;
}
g_launch_queue[idx].tid = tid;
g_launch_queue[idx].arg_size = arg_size;
std::copy(args, args + arg_size, g_launch_queue[idx].args);
return ResultSuccess;
}
Result LaunchQueue::AddCopy(u64 tid_base, u64 tid) {
int idx = GetIndex(tid_base);
if (idx == LAUNCH_QUEUE_FULL) {
return ResultSuccess;
}
return Add(tid, g_launch_queue[idx].args, g_launch_queue[idx].arg_size);
}
Result LaunchQueue::AddItem(const LaunchItem *item) {
if (item->arg_size > LAUNCH_QUEUE_ARG_SIZE_MAX) {
return ResultLoaderTooLongArgument;
}
int idx = GetFreeIndex(item->tid);
if (idx == LAUNCH_QUEUE_FULL) {
return ResultLoaderTooManyArguments;
}
g_launch_queue[idx] = *item;
return ResultSuccess;
}
int LaunchQueue::GetIndex(u64 tid) {
auto it = std::find_if(g_launch_queue.begin(), g_launch_queue.end(), member_equals_fn(&LaunchQueue::LaunchItem::tid, tid));
if (it == g_launch_queue.end()) {
return LAUNCH_QUEUE_FULL;
}
return std::distance(g_launch_queue.begin(), it);
}
int LaunchQueue::GetFreeIndex(u64 tid) {
for (unsigned int i = 0; i < LAUNCH_QUEUE_SIZE; i++) {
if (g_launch_queue[i].tid == tid || g_launch_queue[i].tid == 0x0) {
return i;
}
}
return LAUNCH_QUEUE_FULL;
}
bool LaunchQueue::Contains(u64 tid) {
return GetIndex(tid) != LAUNCH_QUEUE_FULL;
}
void LaunchQueue::Clear() {
for (unsigned int i = 0; i < LAUNCH_QUEUE_SIZE; i++) {
g_launch_queue[i].tid = 0;
}
}
LaunchQueue::LaunchItem *LaunchQueue::GetItem(u64 tid) {
int idx = GetIndex(tid);
if (idx == LAUNCH_QUEUE_FULL) {
return NULL;
}
return &g_launch_queue[idx];
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2018-2019 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 <set>
#include <stratosphere/pm.hpp>
#include "ldr_launch_record.hpp"
namespace sts::ldr {
namespace {
/* Global cache. */
std::set<u64> g_launched_titles;
}
/* Launch Record API. */
bool HasLaunchedTitle(ncm::TitleId title_id) {
return g_launched_titles.find(static_cast<u64>(title_id)) != g_launched_titles.end();
}
void SetLaunchedTitle(ncm::TitleId title_id) {
g_launched_titles.insert(static_cast<u64>(title_id));
}
}
/* Loader wants to override this libstratosphere function, which is weakly linked. */
/* This is necessary to prevent circular dependencies. */
namespace sts::pm::info {
Result HasLaunchedTitle(bool *out, u64 title_id) {
*out = ldr::HasLaunchedTitle(ncm::TitleId{title_id});
return ResultSuccess;
}
}

View file

@ -16,8 +16,13 @@
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include <stratosphere/ldr.hpp>
class HidManagement {
public:
static Result GetKeysHeld(u64 *keys);
};
namespace sts::ldr {
/* Launch Record API. */
bool HasLaunchedTitle(ncm::TitleId title_id);
void SetLaunchedTitle(ncm::TitleId title_id);
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2018-2019 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 <limits>
#include "ldr_arguments.hpp"
#include "ldr_content_management.hpp"
#include "ldr_ecs.hpp"
#include "ldr_process_creation.hpp"
#include "ldr_launch_record.hpp"
#include "ldr_loader_service.hpp"
#include "ldr_ro_manager.hpp"
namespace sts::ldr {
/* Official commands. */
Result LoaderService::CreateProcess(Out<MovedHandle> proc_h, PinId id, u32 flags, CopiedHandle reslimit) {
AutoHandle reslimit_holder(reslimit.GetValue());
ncm::TitleLocation loc;
char path[FS_MAX_PATH];
/* Get location. */
R_TRY(ldr::ro::GetTitleLocation(&loc, id));
if (loc.storage_id != static_cast<u8>(ncm::StorageId::None)) {
R_TRY(ResolveContentPath(path, loc));
}
return ldr::CreateProcess(proc_h.GetHandlePointer(), id, loc, path, flags, reslimit_holder.Get());
}
Result LoaderService::GetProgramInfo(OutPointerWithServerSize<ProgramInfo, 0x1> out_program_info, ncm::TitleLocation loc) {
/* Zero output. */
ProgramInfo *out = out_program_info.pointer;
std::memset(out, 0, sizeof(*out));
R_TRY(ldr::GetProgramInfo(out, loc));
if (loc.storage_id != static_cast<u8>(ncm::StorageId::None) && loc.title_id != out->title_id) {
char path[FS_MAX_PATH];
const ncm::TitleLocation new_loc = ncm::MakeTitleLocation(out->title_id, static_cast<ncm::StorageId>(loc.storage_id));
R_TRY(ResolveContentPath(path, loc));
R_TRY(RedirectContentPath(path, new_loc));
const auto arg_info = args::Get(loc.title_id);
if (arg_info != nullptr) {
R_TRY(args::Set(new_loc.title_id, arg_info->args, arg_info->args_size));
}
}
return ResultSuccess;
}
Result LoaderService::PinTitle(Out<PinId> out_id, ncm::TitleLocation loc) {
return ldr::ro::PinTitle(out_id.GetPointer(), loc);
}
Result LoaderService::UnpinTitle(PinId id) {
return ldr::ro::UnpinTitle(id);
}
Result LoaderService::SetTitleArguments(ncm::TitleId title_id, InPointer<char> args, u32 args_size) {
return args::Set(title_id, args.pointer, std::min(args.num_elements, size_t(args_size)));
}
Result LoaderService::ClearArguments() {
return args::Clear();
}
Result LoaderService::GetProcessModuleInfo(Out<u32> count, OutPointerWithClientSize<ModuleInfo> out, u64 process_id) {
if (out.num_elements > std::numeric_limits<s32>::max()) {
return ResultLoaderInvalidSize;
}
return ldr::ro::GetProcessModuleInfo(count.GetPointer(), out.pointer, out.num_elements, process_id);
}
/* Atmosphere commands. */
Result LoaderService::AtmosphereSetExternalContentSource(Out<MovedHandle> out, ncm::TitleId title_id) {
return ecs::Set(out.GetHandlePointer(), title_id);
}
void LoaderService::AtmosphereClearExternalContentSource(ncm::TitleId title_id) {
R_ASSERT(ecs::Clear(title_id));
}
void LoaderService::AtmosphereHasLaunchedTitle(Out<bool> out, ncm::TitleId title_id) {
out.SetValue(ldr::HasLaunchedTitle(title_id));
}
}

View file

@ -0,0 +1,116 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
#include <stratosphere/ldr.hpp>
namespace sts::ldr {
class LoaderService : public IServiceObject {
protected:
/* Official commands. */
virtual Result CreateProcess(Out<MovedHandle> proc_h, PinId id, u32 flags, CopiedHandle reslimit_h);
virtual Result GetProgramInfo(OutPointerWithServerSize<ProgramInfo, 0x1> out_program_info, ncm::TitleLocation loc);
virtual Result PinTitle(Out<PinId> out_id, ncm::TitleLocation loc);
virtual Result UnpinTitle(PinId id);
virtual Result SetTitleArguments(ncm::TitleId title_id, InPointer<char> args, u32 args_size);
virtual Result ClearArguments();
virtual Result GetProcessModuleInfo(Out<u32> count, OutPointerWithClientSize<ModuleInfo> out, u64 process_id);
/* Atmosphere commands. */
virtual Result AtmosphereSetExternalContentSource(Out<MovedHandle> out, ncm::TitleId title_id);
virtual void AtmosphereClearExternalContentSource(ncm::TitleId title_id);
virtual void AtmosphereHasLaunchedTitle(Out<bool> out, ncm::TitleId title_id);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
/* No commands callable, as LoaderService is abstract. */
};
};
namespace pm {
class ProcessManagerInterface final : public LoaderService {
protected:
enum class CommandId {
CreateProcess = 0,
GetProgramInfo = 1,
PinTitle = 2,
UnpinTitle = 3,
AtmosphereHasLaunchedTitle = 65000,
};
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMetaEx<CommandId::CreateProcess, &ProcessManagerInterface::CreateProcess, ProcessManagerInterface>(),
MakeServiceCommandMetaEx<CommandId::GetProgramInfo, &ProcessManagerInterface::GetProgramInfo, ProcessManagerInterface>(),
MakeServiceCommandMetaEx<CommandId::PinTitle, &ProcessManagerInterface::PinTitle, ProcessManagerInterface>(),
MakeServiceCommandMetaEx<CommandId::UnpinTitle, &ProcessManagerInterface::UnpinTitle, ProcessManagerInterface>(),
MakeServiceCommandMetaEx<CommandId::AtmosphereHasLaunchedTitle, &ProcessManagerInterface::AtmosphereHasLaunchedTitle, ProcessManagerInterface>(),
};
};
}
namespace dmnt {
class DebugMonitorInterface final : public LoaderService {
protected:
enum class CommandId {
SetTitleArguments = 0,
ClearArguments = 1,
GetProcessModuleInfo = 2,
AtmosphereHasLaunchedTitle = 65000,
};
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMetaEx<CommandId::SetTitleArguments, &DebugMonitorInterface::SetTitleArguments, DebugMonitorInterface>(),
MakeServiceCommandMetaEx<CommandId::ClearArguments, &DebugMonitorInterface::ClearArguments, DebugMonitorInterface>(),
MakeServiceCommandMetaEx<CommandId::GetProcessModuleInfo, &DebugMonitorInterface::GetProcessModuleInfo, DebugMonitorInterface>(),
MakeServiceCommandMetaEx<CommandId::AtmosphereHasLaunchedTitle, &DebugMonitorInterface::AtmosphereHasLaunchedTitle, DebugMonitorInterface>(),
};
};
}
namespace shell {
class ShellInterface final : public LoaderService {
protected:
enum class CommandId {
SetTitleArguments = 0,
ClearArguments = 1,
AtmosphereSetExternalContentSource = 65000,
AtmosphereClearExternalContentSource = 65001,
};
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMetaEx<CommandId::SetTitleArguments, &ShellInterface::SetTitleArguments, ShellInterface>(),
MakeServiceCommandMetaEx<CommandId::ClearArguments, &ShellInterface::ClearArguments, ShellInterface>(),
MakeServiceCommandMetaEx<CommandId::AtmosphereSetExternalContentSource, &ShellInterface::AtmosphereSetExternalContentSource, ShellInterface>(),
MakeServiceCommandMetaEx<CommandId::AtmosphereClearExternalContentSource, &ShellInterface::AtmosphereClearExternalContentSource, ShellInterface>(),
};
};
}
}

View file

@ -22,10 +22,10 @@
#include <switch.h>
#include <atmosphere.h>
#include <stratosphere.hpp>
#include <stratosphere/ncm.hpp>
#include <stratosphere/ldr.hpp>
#include "ldr_process_manager.hpp"
#include "ldr_debug_monitor.hpp"
#include "ldr_shell.hpp"
#include "ldr_loader_service.hpp"
extern "C" {
extern u32 __start__;
@ -68,14 +68,13 @@ void __libnx_initheap(void) {
void __appInit(void) {
SetFirmwareVersionForLibnx();
/* Initialize services we need (TODO: SPL) */
/* Initialize services we need. */
DoWithSmSession([&]() {
R_ASSERT(fsInitialize());
R_ASSERT(lrInitialize());
R_ASSERT(fsldrInitialize());
});
CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION);
}
@ -95,14 +94,12 @@ struct LoaderServerOptions {
int main(int argc, char **argv)
{
consoleDebugInit(debugDevice_SVC);
static auto s_server_manager = WaitableManager<LoaderServerOptions>(1);
/* Add services to manager. */
s_server_manager.AddWaitable(new ServiceServer<ProcessManagerService>("ldr:pm", 1));
s_server_manager.AddWaitable(new ServiceServer<ShellService>("ldr:shel", 3));
s_server_manager.AddWaitable(new ServiceServer<DebugMonitorService>("ldr:dmnt", 2));
s_server_manager.AddWaitable(new ServiceServer<sts::ldr::pm::ProcessManagerInterface>("ldr:pm", 1));
s_server_manager.AddWaitable(new ServiceServer<sts::ldr::shell::ShellInterface>("ldr:shel", 3));
s_server_manager.AddWaitable(new ServiceServer<sts::ldr::dmnt::DebugMonitorInterface>("ldr:dmnt", 2));
/* Loop forever, servicing our services. */
s_server_manager.Process();

View file

@ -1,113 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <cstdio>
#include "ldr_map.hpp"
Result MapUtils::LocateSpaceForMap(u64 *out, u64 out_size) {
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) {
return LocateSpaceForMapModern(out, out_size);
} else {
return LocateSpaceForMapDeprecated(out, out_size);
}
}
Result MapUtils::LocateSpaceForMapModern(u64 *out, u64 out_size) {
MemoryInfo mem_info = {};
AddressSpaceInfo address_space = {};
u32 page_info = 0;
u64 cur_base = 0, cur_end = 0;
R_TRY(GetAddressSpaceInfo(&address_space, CUR_PROCESS_HANDLE));
cur_base = address_space.addspace_base;
cur_end = cur_base + out_size;
if (cur_end <= cur_base) {
return ResultKernelOutOfMemory;
}
while (true) {
if (address_space.heap_size && (address_space.heap_base <= cur_end - 1 && cur_base <= address_space.heap_end - 1)) {
/* If we overlap the heap region, go to the end of the heap region. */
if (cur_base == address_space.heap_end) {
return ResultKernelOutOfMemory;
}
cur_base = address_space.heap_end;
} else if (address_space.map_size && (address_space.map_base <= cur_end - 1 && cur_base <= address_space.map_end - 1)) {
/* If we overlap the map region, go to the end of the map region. */
if (cur_base == address_space.map_end) {
return ResultKernelOutOfMemory;
}
cur_base = address_space.map_end;
} else {
R_ASSERT(svcQueryMemory(&mem_info, &page_info, cur_base));
if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= out_size) {
*out = cur_base;
return ResultSuccess;
}
if (mem_info.addr + mem_info.size <= cur_base) {
return ResultKernelOutOfMemory;
}
cur_base = mem_info.addr + mem_info.size;
if (cur_base >= address_space.addspace_end) {
return ResultKernelOutOfMemory;
}
}
cur_end = cur_base + out_size;
if (cur_base + out_size <= cur_base) {
return ResultKernelOutOfMemory;
}
}
}
Result MapUtils::LocateSpaceForMapDeprecated(u64 *out, u64 out_size) {
MemoryInfo mem_info = {};
u32 page_info = 0;
u64 cur_base = 0x8000000ULL;
do {
R_TRY(svcQueryMemory(&mem_info, &page_info, cur_base));
if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= out_size) {
*out = cur_base;
return ResultSuccess;
}
const u64 mem_end = mem_info.addr + mem_info.size;
if (mem_info.type == 0x10 || mem_end < cur_base || (mem_end >> 31)) {
return ResultKernelOutOfMemory;
}
cur_base = mem_end;
} while (true);
}
Result MapUtils::GetAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h) {
R_TRY(svcGetInfo(&out->heap_base, 4, process_h, 0));
R_TRY(svcGetInfo(&out->heap_size, 5, process_h, 0));
R_TRY(svcGetInfo(&out->map_base, 2, process_h, 0));
R_TRY(svcGetInfo(&out->map_size, 3, process_h, 0));
R_TRY(svcGetInfo(&out->addspace_base, 12, process_h, 0));
R_TRY(svcGetInfo(&out->addspace_size, 13, process_h, 0));
out->heap_end = out->heap_base + out->heap_size;
out->map_end = out->map_base + out->map_size;
out->addspace_end = out->addspace_base + out->addspace_size;
return ResultSuccess;
}

View file

@ -1,78 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
class MapUtils {
public:
struct AddressSpaceInfo {
u64 heap_base;
u64 heap_size;
u64 heap_end;
u64 map_base;
u64 map_size;
u64 map_end;
u64 addspace_base;
u64 addspace_size;
u64 addspace_end;
};
static Result GetAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h);
static Result LocateSpaceForMapDeprecated(u64 *out, u64 out_size);
static Result LocateSpaceForMapModern(u64 *out, u64 out_size);
static Result LocateSpaceForMap(u64 *out, u64 out_size);
};
class AutoCloseMap {
private:
void *mapped_address = nullptr;
u64 base_address = 0;
u64 size = 0;
Handle process_handle = 0;
public:
~AutoCloseMap() {
Close();
}
void *GetMappedAddress() {
return this->mapped_address;
}
Result Open(Handle process_h, u64 address, u64 size) {
u64 try_address;
/* Find an address to map at. */
R_TRY(MapUtils::LocateSpaceForMap(&try_address, size));
/* Actually map at address. */
void *try_map_address = reinterpret_cast<void *>(try_address);
R_TRY(svcMapProcessMemory(try_map_address, process_h, address, size));
this->mapped_address = try_map_address;
this->process_handle = process_h;
this->base_address = address;
this->size = size;
return ResultSuccess;
}
void Close() {
if (this->mapped_address) {
R_ASSERT(svcUnmapProcessMemory(this->mapped_address, this->process_handle, this->base_address, this->size));
this->mapped_address = NULL;
}
}
};

View file

@ -0,0 +1,217 @@
/*
* Copyright (c) 2018-2019 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/cfg.hpp>
#include "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
#include "ldr_meta.hpp"
namespace sts::ldr {
namespace {
/* Convenience definitions. */
constexpr size_t MetaCacheBufferSize = 0x8000;
constexpr const char *MetaFilePath = "/main.npdm";
/* Types. */
struct MetaCache {
Meta meta;
u8 buffer[MetaCacheBufferSize];
};
/* Global storage. */
ncm::TitleId g_cached_title_id;
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) {
if (!(size >= min_size && allowed_start <= start && start <= allowed_end && start + size <= allowed_end)) {
return ResultLoaderInvalidMeta;
}
return ResultSuccess;
}
Result ValidateNpdm(const Npdm *npdm, size_t size) {
/* Validate magic. */
if (npdm->magic != Npdm::Magic) {
return ResultLoaderInvalidMeta;
}
/* Validate flags. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_700) {
/* 7.0.0 added 0x10 as a valid bit to NPDM flags. */
if (npdm->flags & ~0x1F) {
return ResultLoaderInvalidMeta;
}
} else {
if (npdm->flags & ~0xF) {
return ResultLoaderInvalidMeta;
}
}
/* 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)));
return ResultSuccess;
}
Result ValidateAcid(const Acid *acid, size_t size) {
/* Validate magic. */
if (acid->magic != Acid::Magic) {
return ResultLoaderInvalidMeta;
}
/* TODO: Check if retail flag is set if not development hardware. */
/* 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));
return ResultSuccess;
}
Result ValidateAci(const Aci *aci, size_t size) {
/* Validate magic. */
if (aci->magic != Aci::Magic) {
return ResultLoaderInvalidMeta;
}
/* 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));
return ResultSuccess;
}
Result LoadMetaFromFile(FILE *f, MetaCache *cache) {
/* Reset cache. */
cache->meta = {};
/* Read from file. */
size_t npdm_size = 0;
{
/* Get file size. */
fseek(f, 0, SEEK_END);
npdm_size = ftell(f);
fseek(f, 0, SEEK_SET);
/* Read data into cache buffer. */
if (npdm_size > MetaCacheBufferSize || fread(cache->buffer, npdm_size, 1, f) != 1) {
return ResultLoaderTooLargeMeta;
}
}
/* Ensure size is big enough. */
if (npdm_size < sizeof(Npdm)) {
return ResultLoaderInvalidMeta;
}
/* Validate the meta. */
{
Meta *meta = &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;
}
return ResultSuccess;
}
}
/* API. */
Result LoadMeta(Meta *out_meta, ncm::TitleId title_id) {
FILE *f = nullptr;
/* Try to load meta from file. */
R_TRY(OpenCodeFile(f, title_id, MetaFilePath));
{
ON_SCOPE_EXIT { fclose(f); };
R_TRY(LoadMetaFromFile(f, &g_meta_cache));
}
/* Patch meta. Start by setting all title ids to the current title id. */
Meta *meta = &g_meta_cache.meta;
meta->acid->title_id_min = title_id;
meta->acid->title_id_max = title_id;
meta->aci->title_id = title_id;
/* For HBL, we need to copy some information from the base meta. */
if (cfg::IsHblOverrideKeyHeld(title_id)) {
if (R_SUCCEEDED(OpenCodeFileFromBaseExefs(f, title_id, MetaFilePath))) {
ON_SCOPE_EXIT { fclose(f); };
if (R_SUCCEEDED(LoadMetaFromFile(f, &g_original_meta_cache))) {
Meta *o_meta = &g_original_meta_cache.meta;
/* Fix pool partition. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
meta->acid->flags = (meta->acid->flags & 0xFFFFFFC3) | (o_meta->acid->flags & 0x0000003C);
}
/* Fix flags. */
const u16 program_info_flags = caps::GetProgramInfoFlags(o_meta->aci_kac, o_meta->aci->kac_size);
caps::SetProgramInfoFlags(program_info_flags, meta->acid_kac, meta->acid->kac_size);
caps::SetProgramInfoFlags(program_info_flags, meta->aci_kac, meta->aci->kac_size);
}
}
}
/* Set output. */
g_cached_title_id = title_id;
*out_meta = *meta;
return ResultSuccess;
}
Result LoadMetaFromCache(Meta *out_meta, ncm::TitleId title_id) {
if (g_cached_title_id != title_id) {
return LoadMeta(out_meta, title_id);
}
*out_meta = g_meta_cache.meta;
return ResultSuccess;
}
void InvalidateMetaCache() {
/* Set the cached title id back to zero. */
g_cached_title_id = {};
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
#include <stratosphere/ldr.hpp>
namespace sts::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;
};
/* Meta API. */
Result LoadMeta(Meta *out_meta, ncm::TitleId title_id);
Result LoadMetaFromCache(Meta *out_meta, ncm::TitleId title_id);
void InvalidateMetaCache();
}

View file

@ -1,500 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <algorithm>
#include <cstdio>
#include "ldr_npdm.hpp"
#include "ldr_registration.hpp"
#include "ldr_content_management.hpp"
static NpdmUtils::NpdmCache g_npdm_cache = {0};
static NpdmUtils::NpdmCache g_original_npdm_cache = {0};
static char g_npdm_path[FS_MAX_PATH] = {0};
Result NpdmUtils::LoadNpdmFromCache(u64 tid, NpdmInfo *out) {
if (g_npdm_cache.info.title_id != tid) {
return LoadNpdm(tid, out);
}
*out = g_npdm_cache.info;
return ResultSuccess;
}
FILE *NpdmUtils::OpenNpdmFromECS(ContentManagement::ExternalContentSource *ecs) {
std::fill(g_npdm_path, g_npdm_path + FS_MAX_PATH, 0);
snprintf(g_npdm_path, FS_MAX_PATH, "%s:/main.npdm", ecs->mountpoint);
return fopen(g_npdm_path, "rb");
}
FILE *NpdmUtils::OpenNpdmFromHBL() {
std::fill(g_npdm_path, g_npdm_path + FS_MAX_PATH, 0);
snprintf(g_npdm_path, FS_MAX_PATH, "hbl:/main.npdm");
return fopen(g_npdm_path, "rb");
}
FILE *NpdmUtils::OpenNpdmFromExeFS() {
std::fill(g_npdm_path, g_npdm_path + FS_MAX_PATH, 0);
snprintf(g_npdm_path, FS_MAX_PATH, "code:/main.npdm");
return fopen(g_npdm_path, "rb");
}
FILE *NpdmUtils::OpenNpdmFromSdCard(u64 title_id) {
std::fill(g_npdm_path, g_npdm_path + FS_MAX_PATH, 0);
snprintf(g_npdm_path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/exefs/main.npdm", title_id);
return fopen(g_npdm_path, "rb");
}
FILE *NpdmUtils::OpenNpdm(u64 title_id) {
ContentManagement::ExternalContentSource *ecs = nullptr;
if ((ecs = ContentManagement::GetExternalContentSource(title_id)) != nullptr) {
return OpenNpdmFromECS(ecs);
}
/* First, check HBL. */
if (ContentManagement::ShouldOverrideContentsWithHBL(title_id)) {
return OpenNpdmFromHBL();
}
/* Next, check other override. */
if (ContentManagement::ShouldOverrideContentsWithSD(title_id)) {
FILE *f_out = OpenNpdmFromSdCard(title_id);
if (f_out != NULL) {
return f_out;
}
}
/* Last resort: real exefs. */
return OpenNpdmFromExeFS();
}
Result NpdmUtils::LoadNpdmInternal(FILE *f_npdm, NpdmUtils::NpdmCache *cache) {
cache->info = {};
if (f_npdm == NULL) {
/* For generic "Couldn't open the file" error, just say the file doesn't exist. */
return ResultFsPathNotFound;
}
fseek(f_npdm, 0, SEEK_END);
size_t npdm_size = ftell(f_npdm);
fseek(f_npdm, 0, SEEK_SET);
if ((npdm_size > sizeof(cache->buffer)) || (fread(cache->buffer, 1, npdm_size, f_npdm) != npdm_size)) {
fclose(f_npdm);
return ResultLoaderTooLargeMeta;
}
fclose(f_npdm);
if (npdm_size < sizeof(NpdmUtils::NpdmHeader)) {
return ResultLoaderInvalidMeta;
}
/* For ease of access... */
cache->info.header = (NpdmUtils::NpdmHeader *)(cache->buffer);
NpdmInfo *info = &cache->info;
if (info->header->magic != MAGIC_META) {
return ResultLoaderInvalidMeta;
}
/* 7.0.0 added 0x10 as a valid bit to NPDM flags. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_700) {
if (info->header->mmu_flags > 0x1F) {
return ResultLoaderInvalidMeta;
}
} else {
if (info->header->mmu_flags > 0xF) {
return ResultLoaderInvalidMeta;
}
}
if (info->header->aci0_offset < sizeof(NpdmUtils::NpdmHeader) || info->header->aci0_size < sizeof(NpdmUtils::NpdmAci0) || info->header->aci0_offset + info->header->aci0_size > npdm_size) {
return ResultLoaderInvalidMeta;
}
info->aci0 = (NpdmAci0 *)(cache->buffer + info->header->aci0_offset);
if (info->aci0->magic != MAGIC_ACI0) {
return ResultLoaderInvalidMeta;
}
if (info->aci0->fah_size > info->header->aci0_size || info->aci0->fah_offset < sizeof(NpdmUtils::NpdmAci0) || info->aci0->fah_offset + info->aci0->fah_size > info->header->aci0_size) {
return ResultLoaderInvalidMeta;
}
info->aci0_fah = (void *)((uintptr_t)info->aci0 + info->aci0->fah_offset);
if (info->aci0->sac_size > info->header->aci0_size || info->aci0->sac_offset < sizeof(NpdmUtils::NpdmAci0) || info->aci0->sac_offset + info->aci0->sac_size > info->header->aci0_size) {
return ResultLoaderInvalidMeta;
}
info->aci0_sac = (void *)((uintptr_t)info->aci0 + info->aci0->sac_offset);
if (info->aci0->kac_size > info->header->aci0_size || info->aci0->kac_offset < sizeof(NpdmUtils::NpdmAci0) || info->aci0->kac_offset + info->aci0->kac_size > info->header->aci0_size) {
return ResultLoaderInvalidMeta;
}
info->aci0_kac = (void *)((uintptr_t)info->aci0 + info->aci0->kac_offset);
if (info->header->acid_offset < sizeof(NpdmUtils::NpdmHeader) || info->header->acid_size < sizeof(NpdmUtils::NpdmAcid) || info->header->acid_offset + info->header->acid_size > npdm_size) {
return ResultLoaderInvalidMeta;
}
info->acid = (NpdmAcid *)(cache->buffer + info->header->acid_offset);
if (info->acid->magic != MAGIC_ACID) {
return ResultLoaderInvalidMeta;
}
/* TODO: Check if retail flag is set if not development hardware. */
if (info->acid->fac_size > info->header->acid_size || info->acid->fac_offset < sizeof(NpdmUtils::NpdmAcid) || info->acid->fac_offset + info->acid->fac_size > info->header->acid_size) {
return ResultLoaderInvalidMeta;
}
info->acid_fac = (void *)((uintptr_t)info->acid + info->acid->fac_offset);
if (info->acid->sac_size > info->header->acid_size || info->acid->sac_offset < sizeof(NpdmUtils::NpdmAcid) || info->acid->sac_offset + info->acid->sac_size > info->header->acid_size) {
return ResultLoaderInvalidMeta;
}
info->acid_sac = (void *)((uintptr_t)info->acid + info->acid->sac_offset);
if (info->acid->kac_size > info->header->acid_size || info->acid->kac_offset < sizeof(NpdmUtils::NpdmAcid) || info->acid->kac_offset + info->acid->kac_size > info->header->acid_size) {
return ResultLoaderInvalidMeta;
}
info->acid_kac = (void *)((uintptr_t)info->acid + info->acid->kac_offset);
return ResultSuccess;
}
Result NpdmUtils::LoadNpdm(u64 tid, NpdmInfo *out) {
/* Load and validate the NPDM. */
R_TRY(LoadNpdmInternal(OpenNpdm(tid), &g_npdm_cache));
NpdmInfo *info = &g_npdm_cache.info;
/* Override the ACID/ACI0 title ID, in order to facilitate HBL takeover of any title. */
info->acid->title_id_range_min = tid;
info->acid->title_id_range_max = tid;
info->aci0->title_id = tid;
if (ContentManagement::ShouldOverrideContentsWithHBL(tid) && R_SUCCEEDED(LoadNpdmInternal(OpenNpdmFromExeFS(), &g_original_npdm_cache))) {
NpdmInfo *original_info = &g_original_npdm_cache.info;
/* Fix pool partition. */
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500)) {
info->acid->flags = (info->acid->flags & 0xFFFFFFC3) | (original_info->acid->flags & 0x0000003C);
}
/* Fix application type. */
const u32 original_application_type = GetApplicationTypeRaw((u32 *)original_info->aci0_kac, original_info->aci0->kac_size/sizeof(u32)) & 7;
u32 *caps = (u32 *)info->aci0_kac;
for (unsigned int i = 0; i < info->aci0->kac_size/sizeof(u32); i++) {
if ((caps[i] & 0x3FFF) == 0x1FFF) {
caps[i] = (caps[i] & 0xFFFE3FFF) | (original_application_type << 14);
}
}
caps = (u32 *)info->acid_kac;
for (unsigned int i = 0; i < info->acid->kac_size/sizeof(u32); i++) {
if ((caps[i] & 0x3FFF) == 0x1FFF) {
caps[i] = (caps[i] & 0xFFFE3FFF) | (original_application_type << 14);
}
}
}
/* We validated! */
info->title_id = tid;
*out = *info;
return ResultSuccess;
}
Result NpdmUtils::ValidateCapabilityAgainstRestrictions(const u32 *restrict_caps, size_t num_restrict_caps, const u32 *&cur_cap, size_t &caps_remaining) {
u32 desc = *cur_cap++;
caps_remaining--;
unsigned int low_bits = 0;
while (desc & 1) {
desc >>= 1;
low_bits++;
}
desc >>= 1;
u32 r_desc = 0;
switch (low_bits) {
case 3: /* Kernel flags. */
for (size_t i = 0; i < num_restrict_caps; i++) {
if ((restrict_caps[i] & 0xF) == 0x7) {
r_desc = restrict_caps[i] >> 4;
u32 highest_thread_prio = desc & 0x3F;
u32 r_highest_thread_prio = r_desc & 0x3F;
desc >>= 6;
r_desc >>= 6;
u32 lowest_thread_prio = desc & 0x3F;
u32 r_lowest_thread_prio = r_desc & 0x3F;
desc >>= 6;
r_desc >>= 6;
u32 lowest_cpu_id = desc & 0xFF;
u32 r_lowest_cpu_id = r_desc & 0xFF;
desc >>= 8;
r_desc >>= 8;
u32 highest_cpu_id = desc & 0xFF;
u32 r_highest_cpu_id = r_desc & 0xFF;
if (highest_thread_prio > r_highest_thread_prio) {
break;
}
if (lowest_thread_prio > highest_thread_prio) {
break;
}
if (lowest_thread_prio < r_lowest_thread_prio) {
break;
}
if (lowest_cpu_id < r_lowest_cpu_id) {
break;
}
if (lowest_cpu_id > r_highest_cpu_id) {
break;
}
if (highest_cpu_id > r_highest_cpu_id) {
break;
}
/* Valid! */
return ResultSuccess;
}
}
return ResultLoaderInvalidCapabilityKernelFlags;
case 4: /* Syscall mask. */
for (size_t i = 0; i < num_restrict_caps; i++) {
if ((restrict_caps[i] & 0x1F) == 0xF) {
r_desc = restrict_caps[i] >> 5;
u32 syscall_base = (desc >> 24);
u32 r_syscall_base = (r_desc >> 24);
if (syscall_base != r_syscall_base) {
continue;
}
u32 syscall_mask = desc & 0xFFFFFF;
u32 r_syscall_mask = r_desc & 0xFFFFFF;
if ((r_syscall_mask & syscall_mask) != syscall_mask) {
break;
}
/* Valid! */
return ResultSuccess;
}
}
return ResultLoaderInvalidCapabilitySyscallMask;
case 6: /* Map IO/Normal. */
{
if (caps_remaining == 0) {
return ResultLoaderInvalidCapabilityMapRange;
}
u32 next_cap = *cur_cap++;
caps_remaining--;
if ((next_cap & 0x7F) != 0x3F) {
return ResultLoaderInvalidCapabilityMapRange;
}
u32 next_desc = next_cap >> 7;
u32 base_addr = desc & 0xFFFFFF;
u32 base_size = next_desc & 0xFFFFFF;
/* Size check the mapping. */
if (base_size >> 20) {
return ResultLoaderInvalidCapabilityMapRange;
}
u32 base_end = base_addr + base_size;
/* Validate it's possible to validate this mapping. */
if (num_restrict_caps < 2) {
return ResultLoaderInvalidCapabilityMapRange;
}
for (size_t i = 0; i < num_restrict_caps - 1; i++) {
if ((restrict_caps[i] & 0x7F) == 0x3F) {
r_desc = restrict_caps[i] >> 7;
if ((restrict_caps[i+1] & 0x7F) != 0x3F) {
break;
}
u32 r_next_desc = restrict_caps[++i] >> 7;
u32 r_base_addr = r_desc & 0xFFFFFF;
u32 r_base_size = r_next_desc & 0xFFFFFF;
/* Size check the mapping. */
if (r_base_size >> 20) {
break;
}
u32 r_base_end = r_base_addr + r_base_size;
/* Validate is_io matches. */
if (((r_desc >> 24) & 1) ^ ((desc >> 24) & 1)) {
continue;
}
/* Validate is_ro matches. */
if (((r_next_desc >> 24) & 1) ^ ((next_desc >> 24) & 1)) {
continue;
}
/* Validate bounds. */
if (base_addr < r_base_addr || base_end > r_base_end) {
continue;
}
/* Valid! */
return ResultSuccess;
}
}
}
return ResultLoaderInvalidCapabilityMapRange;
case 7: /* Map Normal Page. */
for (size_t i = 0; i < num_restrict_caps; i++) {
if ((restrict_caps[i] & 0xFF) == 0x7F) {
r_desc = restrict_caps[i] >> 8;
if (r_desc != desc) {
continue;
}
/* Valid! */
return ResultSuccess;
}
}
return ResultLoaderInvalidCapabilityMapPage;
case 11: /* IRQ Pair. */
for (unsigned int irq_i = 0; irq_i < 2; irq_i++) {
u32 irq = desc & 0x3FF;
desc >>= 10;
if (irq != 0x3FF) {
bool found = false;
for (size_t i = 0; i < num_restrict_caps && !found; i++) {
if ((restrict_caps[i] & 0xFFF) == 0x7FF) {
r_desc = restrict_caps[i] >> 12;
u32 r_irq_0 = r_desc & 0x3FF;
r_desc >>= 10;
u32 r_irq_1 = r_desc & 0x3FF;
found |= irq == r_irq_0 || irq == r_irq_1;
found |= r_irq_0 == 0x3FF && r_irq_1 == 0x3FF;
}
}
if (!found) {
return ResultLoaderInvalidCapabilityInterruptPair;
}
}
}
return ResultSuccess;
case 13: /* App Type. */
if (num_restrict_caps) {
for (size_t i = 0; i < num_restrict_caps; i++) {
if ((restrict_caps[i] & 0x3FFF) == 0x1FFF) {
r_desc = restrict_caps[i] >> 14;
break;
}
}
} else {
r_desc = 0;
}
if (desc == r_desc) {
/* Valid! */
return ResultSuccess;
}
return ResultLoaderInvalidCapabilityApplicationType;
case 14: /* Kernel Release Version. */
if (num_restrict_caps) {
for (size_t i = 0; i < num_restrict_caps; i++) {
if ((restrict_caps[i] & 0x7FFF) == 0x3FFF) {
r_desc = restrict_caps[i] >> 15;
break;
}
}
} else {
r_desc = 0;
}
if (desc == r_desc) {
/* Valid! */
return ResultSuccess;
}
return ResultLoaderInvalidCapabilityKernelVersion;
case 15: /* Handle Table Size. */
for (size_t i = 0; i < num_restrict_caps; i++) {
if ((restrict_caps[i] & 0xFFFF) == 0x7FFF) {
r_desc = restrict_caps[i] >> 16;
desc &= 0x3FF;
r_desc &= 0x3FF;
if (desc > r_desc) {
break;
}
/* Valid! */
return ResultSuccess;
}
}
return ResultLoaderInvalidCapabilityHandleTable;
case 16: /* Debug Flags. */
if (num_restrict_caps) {
for (size_t i = 0; i < num_restrict_caps; i++) {
if ((restrict_caps[i] & 0x1FFFF) == 0xFFFF) {
r_desc = restrict_caps[i] >> 17;
break;
}
}
} else {
r_desc = 0;
}
if ((desc & ~r_desc) == 0) {
/* Valid! */
return ResultSuccess;
}
return ResultLoaderInvalidCapabilityDebugFlags;
case 32: /* Empty Descriptor. */
return ResultSuccess;
default: /* Unrecognized Descriptor. */
return ResultLoaderUnknownCapability;
}
}
Result NpdmUtils::ValidateCapabilities(const u32 *acid_caps, size_t num_acid_caps, const u32 *aci0_caps, size_t num_aci0_caps) {
const u32 *cur_cap = aci0_caps;
size_t remaining = num_aci0_caps;
while (remaining) {
/* Validate, update capabilities. cur_cap and remaining passed by reference. */
R_TRY(ValidateCapabilityAgainstRestrictions(acid_caps, num_acid_caps, cur_cap, remaining));
}
return ResultSuccess;
}
u32 NpdmUtils::GetApplicationType(const u32 *caps, size_t num_caps) {
u32 application_type = 0;
for (unsigned int i = 0; i < num_caps; i++) {
if ((caps[i] & 0x3FFF) == 0x1FFF) {
u16 app_type = (caps[i] >> 14) & 7;
if (app_type == 1) {
application_type |= 1;
} else if (app_type == 2) {
application_type |= 2;
}
}
/* After 1.0.0, allow_debug is used as bit 4. */
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_200) && (caps[i] & 0x1FFFF) == 0xFFFF) {
application_type |= (caps[i] >> 15) & 4;
}
}
return application_type;
}
/* Like GetApplicationType, except this returns the raw kac descriptor value. */
u32 NpdmUtils::GetApplicationTypeRaw(const u32 *caps, size_t num_caps) {
u32 application_type = 0;
for (unsigned int i = 0; i < num_caps; i++) {
if ((caps[i] & 0x3FFF) == 0x1FFF) {
return (caps[i] >> 14) & 7;
}
}
return application_type;
}
void NpdmUtils::InvalidateCache(u64 tid) {
if (g_npdm_cache.info.title_id == tid) {
g_npdm_cache.info = {};
}
}

View file

@ -1,116 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <cstdio>
#include "ldr_registration.hpp"
#include "ldr_content_management.hpp" /* for ExternalContentSource */
#define MAGIC_META 0x4154454D
#define MAGIC_ACI0 0x30494341
#define MAGIC_ACID 0x44494341
class NpdmUtils {
public:
struct NpdmHeader {
u32 magic;
u32 _0x4;
u32 _0x8;
u8 mmu_flags;
u8 _0xD;
u8 main_thread_prio;
u8 default_cpuid;
u32 _0x10;
u32 system_resource_size;
u32 version;
u32 main_stack_size;
char title_name[0x50];
u32 aci0_offset;
u32 aci0_size;
u32 acid_offset;
u32 acid_size;
};
struct NpdmAcid {
u8 signature[0x100];
u8 modulus[0x100];
u32 magic;
u32 size;
u32 _0x208;
u32 flags;
u64 title_id_range_min;
u64 title_id_range_max;
u32 fac_offset;
u32 fac_size;
u32 sac_offset;
u32 sac_size;
u32 kac_offset;
u32 kac_size;
u64 padding;
};
struct NpdmAci0 {
u32 magic;
u8 _0x4[0xC];
u64 title_id;
u64 _0x18;
u32 fah_offset;
u32 fah_size;
u32 sac_offset;
u32 sac_size;
u32 kac_offset;
u32 kac_size;
u64 padding;
};
struct NpdmInfo {
NpdmHeader *header;
NpdmAcid *acid;
NpdmAci0 *aci0;
void *acid_fac;
void *acid_sac;
void *acid_kac;
void *aci0_fah;
void *aci0_sac;
void *aci0_kac;
u64 title_id;
};
struct NpdmCache {
NpdmInfo info;
u8 buffer[0x8000];
};
static_assert(sizeof(NpdmHeader) == 0x80, "Incorrectly defined NpdmHeader!");
static_assert(sizeof(NpdmAcid) == 0x240, "Incorrectly defined NpdmAcid!");
static_assert(sizeof(NpdmAci0) == 0x40, "Incorrectly defined NpdmAci0!");
static u32 GetApplicationType(const u32 *caps, size_t num_caps);
static u32 GetApplicationTypeRaw(const u32 *caps, size_t num_caps);
static Result ValidateCapabilityAgainstRestrictions(const u32 *restrict_caps, size_t num_restrict_caps, const u32 *&cur_cap, size_t &caps_remaining);
static Result ValidateCapabilities(const u32 *acid_caps, size_t num_acid_caps, const u32 *aci0_caps, size_t num_aci0_caps);
static FILE *OpenNpdmFromECS(ContentManagement::ExternalContentSource *ecs);
static FILE *OpenNpdmFromHBL();
static FILE *OpenNpdmFromExeFS();
static FILE *OpenNpdmFromSdCard(u64 tid);
static FILE *OpenNpdm(u64 tid);
static Result LoadNpdm(u64 tid, NpdmInfo *out);
static Result LoadNpdmFromCache(u64 tid, NpdmInfo *out);
static void InvalidateCache(u64 tid);
private:
static Result LoadNpdmInternal(FILE *f_npdm, NpdmCache *cache);
};

View file

@ -1,357 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include "lz4.h"
#include "ldr_nso.hpp"
#include "ldr_map.hpp"
#include "ldr_patcher.hpp"
#include "ldr_content_management.hpp"
static NsoUtils::NsoHeader g_nso_headers[NSO_NUM_MAX] = {0};
static bool g_nso_present[NSO_NUM_MAX] = {0};
static char g_nso_path[FS_MAX_PATH] = {0};
FILE *NsoUtils::OpenNsoFromECS(unsigned int index, ContentManagement::ExternalContentSource *ecs) {
std::fill(g_nso_path, g_nso_path + FS_MAX_PATH, 0);
snprintf(g_nso_path, FS_MAX_PATH, "%s:/%s", ecs->mountpoint, NsoUtils::GetNsoFileName(index));
return fopen(g_nso_path, "rb");
}
FILE *NsoUtils::OpenNsoFromHBL(unsigned int index) {
std::fill(g_nso_path, g_nso_path + FS_MAX_PATH, 0);
snprintf(g_nso_path, FS_MAX_PATH, "hbl:/%s", NsoUtils::GetNsoFileName(index));
return fopen(g_nso_path, "rb");
}
FILE *NsoUtils::OpenNsoFromExeFS(unsigned int index) {
std::fill(g_nso_path, g_nso_path + FS_MAX_PATH, 0);
snprintf(g_nso_path, FS_MAX_PATH, "code:/%s", NsoUtils::GetNsoFileName(index));
return fopen(g_nso_path, "rb");
}
FILE *NsoUtils::OpenNsoFromSdCard(unsigned int index, u64 title_id) {
std::fill(g_nso_path, g_nso_path + FS_MAX_PATH, 0);
snprintf(g_nso_path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/exefs/%s", title_id, NsoUtils::GetNsoFileName(index));
return fopen(g_nso_path, "rb");
}
bool NsoUtils::CheckNsoStubbed(unsigned int index, u64 title_id) {
std::fill(g_nso_path, g_nso_path + FS_MAX_PATH, 0);
snprintf(g_nso_path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/exefs/%s.stub", title_id, NsoUtils::GetNsoFileName(index));
FILE *f = fopen(g_nso_path, "rb");
bool ret = (f != NULL);
if (ret) {
fclose(f);
}
return ret;
}
FILE *NsoUtils::OpenNso(unsigned int index, u64 title_id) {
ContentManagement::ExternalContentSource *ecs = nullptr;
if ((ecs = ContentManagement::GetExternalContentSource(title_id)) != nullptr) {
return OpenNsoFromECS(index, ecs);
}
/* First, check HBL. */
if (ContentManagement::ShouldOverrideContentsWithHBL(title_id)) {
return OpenNsoFromHBL(index);
}
/* Next, check secondary override. */
if (ContentManagement::ShouldOverrideContentsWithSD(title_id)) {
FILE *f_out = OpenNsoFromSdCard(index, title_id);
if (f_out != NULL) {
return f_out;
} else if (CheckNsoStubbed(index, title_id)) {
return NULL;
}
}
/* Finally, default to exefs. */
return OpenNsoFromExeFS(index);
}
bool NsoUtils::IsNsoPresent(unsigned int index) {
return g_nso_present[index];
}
unsigned char *NsoUtils::GetNsoBuildId(unsigned int index) {
if (g_nso_present[index]) {
return g_nso_headers[index].build_id;
}
return NULL;
}
Result NsoUtils::LoadNsoHeaders(u64 title_id) {
FILE *f_nso;
/* Zero out the cache. */
std::fill(g_nso_present, g_nso_present + NSO_NUM_MAX, false);
std::fill(g_nso_headers, g_nso_headers + NSO_NUM_MAX, NsoUtils::NsoHeader{});
for (unsigned int i = 0; i < NSO_NUM_MAX; i++) {
f_nso = OpenNso(i, title_id);
if (f_nso != NULL) {
if (fread(&g_nso_headers[i], 1, sizeof(NsoUtils::NsoHeader), f_nso) != sizeof(NsoUtils::NsoHeader)) {
return ResultLoaderInvalidNso;
}
g_nso_present[i] = true;
fclose(f_nso);
f_nso = NULL;
continue;
}
if (1 < i && i < 12) {
/* If we failed to open a subsdk, there are no more subsdks. */
i = 11;
}
}
return ResultSuccess;
}
Result NsoUtils::ValidateNsoLoadSet() {
/* We *must* have a "main" NSO. */
if (!g_nso_present[1]) {
return ResultLoaderInvalidNso;
}
/* Behavior switches depending on whether we have an rtld. */
if (g_nso_present[0]) {
/* If we have an rtld, dst offset for .text must be 0 for all other NSOs. */
for (unsigned int i = 0; i < NSO_NUM_MAX; i++) {
if (g_nso_present[i] && g_nso_headers[i].segments[0].dst_offset != 0) {
return ResultLoaderInvalidNso;
}
}
} else {
/* If we don't have an rtld, we must ONLY have a main. */
for (unsigned int i = 2; i < NSO_NUM_MAX; i++) {
if (g_nso_present[i]) {
return ResultLoaderInvalidNso;
}
}
/* That main's .text must be at dst_offset 0. */
if (g_nso_headers[1].segments[0].dst_offset != 0) {
return ResultLoaderInvalidNso;
}
}
return ResultSuccess;
}
Result NsoUtils::CalculateNsoLoadExtents(u32 addspace_type, u32 args_size, NsoLoadExtents *extents) {
*extents = {};
/* Calculate base offsets. */
for (unsigned int i = 0; i < NSO_NUM_MAX; i++) {
if (g_nso_present[i]) {
extents->nso_addresses[i] = extents->total_size;
u32 text_end = g_nso_headers[i].segments[0].dst_offset + g_nso_headers[i].segments[0].decomp_size;
u32 ro_end = g_nso_headers[i].segments[1].dst_offset + g_nso_headers[i].segments[1].decomp_size;
u32 rw_end = g_nso_headers[i].segments[2].dst_offset + g_nso_headers[i].segments[2].decomp_size + g_nso_headers[i].segments[2].align_or_total_size;
extents->nso_sizes[i] = text_end;
if (extents->nso_sizes[i] < ro_end) {
extents->nso_sizes[i] = ro_end;
}
if (extents->nso_sizes[i] < rw_end) {
extents->nso_sizes[i] = rw_end;
}
extents->nso_sizes[i] += 0xFFF;
extents->nso_sizes[i] &= ~0xFFFULL;
extents->total_size += extents->nso_sizes[i];
if (args_size && !extents->args_size) {
extents->args_address = extents->total_size;
/* What the fuck? Where does 0x9007 come from? */
extents->args_size = (2 * args_size + 0x9007);
extents->args_size &= ~0xFFFULL;
extents->total_size += extents->args_size;
}
}
}
/* Calculate ASLR extents for address space type. */
u64 addspace_start, addspace_size;
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_200)) {
switch (addspace_type & 0xE) {
case 0:
case 4:
addspace_start = 0x200000ULL;
addspace_size = 0x3FE00000ULL;
break;
case 2:
addspace_start = 0x8000000ULL;
addspace_size = 0x78000000ULL;
break;
case 6:
addspace_start = 0x8000000ULL;
addspace_size = 0x7FF8000000ULL;
break;
default:
std::abort();
}
} else {
if (addspace_type & 2) {
addspace_start = 0x8000000ULL;
addspace_size = 0x78000000ULL;
} else {
addspace_start = 0x200000ULL;
addspace_size = 0x3FE00000ULL;
}
}
if (extents->total_size > addspace_size) {
return ResultKernelOutOfMemory;
}
u64 aslr_slide = 0;
if (addspace_type & 0x20) {
aslr_slide = sts::rnd::GenerateRandomU64((addspace_size - extents->total_size) >> 21) << 21;
}
extents->base_address = addspace_start + aslr_slide;
for (unsigned int i = 0; i < NSO_NUM_MAX; i++) {
if (g_nso_present[i]) {
extents->nso_addresses[i] += extents->base_address;
}
}
if (extents->args_address) {
extents->args_address += extents->base_address;
}
return ResultSuccess;
}
Result NsoUtils::LoadNsoSegment(u64 title_id, unsigned int index, unsigned int segment, FILE *f_nso, u8 *map_base, u8 *map_end) {
bool is_compressed = ((g_nso_headers[index].flags >> segment) & 1) != 0;
bool check_hash = ((g_nso_headers[index].flags >> (segment + 3)) & 1) != 0;
size_t out_size = g_nso_headers[index].segments[segment].decomp_size;
size_t size = is_compressed ? g_nso_headers[index].compressed_sizes[segment] : out_size;
if (size > out_size) {
return ResultLoaderInvalidNso;
}
if ((u32)(size | out_size) >> 31) {
return ResultLoaderInvalidNso;
}
u8 *dst_addr = map_base + g_nso_headers[index].segments[segment].dst_offset;
u8 *load_addr = is_compressed ? map_end - size : dst_addr;
fseek(f_nso, g_nso_headers[index].segments[segment].file_offset, SEEK_SET);
if (fread(load_addr, 1, size, f_nso) != size) {
return ResultLoaderInvalidNso;
}
if (is_compressed) {
if (LZ4_decompress_safe((char *)load_addr, (char *)dst_addr, size, out_size) != (int)out_size) {
return ResultLoaderInvalidNso;
}
}
if (check_hash) {
u8 hash[0x20] = {0};
sha256CalculateHash(hash, dst_addr, out_size);
if (std::memcmp(g_nso_headers[index].section_hashes[segment], hash, sizeof(hash))) {
return ResultLoaderInvalidNso;
}
}
return ResultSuccess;
}
Result NsoUtils::LoadNsosIntoProcessMemory(Handle process_h, u64 title_id, NsoLoadExtents *extents, const u8 *args, u32 args_size) {
for (unsigned int i = 0; i < NSO_NUM_MAX; i++) {
if (g_nso_present[i]) {
AutoCloseMap nso_map;
R_TRY(nso_map.Open(process_h, extents->nso_addresses[i], extents->nso_sizes[i]));
u8 *map_base = (u8 *)nso_map.GetMappedAddress();
/* Load NSO segments from file. */
{
FILE *f_nso = OpenNso(i, title_id);
if (f_nso == NULL) {
/* TODO: Is there a better error to return here? */
return ResultLoaderInvalidNso;
}
ON_SCOPE_EXIT { fclose(f_nso); };
for (unsigned int seg = 0; seg < 3; seg++) {
R_TRY(LoadNsoSegment(title_id, i, seg, f_nso, map_base, map_base + extents->nso_sizes[i]));
}
}
/* Zero out memory before .text. */
u64 text_base = 0, text_start = g_nso_headers[i].segments[0].dst_offset;
std::fill(map_base + text_base, map_base + text_start, 0);
/* Zero out memory before .rodata. */
u64 ro_base = text_start + g_nso_headers[i].segments[0].decomp_size, ro_start = g_nso_headers[i].segments[1].dst_offset;
std::fill(map_base + ro_base, map_base + ro_start, 0);
/* Zero out memory before .rwdata. */
u64 rw_base = ro_start + g_nso_headers[i].segments[1].decomp_size, rw_start = g_nso_headers[i].segments[2].dst_offset;
std::fill(map_base + rw_base, map_base + rw_start, 0);
/* Zero out .bss. */
u64 bss_base = rw_start + g_nso_headers[i].segments[2].decomp_size, bss_size = g_nso_headers[i].segments[2].align_or_total_size;
std::fill(map_base + bss_base, map_base + bss_base + bss_size, 0);
/* Apply patches to loaded module. */
PatchUtils::ApplyPatches(&g_nso_headers[i], map_base, bss_base);
nso_map.Close();
for (unsigned int seg = 0; seg < 3; seg++) {
u64 size = g_nso_headers[i].segments[seg].decomp_size;
if (seg == 2) {
size += g_nso_headers[i].segments[2].align_or_total_size;
}
size += 0xFFF;
size &= ~0xFFFULL;
const static unsigned int segment_perms[3] = {5, 1, 3};
R_TRY(svcSetProcessMemoryPermission(process_h, extents->nso_addresses[i] + g_nso_headers[i].segments[seg].dst_offset, size, segment_perms[seg]));
}
}
}
/* Map in arguments. */
if (args != nullptr && args_size) {
AutoCloseMap args_map;
R_TRY(args_map.Open(process_h, extents->args_address, extents->args_size));
NsoArgument *arg_map_base = (NsoArgument *)args_map.GetMappedAddress();
arg_map_base->allocated_space = extents->args_size;
arg_map_base->args_size = args_size;
std::fill(arg_map_base->_0x8, arg_map_base->_0x8 + sizeof(arg_map_base->_0x8), 0);
std::copy(args, args + args_size, arg_map_base->arguments);
args_map.Close();
R_TRY(svcSetProcessMemoryPermission(process_h, extents->args_address, extents->args_size, 3));
}
return ResultSuccess;
}

View file

@ -1,116 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <cstdio>
#include "ldr_content_management.hpp" /* for ExternalContentSource */
#define MAGIC_NSO0 0x304F534E
#define NSO_NUM_MAX 13
class NsoUtils {
public:
struct NsoSegment {
u32 file_offset;
u32 dst_offset;
u32 decomp_size;
u32 align_or_total_size;
};
struct NsoHeader {
u32 magic;
u32 _0x4;
u32 _0x8;
u32 flags;
NsoSegment segments[3];
u8 build_id[0x20];
u32 compressed_sizes[3];
u8 _0x6C[0x24];
u64 dynstr_extents;
u64 dynsym_extents;
u8 section_hashes[3][0x20];
};
struct NsoLoadExtents {
u64 base_address;
u64 total_size;
u64 args_address;
u64 args_size;
u64 nso_addresses[NSO_NUM_MAX];
u64 nso_sizes[NSO_NUM_MAX];
};
struct NsoArgument {
u32 allocated_space;
u32 args_size;
u8 _0x8[0x18];
u8 arguments[];
};
static_assert(sizeof(NsoHeader) == 0x100, "Incorrectly defined NsoHeader!");
static const char *GetNsoFileName(unsigned int index) {
switch (index) {
case 0:
return "rtld";
case 1:
return "main";
case 2:
return "subsdk0";
case 3:
return "subsdk1";
case 4:
return "subsdk2";
case 5:
return "subsdk3";
case 6:
return "subsdk4";
case 7:
return "subsdk5";
case 8:
return "subsdk6";
case 9:
return "subsdk7";
case 10:
return "subsdk8";
case 11:
return "subsdk9";
case 12:
return "sdk";
default:
std::abort();
}
}
static FILE *OpenNsoFromECS(unsigned int index, ContentManagement::ExternalContentSource *ecs);
static FILE *OpenNsoFromHBL(unsigned int index);
static FILE *OpenNsoFromExeFS(unsigned int index);
static FILE *OpenNsoFromSdCard(unsigned int index, u64 title_id);
static bool CheckNsoStubbed(unsigned int index, u64 title_id);
static FILE *OpenNso(unsigned int index, u64 title_id);
static bool IsNsoPresent(unsigned int index);
static unsigned char *GetNsoBuildId(unsigned int index);
static Result LoadNsoHeaders(u64 title_id);
static Result ValidateNsoLoadSet();
static Result CalculateNsoLoadExtents(u32 addspace_type, u32 args_size, NsoLoadExtents *extents);
static Result LoadNsoSegment(u64 title_id, unsigned int index, unsigned int segment, FILE *f_nso, u8 *map_base, u8 *map_end);
static Result LoadNsosIntoProcessMemory(Handle process_h, u64 title_id, NsoLoadExtents *extents, const u8 *args, u32 args_size);
};

View file

@ -14,174 +14,30 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <dirent.h>
#include <ctype.h>
#include <stratosphere/patcher.hpp>
#include <stratosphere/ro.hpp>
#include <switch.h>
#include "ldr_patcher.hpp"
/* IPS Patching adapted from Luma3DS (https://github.com/AuroraWright/Luma3DS/blob/master/sysmodules/loader/source/patcher.c) */
namespace sts::ldr {
#define IPS_MAGIC "PATCH"
#define IPS_TAIL "EOF"
namespace {
#define IPS32_MAGIC "IPS32"
#define IPS32_TAIL "EEOF"
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);
static inline u8 HexNybbleToU8(const char nybble) {
if ('0' <= nybble && nybble <= '9') {
return nybble - '0';
} else if ('a' <= nybble && nybble <= 'f') {
return nybble - 'a' + 0xa;
} else {
return nybble - 'A' + 0xA;
}
}
static bool MatchesBuildId(const char *name, size_t name_len, const u8 *build_id) {
/* Validate name is hex build id. */
for (unsigned int i = 0; i < name_len - 4; i++) {
if (isxdigit(name[i]) == 0) {
return false;
}
/* Apply IPS patches. */
void LocateAndApplyIpsPatchesToModule(const u8 *build_id, uintptr_t mapped_nso, size_t mapped_size) {
ro::ModuleId module_id;
std::memcpy(&module_id.build_id, build_id, sizeof(module_id.build_id));
sts::patcher::LocateAndApplyIpsPatchesToModule(NsoPatchesDirectory, NsoPatchesProtectedSize, NsoPatchesProtectedOffset, &module_id, reinterpret_cast<u8 *>(mapped_nso), mapped_size);
}
/* Read build id from name. */
u8 build_id_from_name[0x20] = {0};
for (unsigned int name_ofs = 0, id_ofs = 0; name_ofs < name_len - 4; id_ofs++) {
build_id_from_name[id_ofs] |= HexNybbleToU8(name[name_ofs++]) << 4;
build_id_from_name[id_ofs] |= HexNybbleToU8(name[name_ofs++]);
}
return memcmp(build_id, build_id_from_name, sizeof(build_id_from_name)) == 0;
}
static void ApplyIpsPatch(u8 *mapped_nso, size_t mapped_size, bool is_ips32, FILE *f_ips) {
u8 buffer[4];
while (fread(buffer, is_ips32 ? 4 : 3, 1, f_ips) == 1) {
if (is_ips32 && memcmp(buffer, IPS32_TAIL, 4) == 0) {
break;
} else if (!is_ips32 && memcmp(buffer, IPS_TAIL, 3) == 0) {
break;
}
/* Offset of patch. */
u32 patch_offset;
if (is_ips32) {
patch_offset = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];
} else {
patch_offset = (buffer[0] << 16) | (buffer[1] << 8) | (buffer[2]);
}
/* Size of patch. */
if (fread(buffer, 2, 1, f_ips) != 1) {
break;
}
u32 patch_size = (buffer[0] << 8) | (buffer[1]);
/* Check for RLE encoding. */
if (patch_size == 0) {
/* Size of RLE. */
if (fread(buffer, 2, 1, f_ips) != 1) {
break;
}
u32 rle_size = (buffer[0] << 8) | (buffer[1]);
/* Value for RLE. */
if (fread(buffer, 1, 1, f_ips) != 1) {
break;
}
if (patch_offset < sizeof(NsoUtils::NsoHeader)) {
if (patch_offset + rle_size > sizeof(NsoUtils::NsoHeader)) {
u32 diff = sizeof(NsoUtils::NsoHeader) - patch_offset;
patch_offset += diff;
rle_size -= diff;
goto IPS_RLE_PATCH_OFFSET_WITHIN_BOUNDS;
}
} else {
IPS_RLE_PATCH_OFFSET_WITHIN_BOUNDS:
patch_offset -= sizeof(NsoUtils::NsoHeader);
if (patch_offset + rle_size > mapped_size) {
rle_size = mapped_size - patch_offset;
}
memset(mapped_nso + patch_offset, buffer[0], rle_size);
}
} else {
if (patch_offset < sizeof(NsoUtils::NsoHeader)) {
if (patch_offset + patch_size > sizeof(NsoUtils::NsoHeader)) {
u32 diff = sizeof(NsoUtils::NsoHeader) - patch_offset;
patch_offset += diff;
patch_size -= diff;
fseek(f_ips, diff, SEEK_CUR);
goto IPS_DATA_PATCH_OFFSET_WITHIN_BOUNDS;
} else {
fseek(f_ips, patch_size, SEEK_CUR);
}
} else {
IPS_DATA_PATCH_OFFSET_WITHIN_BOUNDS:
patch_offset -= sizeof(NsoUtils::NsoHeader);
u32 read_size = patch_size;
if (patch_offset + read_size > mapped_size) {
read_size = mapped_size - patch_offset;
}
if (fread(mapped_nso + patch_offset, read_size, 1, f_ips) != 1) {
break;
}
if (patch_size > read_size) {
fseek(f_ips, patch_size - read_size, SEEK_CUR);
}
}
}
}
}
void PatchUtils::ApplyPatches(const NsoUtils::NsoHeader *header, u8 *mapped_nso, size_t mapped_size) {
/* Inspect all patches from /atmosphere/exefs_patches/<*>/<*>.ips */
char path[FS_MAX_PATH+1] = {0};
snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/exefs_patches");
DIR *patches_dir = opendir(path);
struct dirent *pdir_ent;
if (patches_dir != NULL) {
/* Iterate over the patches directory to find patch subdirectories. */
while ((pdir_ent = readdir(patches_dir)) != NULL) {
if (strcmp(pdir_ent->d_name, ".") == 0 || strcmp(pdir_ent->d_name, "..") == 0) {
continue;
}
snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/exefs_patches/%s", pdir_ent->d_name);
DIR *patch_dir = opendir(path);
struct dirent *ent;
if (patch_dir != NULL) {
/* Iterate over the patch subdirectory to find .ips patches. */
while ((ent = readdir(patch_dir)) != NULL) {
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
continue;
}
size_t name_len = strlen(ent->d_name);
if ((4 < name_len && name_len <= 0x44) && ((name_len & 1) == 0) && strcmp(ent->d_name + name_len - 4, ".ips") == 0 && MatchesBuildId(ent->d_name, name_len, header->build_id)) {
snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/exefs_patches/%s/%s", pdir_ent->d_name, ent->d_name);
FILE *f_ips = fopen(path, "rb");
if (f_ips != NULL) {
u8 header[5];
if (fread(header, 5, 1, f_ips) == 1) {
if (memcmp(header, IPS_MAGIC, 5) == 0) {
ApplyIpsPatch(mapped_nso, mapped_size, false, f_ips);
} else if (memcmp(header, IPS32_MAGIC, 5) == 0) {
ApplyIpsPatch(mapped_nso, mapped_size, true, f_ips);
}
}
fclose(f_ips);
}
}
}
closedir(patch_dir);
}
}
closedir(patches_dir);
}
}

View file

@ -16,11 +16,11 @@
#pragma once
#include <switch.h>
#include <cstdio>
#include <stratosphere/ldr.hpp>
#include "ldr_nso.hpp"
namespace sts::ldr {
class PatchUtils {
public:
static void ApplyPatches(const NsoUtils::NsoHeader *header, u8 *mapped_nso, size_t size);
};
/* Apply IPS patches. */
void LocateAndApplyIpsPatchesToModule(const u8 *build_id, uintptr_t mapped_nso, size_t mapped_size);
}

View file

@ -14,18 +14,189 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <algorithm>
#include <stratosphere.hpp>
#include <limits>
#include <stratosphere/map.hpp>
#include <stratosphere/rnd.hpp>
#include <stratosphere/util.hpp>
#include "ldr_process_creation.hpp"
#include "ldr_registration.hpp"
#include "ldr_launch_queue.hpp"
#include "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
#include "ldr_npdm.hpp"
#include "ldr_nso.hpp"
#include "ldr_ecs.hpp"
#include "ldr_launch_record.hpp"
#include "ldr_meta.hpp"
#include "ldr_patcher.hpp"
#include "ldr_process_creation.hpp"
#include "ldr_ro_manager.hpp"
static inline bool IsDisallowedVersion810(const u64 title_id, const u32 version) {
/* TODO: Move into libstratosphere header? */
namespace sts::svc {
namespace {
enum CreateProcessFlag : u32 {
/* Is 64 bit? */
CreateProcessFlag_Is64Bit = (1 << 0),
/* What kind of address space? */
CreateProcessFlag_AddressSpaceShift = 1,
CreateProcessFlag_AddressSpaceMask = (7 << CreateProcessFlag_AddressSpaceShift),
CreateProcessFlag_AddressSpace32Bit = (ldr::Npdm::AddressSpaceType_32Bit << CreateProcessFlag_AddressSpaceShift),
CreateProcessFlag_AddressSpace64BitDeprecated = (ldr::Npdm::AddressSpaceType_64BitDeprecated << CreateProcessFlag_AddressSpaceShift),
CreateProcessFlag_AddressSpace32BitWithoutAlias = (ldr::Npdm::AddressSpaceType_32BitWithoutAlias << CreateProcessFlag_AddressSpaceShift),
CreateProcessFlag_AddressSpace64Bit = (ldr::Npdm::AddressSpaceType_64Bit << CreateProcessFlag_AddressSpaceShift),
/* Should JIT debug be done on crash? */
CreateProcessFlag_EnableDebug = (1 << 4),
/* Should ASLR be enabled for the process? */
CreateProcessFlag_EnableAslr = (1 << 5),
/* Is the process an application? */
CreateProcessFlag_IsApplication = (1 << 6),
/* 4.x deprecated: Should use secure memory? */
CreateProcessFlag_DeprecatedUseSecureMemory = (1 << 7),
/* 5.x+ Pool partition type. */
CreateProcessFlag_PoolPartitionShift = 7,
CreateProcessFlag_PoolPartitionMask = (0xF << CreateProcessFlag_PoolPartitionShift),
CreateProcessFlag_PoolPartitionApplication = (ldr::Acid::PoolPartition_Application << CreateProcessFlag_PoolPartitionShift),
CreateProcessFlag_PoolPartitionApplet = (ldr::Acid::PoolPartition_Applet << CreateProcessFlag_PoolPartitionShift),
CreateProcessFlag_PoolPartitionSystem = (ldr::Acid::PoolPartition_System << CreateProcessFlag_PoolPartitionShift),
CreateProcessFlag_PoolPartitionSystemNonSecure = (ldr::Acid::PoolPartition_SystemNonSecure << CreateProcessFlag_PoolPartitionShift),
/* 7.x+ Should memory allocation be optimized? This requires IsApplication. */
CreateProcessFlag_OptimizeMemoryAllocation = (1 << 11),
};
}
}
namespace sts::ldr {
namespace {
/* Convenience defines. */
constexpr size_t BaseAddressAlignment = 0x200000;
constexpr size_t SystemResourceSizeAlignment = 0x200000;
constexpr size_t SystemResourceSizeMax = 0x1FE00000;
/* Types. */
enum NsoIndex {
Nso_Rtld = 0,
Nso_Main = 1,
Nso_SubSdk0 = 2,
Nso_SubSdk1 = 3,
Nso_SubSdk2 = 4,
Nso_SubSdk3 = 5,
Nso_SubSdk4 = 6,
Nso_SubSdk5 = 7,
Nso_SubSdk6 = 8,
Nso_SubSdk7 = 9,
Nso_SubSdk8 = 10,
Nso_SubSdk9 = 11,
Nso_Sdk = 12,
Nso_Count,
};
constexpr const char *GetNsoName(size_t idx) {
if (idx >= Nso_Count) {
std::abort();
}
constexpr const char *NsoNames[Nso_Count] = {
"rtld",
"main",
"subsdk0",
"subsdk1",
"subsdk2",
"subsdk3",
"subsdk4",
"subsdk5",
"subsdk6",
"subsdk7",
"subsdk8",
"subsdk9",
"sdk",
};
return NsoNames[idx];
}
struct CreateProcessInfo {
char name[12];
u32 version;
ncm::TitleId title_id;
u64 code_address;
u32 code_num_pages;
u32 flags;
Handle reslimit;
u32 system_resource_num_pages;
};
static_assert(sizeof(CreateProcessInfo) == 0x30, "CreateProcessInfo definition!");
struct ProcessInfo {
AutoHandle 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];
/* 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->title_id = meta->aci->title_id;
/* Copy access controls. */
size_t offset = 0;
#define COPY_ACCESS_CONTROL(source, which) \
({ \
const size_t size = meta->source->which##_size; \
if (offset + size >= sizeof(out->ac_buffer)) { \
return ResultLoaderInternalError; \
} \
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 = caps::GetProgramInfoFlags(meta->acid_kac, meta->acid->kac_size);
return ResultSuccess;
}
bool IsApplet(const Meta *meta) {
return (caps::GetProgramInfoFlags(meta->aci_kac, meta->aci->kac_size) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet;
}
bool IsApplication(const Meta *meta) {
return (caps::GetProgramInfoFlags(meta->aci_kac, meta->aci->kac_size) & 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);
}
constexpr bool IsDisallowedVersion810(const u64 title_id, const u32 version) {
return version == 0 &&
(title_id == TitleId_Settings ||
title_id == TitleId_Bus ||
@ -38,12 +209,12 @@ static inline bool IsDisallowedVersion810(const u64 title_id, const u32 version)
title_id == TitleId_Ro);
}
Result ProcessCreation::ValidateProcessVersion(u64 title_id, u32 version) {
Result ValidateTitleVersion(ncm::TitleId title_id, u32 version) {
if (GetRuntimeFirmwareVersion() < FirmwareVersion_810) {
return ResultSuccess;
} else {
#ifdef LDR_VALIDATE_PROCESS_VERSION
if (IsDisallowedVersion810(title_id, version)) {
if (IsDisallowedVersion810(static_cast<u64>(title_id), version)) {
return ResultLoaderInvalidVersion;
} else {
return ResultSuccess;
@ -54,208 +225,500 @@ Result ProcessCreation::ValidateProcessVersion(u64 title_id, u32 version) {
}
}
Result ProcessCreation::InitializeProcessInfo(NpdmUtils::NpdmInfo *npdm, Handle reslimit_h, u64 arg_flags, ProcessInfo *out_proc_info) {
/* Initialize a ProcessInfo using an npdm. */
*out_proc_info = {};
Result LoadNsoHeaders(ncm::TitleId title_id, 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);
/* Copy all but last char of name, insert NULL terminator. */
std::copy(npdm->header->title_name, npdm->header->title_name + sizeof(out_proc_info->name) - 1, out_proc_info->name);
out_proc_info->name[sizeof(out_proc_info->name) - 1] = 0;
/* Set title id. */
out_proc_info->title_id = npdm->aci0->title_id;
/* Set version. */
out_proc_info->version = npdm->header->version;
/* Copy reslimit handle raw. */
out_proc_info->reslimit_h = reslimit_h;
/* Set IsAddressSpace64Bit, AddressSpaceType. */
if (npdm->header->mmu_flags & 8) {
/* Invalid Address Space Type. */
return ResultLoaderInvalidMeta;
for (size_t i = 0; i < Nso_Count; i++) {
FILE *f = nullptr;
if (R_SUCCEEDED(OpenCodeFile(f, title_id, GetNsoName(i)))) {
ON_SCOPE_EXIT { fclose(f); };
/* Read NSO header. */
if (fread(nso_headers + i, sizeof(*nso_headers), 1, f) != 1) {
return ResultLoaderInvalidNso;
}
has_nso[i] = true;
}
}
out_proc_info->process_flags = (npdm->header->mmu_flags & 0xF);
/* Set Bit 4 (?) and EnableAslr based on argument flags. */
out_proc_info->process_flags |= ((arg_flags & 3) << 4) ^ 0x20;
/* Set UseSystemMemBlocks if application type is 1. */
u32 application_type = NpdmUtils::GetApplicationType((u32 *)npdm->aci0_kac, npdm->aci0->kac_size / sizeof(u32));
if ((application_type & 3) == 1) {
out_proc_info->process_flags |= 0x40;
/* 7.0.0+: Set unknown bit related to system resource heap if relevant. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_700) {
if ((npdm->header->mmu_flags & 0x10)) {
out_proc_info->process_flags |= 0x800;
return ResultSuccess;
}
Result ValidateNsoHeaders(const NsoHeader *nso_headers, const bool *has_nso) {
/* We must always have a main. */
if (!has_nso[Nso_Main]) {
return ResultLoaderInvalidNso;
}
/* 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++) {
if (has_nso[i]) {
return ResultLoaderInvalidNso;
}
}
}
/* 3.0.0+ System Resource Size. */
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_300)) {
if (npdm->header->system_resource_size & 0x1FFFFF) {
return ResultLoaderInvalidSize;
/* All NSOs must have zero text offset. */
for (size_t i = 0; i < Nso_Count; i++) {
if (nso_headers[i].text_dst_offset != 0) {
return ResultLoaderInvalidNso;
}
if (npdm->header->system_resource_size) {
if ((out_proc_info->process_flags & 6) == 0) {
return ResultLoaderInvalidMeta;
}
if (!(((application_type & 3) == 1) || ((GetRuntimeFirmwareVersion() >= FirmwareVersion_600) && (application_type & 3) == 2))) {
return ResultLoaderInvalidMeta;
}
if (npdm->header->system_resource_size > 0x1FE00000) {
return ResultLoaderInvalidMeta;
}
}
out_proc_info->system_resource_num_pages = npdm->header->system_resource_size >> 12;
} else {
out_proc_info->system_resource_num_pages = 0;
}
/* 5.0.0+ Pool Partition. */
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500)) {
u32 pool_partition_id = (npdm->acid->flags >> 2) & 0xF;
switch (pool_partition_id) {
case 0: /* Application. */
if ((application_type & 3) == 2) {
out_proc_info->process_flags |= 0x80;
return ResultSuccess;
}
Result ValidateMeta(const Meta *meta, const ncm::TitleLocation &loc) {
/* Validate version. */
R_TRY(ValidateTitleVersion(loc.title_id, meta->npdm->version));
/* Validate title id. */
if (meta->aci->title_id < meta->acid->title_id_min || meta->aci->title_id > meta->acid->title_id_max) {
return ResultLoaderInvalidProgramId;
}
/* Validate the kernel capabilities. */
R_TRY(caps::ValidateCapabilities(meta->acid_kac, meta->acid->kac_size, meta->aci_kac, meta->aci->kac_size));
/* All good. */
return ResultSuccess;
}
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 1: /* Applet. */
out_proc_info->process_flags |= 0x80;
case Npdm::AddressSpaceType_64BitDeprecated:
flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
break;
case 2: /* Sysmodule. */
out_proc_info->process_flags |= 0x100;
case Npdm::AddressSpaceType_32BitWithoutAlias:
flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
break;
case 3: /* nvservices. */
out_proc_info->process_flags |= 0x180;
case Npdm::AddressSpaceType_64Bit:
flags |= svc::CreateProcessFlag_AddressSpace64Bit;
break;
default:
return ResultLoaderInvalidMeta;
}
/* 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 (GetRuntimeFirmwareVersion() >= FirmwareVersion_700) {
if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) {
flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation;
}
}
}
/* 5.0.0+ Set Pool Partition. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
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:
return ResultLoaderInvalidMeta;
}
} else if (GetRuntimeFirmwareVersion() >= FirmwareVersion_400) {
/* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */
if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) {
flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory;
}
}
*out = flags;
return ResultSuccess;
}
Result GetCreateProcessInfo(CreateProcessInfo *out, const Meta *meta, u32 flags, Handle reslimit_h) {
/* Clear output. */
std::memset(out, 0, sizeof(*out));
/* Set name, version, title id, resource limit handle. */
std::memcpy(out->name, meta->npdm->title_name, sizeof(out->name) - 1);
out->version = meta->npdm->version;
out->title_id = meta->aci->title_id;
out->reslimit = reslimit_h;
/* Set flags. */
R_TRY(GetCreateProcessFlags(&out->flags, meta, flags));
/* 3.0.0+ System Resource Size. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) {
/* Validate size is aligned. */
if (meta->npdm->system_resource_size & (SystemResourceSizeAlignment - 1)) {
return ResultLoaderInvalidSize;
}
/* Validate system resource usage. */
if (meta->npdm->system_resource_size) {
/* Process must be 64-bit. */
if (!(out->flags & svc::CreateProcessFlag_AddressSpace64Bit)) {
return ResultLoaderInvalidMeta;
}
/* Process must be application or applet. */
if (!IsApplication(meta) && !IsApplet(meta)) {
return ResultLoaderInvalidMeta;
}
/* Size must be less than or equal to max. */
if (meta->npdm->system_resource_size > SystemResourceSizeMax) {
return ResultLoaderInvalidMeta;
}
}
out->system_resource_num_pages = meta->npdm->system_resource_size >> 12;
}
return ResultSuccess;
}
Result ProcessCreation::CreateProcess(Handle *out_process_h, u64 index, char *nca_path, LaunchQueue::LaunchItem *launch_item, u64 arg_flags, Handle reslimit_h) {
NpdmUtils::NpdmInfo npdm_info = {};
ProcessInfo process_info = {};
NsoUtils::NsoLoadExtents nso_extents = {};
Registration::Process *target_process;
Handle process_h = 0;
u64 process_id = 0;
Result DecideAddressSpaceLayout(ProcessInfo *out, CreateProcessInfo *out_cpi, const NsoHeader *nso_headers, const bool *has_nso, const args::ArgumentInfo *arg_info) {
/* 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));
/* Get the process from the registration queue. */
target_process = Registration::GetProcess(index);
if (target_process == nullptr) {
return ResultLoaderProcessNotRegistered;
size_t total_size = 0;
/* 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] = (out->nso_size[i] + size_t(0xFFFul)) & ~size_t(0xFFFul);
total_size += out->nso_size[i];
if (arg_info != nullptr && arg_info->args_size && !out->args_size) {
out->args_address = total_size;
out->args_size = 2 * arg_info->args_size + args::ArgumentSizeMax + 2 * sizeof(u32);
out->args_size = (out->args_size + size_t(0xFFFul)) & ~size_t(0xFFFul);
total_size += out->args_size;
}
}
}
/* Mount the title's exefs. */
bool mounted_code = false;
if (target_process->tid_sid.storage_id != FsStorageId_None) {
R_TRY(ContentManagement::MountCodeForTidSid(&target_process->tid_sid));
mounted_code = true;
/* Calculate ASLR. */
uintptr_t aslr_start = 0;
uintptr_t aslr_size = 0;
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) {
switch (out_cpi->flags & svc::CreateProcessFlag_AddressSpaceMask) {
case svc::CreateProcessFlag_AddressSpace32Bit:
case svc::CreateProcessFlag_AddressSpace32BitWithoutAlias:
aslr_start = map::AslrBase32Bit;
aslr_size = map::AslrSize32Bit;
break;
case svc::CreateProcessFlag_AddressSpace64BitDeprecated:
aslr_start = map::AslrBase64BitDeprecated;
aslr_size = map::AslrSize64BitDeprecated;
break;
case svc::CreateProcessFlag_AddressSpace64Bit:
aslr_start = map::AslrBase64Bit;
aslr_size = map::AslrSize64Bit;
break;
default:
std::abort();
}
} else {
if (R_SUCCEEDED(ContentManagement::MountCodeNspOnSd(target_process->tid_sid.title_id))) {
mounted_code = true;
/* On 1.0.0, only 2 address space types existed. */
if (out_cpi->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) {
aslr_start = map::AslrBase64BitDeprecated;
aslr_size = map::AslrSize64BitDeprecated;
} else {
aslr_start = map::AslrBase32Bit;
aslr_size = map::AslrSize32Bit;
}
}
ON_SCOPE_EXIT {
if (mounted_code) {
const Result unmount_res = ContentManagement::UnmountCode();
if (target_process->tid_sid.storage_id != FsStorageId_None) {
R_ASSERT(unmount_res);
}
}
};
/* Load the process's NPDM. */
R_TRY(NpdmUtils::LoadNpdmFromCache(target_process->tid_sid.title_id, &npdm_info));
/* Validate version. */
R_TRY(ValidateProcessVersion(target_process->tid_sid.title_id, npdm_info.header->version));
/* Validate the title we're loading is what we expect. */
if (npdm_info.aci0->title_id < npdm_info.acid->title_id_range_min || npdm_info.aci0->title_id > npdm_info.acid->title_id_range_max) {
return ResultLoaderInvalidProgramId;
if (total_size > aslr_size) {
return ResultKernelOutOfMemory;
}
/* Validate that the ACI0 Kernel Capabilities are valid and restricted by the ACID Kernel Capabilities. */
const u32 *acid_caps = reinterpret_cast<u32 *>(npdm_info.acid_kac);
const u32 *aci0_caps = reinterpret_cast<u32 *>(npdm_info.aci0_kac);
const size_t num_acid_caps = npdm_info.acid->kac_size / sizeof(*acid_caps);
const size_t num_aci0_caps = npdm_info.aci0->kac_size / sizeof(*aci0_caps);
R_TRY(NpdmUtils::ValidateCapabilities(acid_caps, num_acid_caps, aci0_caps, num_aci0_caps));
/* Set Create Process output. */
uintptr_t aslr_slide = 0;
uintptr_t unused_size = (aslr_size - total_size);
if (out_cpi->flags & svc::CreateProcessFlag_EnableAslr) {
aslr_slide = sts::rnd::GenerateRandomU64(unused_size / BaseAddressAlignment) * BaseAddressAlignment;
}
/* Read in all NSO headers, see what NSOs are present. */
R_TRY(NsoUtils::LoadNsoHeaders(npdm_info.aci0->title_id));
/* 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;
}
/* Validate that the set of NSOs to be loaded is correct. */
R_TRY(NsoUtils::ValidateNsoLoadSet());
out_cpi->code_address = aslr_start;
out_cpi->code_num_pages = total_size >> 12;
/* Initialize the ProcessInfo. */
R_TRY(ProcessCreation::InitializeProcessInfo(&npdm_info, reslimit_h, arg_flags, &process_info));
return ResultSuccess;
}
/* Figure out where NSOs will be mapped, and how much space they (and arguments) will take up. */
R_TRY(NsoUtils::CalculateNsoLoadExtents(process_info.process_flags, launch_item != nullptr ? launch_item->arg_size : 0, &nso_extents));
Result CreateProcessImpl(ProcessInfo *out, const Meta *meta, const NsoHeader *nso_headers, const bool *has_nso, const args::ArgumentInfo *arg_info, u32 flags, Handle reslimit_h) {
/* Get CreateProcessInfo. */
CreateProcessInfo cpi;
R_TRY(GetCreateProcessInfo(&cpi, meta, flags, reslimit_h));
/* Set Address Space information in ProcessInfo. */
process_info.code_addr = nso_extents.base_address;
process_info.code_num_pages = nso_extents.total_size + 0xFFF;
process_info.code_num_pages >>= 12;
/* Decide on an NSO layout. */
R_TRY(DecideAddressSpaceLayout(out, &cpi, nso_headers, has_nso, arg_info));
/* Call svcCreateProcess(). */
R_TRY(svcCreateProcess(&process_h, &process_info, (u32 *)npdm_info.aci0_kac, npdm_info.aci0->kac_size/sizeof(u32)));
auto proc_handle_guard = SCOPE_GUARD {
svcCloseHandle(process_h);
};
/* Actually create process. const_cast necessary because libnx doesn't declare svcCreateProcess with const u32*. */
return svcCreateProcess(out->process_handle.GetPointer(), &cpi, reinterpret_cast<const u32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(u32));
}
Result LoadNsoSegment(FILE *f, 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;
}
/* Load all NSOs into Process memory, and set permissions accordingly. */
/* Validate size. */
if (file_size > segment->size || file_size > std::numeric_limits<s32>::max() || segment->size > std::numeric_limits<s32>::max()) {
return ResultLoaderInvalidNso;
}
/* Load data from file. */
uintptr_t load_address = is_compressed ? map_end - file_size : map_base;
fseek(f, segment->file_offset, SEEK_SET);
if (fread(reinterpret_cast<void *>(load_address), file_size, 1, f) != 1) {
return ResultLoaderInvalidNso;
}
/* Uncompress if necessary. */
if (is_compressed) {
if (util::DecompressLZ4(reinterpret_cast<void *>(map_base), segment->size, reinterpret_cast<const void *>(load_address), file_size) != static_cast<int>(segment->size)) {
return ResultLoaderInvalidNso;
}
}
/* Check hash if necessary. */
if (check_hash) {
u8 hash[SHA256_HASH_SIZE];
sha256CalculateHash(hash, reinterpret_cast<void *>(map_base), segment->size);
if (std::memcmp(hash, file_hash, sizeof(hash)) != 0) {
return ResultLoaderInvalidNso;
}
}
return ResultSuccess;
}
Result LoadNsoIntoProcessMemory(Handle process_handle, FILE *f, uintptr_t map_address, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size) {
/* Map and read data from file. */
{
const u8 *launch_args = nullptr;
size_t launch_args_size = 0;
if (launch_item != nullptr) {
launch_args = reinterpret_cast<const u8 *>(launch_item->args);
launch_args_size = launch_item->arg_size;
map::AutoCloseMap mapper(map_address, process_handle, nso_address, nso_size);
R_TRY(mapper.GetResult());
/* Load NSO segments. */
R_TRY(LoadNsoSegment(f, &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(LoadNsoSegment(f, &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(LoadNsoSegment(f, &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, 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 IPS patches. */
LocateAndApplyIpsPatchesToModule(nso_header->build_id, map_address, nso_size);
}
R_TRY(NsoUtils::LoadNsosIntoProcessMemory(process_h, npdm_info.aci0->title_id, &nso_extents, launch_args, launch_args_size));
/* Set permissions. */
const size_t text_size = (static_cast<size_t>(nso_header->text_size) + size_t(0xFFFul)) & ~size_t(0xFFFul);
const size_t ro_size = (static_cast<size_t>(nso_header->ro_size) + size_t(0xFFFul)) & ~size_t(0xFFFul);
const size_t rw_size = (static_cast<size_t>(nso_header->rw_size + nso_header->bss_size) + size_t(0xFFFul)) & ~size_t(0xFFFul);
if (text_size) {
R_TRY(svcSetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, Perm_Rx));
}
if (ro_size) {
R_TRY(svcSetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, Perm_R));
}
if (rw_size) {
R_TRY(svcSetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, Perm_Rw));
}
/* Update the list of registered processes with the new process. */
svcGetProcessId(&process_id, process_h);
bool is_64_bit_addspace;
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_200)) {
is_64_bit_addspace = (((npdm_info.header->mmu_flags >> 1) & 5) | 2) == 3;
} else {
is_64_bit_addspace = (npdm_info.header->mmu_flags & 0xE) == 0x2;
}
Registration::SetProcessIdTidAndIs64BitAddressSpace(index, process_id, npdm_info.aci0->title_id, is_64_bit_addspace);
for (unsigned int i = 0; i < NSO_NUM_MAX; i++) {
if (NsoUtils::IsNsoPresent(i)) {
Registration::AddModuleInfo(index, nso_extents.nso_addresses[i], nso_extents.nso_sizes[i], NsoUtils::GetNsoBuildId(i));
}
}
/* Send the pid/tid pair to anyone interested in man-in-the-middle-attacking it. */
Registration::AssociatePidTidForMitM(index);
/* If HBL, override HTML document path. */
if (ContentManagement::ShouldOverrideContentsWithHBL(target_process->tid_sid.title_id)) {
ContentManagement::RedirectHtmlDocumentPathForHbl(target_process->tid_sid.title_id, target_process->tid_sid.storage_id);
}
/* ECS is a one-shot operation, but we don't clear on failure. */
ContentManagement::ClearExternalContentSource(target_process->tid_sid.title_id);
/* Cancel the process handle guard. */
proc_handle_guard.Cancel();
/* Write process handle to output. */
*out_process_h = process_h;
return ResultSuccess;
}
Result LoadNsosIntoProcessMemory(const ProcessInfo *process_info, const ncm::TitleId title_id, const NsoHeader *nso_headers, const bool *has_nso, const args::ArgumentInfo *arg_info) {
const Handle process_handle = process_info->process_handle.Get();
/* Load each NSO. */
for (size_t i = 0; i < Nso_Count; i++) {
if (has_nso[i]) {
FILE *f = nullptr;
R_TRY(OpenCodeFile(f, title_id, GetNsoName(i)));
ON_SCOPE_EXIT { fclose(f); };
uintptr_t map_address = 0;
R_TRY(map::LocateMappableSpace(&map_address, process_info->nso_size[i]));
R_TRY(LoadNsoIntoProcessMemory(process_handle, f, map_address, nso_headers + i, process_info->nso_address[i], process_info->nso_size[i]));
}
}
/* Load arguments, if present. */
if (arg_info != nullptr) {
/* Write argument data into memory. */
{
uintptr_t map_address = 0;
R_TRY(map::LocateMappableSpace(&map_address, process_info->args_size));
map::AutoCloseMap mapper(map_address, process_handle, process_info->args_address, process_info->args_size);
R_TRY(mapper.GetResult());
ProgramArguments *args = reinterpret_cast<ProgramArguments *>(map_address);
std::memset(args, 0, sizeof(*args));
args->allocated_size = process_info->args_size;
args->arguments_size = arg_info->args_size;
std::memcpy(args->arguments, arg_info->args, arg_info->args_size);
}
/* Set argument region permissions. */
R_TRY(svcSetProcessMemoryPermission(process_handle, process_info->args_address, process_info->args_size, Perm_Rw));
}
return ResultSuccess;
}
}
/* Process Creation API. */
Result CreateProcess(Handle *out, PinId pin_id, const ncm::TitleLocation &loc, const char *path, u32 flags, Handle reslimit_h) {
/* Use global storage for NSOs. */
NsoHeader *nso_headers = g_nso_headers;
bool *has_nso = g_has_nso;
const auto arg_info = args::Get(loc.title_id);
{
/* Mount code. */
ScopedCodeMount mount(loc);
R_TRY(mount.GetResult());
/* Load meta, possibly from cache. */
Meta meta;
R_TRY(LoadMetaFromCache(&meta, loc.title_id));
/* Validate meta. */
R_TRY(ValidateMeta(&meta, loc));
/* Load, validate NSOs. */
R_TRY(LoadNsoHeaders(loc.title_id, nso_headers, has_nso));
R_TRY(ValidateNsoHeaders(nso_headers, has_nso));
/* Actually create process. */
ProcessInfo info;
R_TRY(CreateProcessImpl(&info, &meta, nso_headers, has_nso, arg_info, flags, reslimit_h));
/* Load NSOs into process memory. */
R_TRY(LoadNsosIntoProcessMemory(&info, loc.title_id, nso_headers, has_nso, arg_info));
/* Register NSOs with ro manager. */
u64 process_id;
{
/* Nintendo doesn't validate this result, but we will. */
R_ASSERT(svcGetProcessId(&process_id, info.process_handle.Get()));
/* Register new process. */
ldr::ro::RegisterProcess(pin_id, process_id, loc.title_id);
/* Register all NSOs. */
for (size_t i = 0; i < Nso_Count; i++) {
if (has_nso[i]) {
ldr::ro::RegisterModule(pin_id, nso_headers[i].build_id, info.nso_address[i], info.nso_size[i]);
}
}
}
/* Inform SM about the title for association purposes. */
R_ASSERT(sm::mitm::AssociateProcessIdAndTitleId(process_id, static_cast<u64>(loc.title_id)));
/* If we're overriding for HBL, perform HTML document redirection. */
if (mount.IsHblMounted()) {
/* Don't validate result, failure is okay. */
RedirectHtmlDocumentPathForHbl(loc);
}
/* Clear the ECS entry for the title. */
R_ASSERT(ecs::Clear(loc.title_id));
/* Note that we've created the title. */
SetLaunchedTitle(loc.title_id);
/* Move the process handle to output. */
*out = info.process_handle.Move();
}
return ResultSuccess;
}
Result GetProgramInfo(ProgramInfo *out, const ncm::TitleLocation &loc) {
Meta meta;
/* Load Meta. */
{
ScopedCodeMount mount(loc);
R_TRY(mount.GetResult());
R_TRY(LoadMeta(&meta, loc.title_id));
}
return GetProgramInfoFromMeta(out, &meta);
}
}

View file

@ -16,26 +16,15 @@
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include <stratosphere/ldr.hpp>
#include "ldr_registration.hpp"
#include "ldr_launch_queue.hpp"
#include "ldr_npdm.hpp"
#include "ldr_arguments.hpp"
/* Utilities for Process Creation, for Loader. */
namespace sts::ldr {
class ProcessCreation {
public:
struct ProcessInfo {
u8 name[12];
u32 version;
u64 title_id;
u64 code_addr;
u32 code_num_pages;
u32 process_flags;
Handle reslimit_h;
u32 system_resource_num_pages;
};
static Result ValidateProcessVersion(u64 title_id, u32 version);
static Result InitializeProcessInfo(NpdmUtils::NpdmInfo *npdm, Handle reslimit_h, u64 arg_flags, ProcessInfo *out_proc_info);
static Result CreateProcess(Handle *out_process_h, u64 index, char *nca_path, LaunchQueue::LaunchItem *launch_item, u64 arg_flags, Handle reslimit_h);
};
/* Process Creation API. */
Result CreateProcess(Handle *out, PinId pin_id, const ncm::TitleLocation &loc, const char *path, u32 flags, Handle reslimit_h);
Result GetProgramInfo(ProgramInfo *out, const ncm::TitleLocation &loc);
}

View file

@ -1,142 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
#include "ldr_process_manager.hpp"
#include "ldr_registration.hpp"
#include "ldr_launch_queue.hpp"
#include "ldr_content_management.hpp"
#include "ldr_npdm.hpp"
Result ProcessManagerService::CreateProcess(Out<MovedHandle> proc_h, u64 index, u32 flags, CopiedHandle reslimit_h) {
Registration::TidSid tid_sid;
LaunchQueue::LaunchItem *launch_item;
char nca_path[FS_MAX_PATH] = {0};
ON_SCOPE_EXIT {
/* Loader doesn't persist the copied resource limit handle. */
svcCloseHandle(reslimit_h.handle);
};
R_TRY(Registration::GetRegisteredTidSid(index, &tid_sid));
if (tid_sid.storage_id != FsStorageId_None) {
R_TRY(ContentManagement::ResolveContentPathForTidSid(nca_path, &tid_sid));
}
launch_item = LaunchQueue::GetItem(tid_sid.title_id);
R_TRY(ProcessCreation::CreateProcess(proc_h.GetHandlePointer(), index, nca_path, launch_item, flags, reslimit_h.handle));
ContentManagement::SetCreatedTitle(tid_sid.title_id);
return ResultSuccess;
}
Result ProcessManagerService::GetProgramInfo(OutPointerWithServerSize<ProcessManagerService::ProgramInfo, 0x1> out_program_info, Registration::TidSid tid_sid) {
char nca_path[FS_MAX_PATH] = {0};
/* Zero output. */
std::fill(out_program_info.pointer, out_program_info.pointer + out_program_info.num_elements, ProcessManagerService::ProgramInfo{});
R_TRY(PopulateProgramInfoBuffer(out_program_info.pointer, &tid_sid));
if (tid_sid.storage_id != FsStorageId_None && tid_sid.title_id != out_program_info.pointer->title_id) {
R_TRY(ContentManagement::ResolveContentPathForTidSid(nca_path, &tid_sid));
R_TRY(ContentManagement::RedirectContentPath(nca_path, out_program_info.pointer->title_id, tid_sid.storage_id));
R_TRY(LaunchQueue::AddCopy(tid_sid.title_id, out_program_info.pointer->title_id));
}
return ResultSuccess;
}
Result ProcessManagerService::RegisterTitle(Out<u64> index, Registration::TidSid tid_sid) {
return Registration::RegisterTidSid(&tid_sid, index.GetPointer()) ? 0 : ResultLoaderTooManyProcesses;
}
Result ProcessManagerService::UnregisterTitle(u64 index) {
return Registration::UnregisterIndex(index) ? 0 : ResultLoaderProcessNotRegistered;
}
Result ProcessManagerService::PopulateProgramInfoBuffer(ProcessManagerService::ProgramInfo *out, Registration::TidSid *tid_sid) {
NpdmUtils::NpdmInfo info;
/* Mount code, load NPDM. */
{
bool mounted_code = false;
if (tid_sid->storage_id != FsStorageId_None) {
R_TRY(ContentManagement::MountCodeForTidSid(tid_sid));
mounted_code = true;
} else if (R_SUCCEEDED(ContentManagement::MountCodeNspOnSd(tid_sid->title_id))) {
mounted_code = true;
}
ON_SCOPE_EXIT {
if (mounted_code) {
ContentManagement::UnmountCode();
}
};
R_TRY(NpdmUtils::LoadNpdm(tid_sid->title_id, &info));
}
out->main_thread_priority = info.header->main_thread_prio;
out->default_cpu_id = info.header->default_cpuid;
out->main_thread_stack_size = info.header->main_stack_size;
out->title_id = info.aci0->title_id;
out->acid_fac_size = info.acid->fac_size;
out->aci0_sac_size = info.aci0->sac_size;
out->aci0_fah_size = info.aci0->fah_size;
size_t offset = 0;
/* Copy ACID Service Access Control. */
if (offset + info.acid->sac_size >= sizeof(out->ac_buffer)) {
return ResultLoaderInternalError;
}
out->acid_sac_size = info.acid->sac_size;
std::memcpy(out->ac_buffer + offset, info.acid_sac, out->acid_sac_size);
offset += out->acid_sac_size;
/* Copy ACI0 Service Access Control. */
if (offset + info.aci0->sac_size >= sizeof(out->ac_buffer)) {
return ResultLoaderInternalError;
}
out->aci0_sac_size = info.aci0->sac_size;
std::memcpy(out->ac_buffer + offset, info.aci0_sac, out->aci0_sac_size);
offset += out->aci0_sac_size;
/* Copy ACID Filesystem Access Control. */
if (offset + info.acid->fac_size >= sizeof(out->ac_buffer)) {
return ResultLoaderInternalError;
}
out->acid_fac_size = info.acid->fac_size;
std::memcpy(out->ac_buffer + offset, info.acid_fac, out->acid_fac_size);
offset += out->acid_fac_size;
/* Copy ACI0 Filesystem Access Header. */
if (offset + info.aci0->fah_size >= sizeof(out->ac_buffer)) {
return ResultLoaderInternalError;
}
out->aci0_fah_size = info.aci0->fah_size;
std::memcpy(out->ac_buffer + offset, info.aci0_fah, out->aci0_fah_size);
offset += out->aci0_fah_size;
/* Parse application type. */
out->application_type = NpdmUtils::GetApplicationType(reinterpret_cast<const u32 *>(info.acid_kac), info.acid->kac_size / sizeof(u32));
return ResultSuccess;
}

View file

@ -1,62 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
#include "ldr_registration.hpp"
#include "ldr_process_creation.hpp"
enum ProcessManagerServiceCmd {
Pm_Cmd_CreateProcess = 0,
Pm_Cmd_GetProgramInfo = 1,
Pm_Cmd_RegisterTitle = 2,
Pm_Cmd_UnregisterTitle = 3
};
class ProcessManagerService final : public IServiceObject {
struct ProgramInfo {
u8 main_thread_priority;
u8 default_cpu_id;
u16 application_type;
u32 main_thread_stack_size;
u64 title_id;
u32 acid_sac_size;
u32 aci0_sac_size;
u32 acid_fac_size;
u32 aci0_fah_size;
u8 ac_buffer[0x3E0];
};
static_assert(sizeof(ProcessManagerService::ProgramInfo) == 0x400, "Incorrect ProgramInfo definition.");
private:
/* Actual commands. */
Result CreateProcess(Out<MovedHandle> proc_h, u64 index, u32 flags, CopiedHandle reslimit_h);
Result GetProgramInfo(OutPointerWithServerSize<ProcessManagerService::ProgramInfo, 0x1> out_program_info, Registration::TidSid tid_sid);
Result RegisterTitle(Out<u64> index, Registration::TidSid tid_sid);
Result UnregisterTitle(u64 index);
/* Utilities */
Result PopulateProgramInfoBuffer(ProcessManagerService::ProgramInfo *out, Registration::TidSid *tid_sid);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<Pm_Cmd_CreateProcess, &ProcessManagerService::CreateProcess>(),
MakeServiceCommandMeta<Pm_Cmd_GetProgramInfo, &ProcessManagerService::GetProgramInfo>(),
MakeServiceCommandMeta<Pm_Cmd_RegisterTitle, &ProcessManagerService::RegisterTitle>(),
MakeServiceCommandMeta<Pm_Cmd_UnregisterTitle, &ProcessManagerService::UnregisterTitle>(),
};
};

View file

@ -1,198 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <functional>
#include "ldr_registration.hpp"
static Registration::List g_registration_list = {};
static u64 g_num_registered = 1;
Registration::Process *Registration::GetFreeProcess() {
auto process_it = std::find_if_not(g_registration_list.processes.begin(), g_registration_list.processes.end(), std::mem_fn(&Registration::Process::in_use));
if (process_it == g_registration_list.processes.end()) {
return nullptr;
}
return &*process_it;
}
Registration::Process *Registration::GetProcess(u64 index) {
for (unsigned int i = 0; i < Registration::MaxProcesses; i++) {
if (g_registration_list.processes[i].in_use && g_registration_list.processes[i].index == index) {
return &g_registration_list.processes[i];
}
}
return NULL;
}
Registration::Process *Registration::GetProcessByProcessId(u64 pid) {
for (unsigned int i = 0; i < Registration::MaxProcesses; i++) {
if (g_registration_list.processes[i].in_use && g_registration_list.processes[i].process_id == pid) {
return &g_registration_list.processes[i];
}
}
return NULL;
}
bool Registration::RegisterTidSid(const TidSid *tid_sid, u64 *out_index) {
Registration::Process *free_process = GetFreeProcess();
if (free_process == NULL) {
return false;
}
/* Reset the process. */
*free_process = {};
free_process->tid_sid = *tid_sid;
free_process->in_use = true;
free_process->index = g_num_registered++;
*out_index = free_process->index;
return true;
}
bool Registration::UnregisterIndex(u64 index) {
Registration::Process *target_process = GetProcess(index);
if (target_process == NULL) {
return false;
}
/* Reset the process. */
*target_process = {};
return true;
}
Result Registration::GetRegisteredTidSid(u64 index, Registration::TidSid *out) {
Registration::Process *target_process = GetProcess(index);
if (target_process == NULL) {
return ResultLoaderProcessNotRegistered;
}
*out = target_process->tid_sid;
return ResultSuccess;
}
void Registration::SetProcessIdTidAndIs64BitAddressSpace(u64 index, u64 process_id, u64 tid, bool is_64_bit_addspace) {
Registration::Process *target_process = GetProcess(index);
if (target_process == NULL) {
return;
}
target_process->process_id = process_id;
target_process->title_id = tid;
target_process->is_64_bit_addspace = is_64_bit_addspace;
}
void Registration::AddModuleInfo(u64 index, u64 base_address, u64 size, const unsigned char *build_id) {
Registration::Process *target_process = GetProcess(index);
if (target_process == NULL) {
return;
}
auto nso_info_it = std::find_if_not(target_process->module_infos.begin(), target_process->module_infos.end(), std::mem_fn(&Registration::ModuleInfoHolder::in_use));
if (nso_info_it != target_process->module_infos.end()) {
nso_info_it->info.base_address = base_address;
nso_info_it->info.size = size;
memcpy(nso_info_it->info.build_id, build_id, sizeof(nso_info_it->info.build_id));
nso_info_it->in_use = true;
}
}
Result Registration::GetProcessModuleInfo(LoaderModuleInfo *out, u32 max_out, u64 process_id, u32 *num_written) {
Registration::Process *target_process = GetProcessByProcessId(process_id);
if (target_process == NULL) {
return ResultLoaderProcessNotRegistered;
}
u32 cur = 0;
for (unsigned int i = 0; i < Registration::MaxModuleInfos && cur < max_out; i++) {
if (target_process->module_infos[i].in_use) {
out[cur++] = target_process->module_infos[i].info;
}
}
*num_written = cur;
return ResultSuccess;
}
void Registration::AssociatePidTidForMitM(u64 index) {
Registration::Process *target_process = GetProcess(index);
if (target_process == NULL) {
return;
}
Handle sm_hnd;
if (R_SUCCEEDED(svcConnectToNamedPort(&sm_hnd, "sm:"))) {
ON_SCOPE_EXIT { svcCloseHandle(sm_hnd); };
/* Initialize. */
{
IpcCommand c;
ipcInitialize(&c);
ipcSendPid(&c);
struct {
u64 magic;
u64 cmd_id;
u64 zero;
u64 reserved[2];
} *raw = (decltype(raw))ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 0;
raw->zero = 0;
if (R_FAILED(ipcDispatch(sm_hnd))) {
return;
}
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = (decltype(resp))r.Raw;
if (R_FAILED(resp->result)) {
return;
}
}
/* Associate. */
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 process_id;
u64 title_id;
} *raw = (decltype(raw))ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 65002;
raw->process_id = target_process->process_id;
raw->title_id = target_process->tid_sid.title_id;
ipcDispatch(sm_hnd);
}
}
}

View file

@ -1,65 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <array>
#include "ldr_map.hpp"
class Registration {
public:
static constexpr size_t MaxProcesses = 0x40;
static constexpr size_t MaxModuleInfos = 0x20;
public:
struct ModuleInfoHolder {
bool in_use;
LoaderModuleInfo info;
};
struct TidSid {
u64 title_id;
FsStorageId storage_id;
};
struct Process {
bool in_use;
bool is_64_bit_addspace;
u64 index;
u64 process_id;
u64 title_id;
Registration::TidSid tid_sid;
std::array<Registration::ModuleInfoHolder, MaxModuleInfos> module_infos;
};
struct List {
std::array<Registration::Process, MaxProcesses> processes;
u64 num_processes;
};
static Registration::Process *GetFreeProcess();
static Registration::Process *GetProcess(u64 index);
static Registration::Process *GetProcessByProcessId(u64 pid);
static Result GetRegisteredTidSid(u64 index, Registration::TidSid *out);
static bool RegisterTidSid(const TidSid *tid_sid, u64 *out_index);
static bool UnregisterIndex(u64 index);
static void SetProcessIdTidAndIs64BitAddressSpace(u64 index, u64 process_id, u64 tid, bool is_64_bit_addspace);
static void AddModuleInfo(u64 index, u64 base_address, u64 size, const unsigned char *build_id);
static Result GetProcessModuleInfo(LoaderModuleInfo *out, u32 max_out, u64 process_id, u32 *num_written);
/* Atmosphere MitM Extension. */
static void AssociatePidTidForMitM(u64 index);
};

View file

@ -0,0 +1,172 @@
/*
* Copyright (c) 2018-2019 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/ro.hpp>
#include "ldr_ro_manager.hpp"
namespace sts::ldr::ro {
namespace {
/* Convenience definitions. */
constexpr PinId InvalidPinId = {};
constexpr size_t ProcessCountMax = 0x40;
constexpr size_t ModuleCountMax = 0x20;
/* Types. */
struct ModuleInfo {
ldr::ModuleInfo info;
bool in_use;
};
struct ProcessInfo {
PinId pin_id;
u64 process_id;
ncm::TitleId title_id;
ncm::TitleLocation loc;
ModuleInfo modules[ModuleCountMax];
bool in_use;
};
/* Globals. */
ProcessInfo g_process_infos[ProcessCountMax];
/* Helpers. */
ProcessInfo *GetProcessInfo(PinId pin_id) {
for (size_t i = 0; i < ProcessCountMax; i++) {
ProcessInfo *info = &g_process_infos[i];
if (info->in_use && info->pin_id == pin_id) {
return info;
}
}
return nullptr;
}
ProcessInfo *GetProcessInfo(u64 process_id) {
for (size_t i = 0; i < ProcessCountMax; i++) {
ProcessInfo *info = &g_process_infos[i];
if (info->in_use && info->process_id == process_id) {
return info;
}
}
return nullptr;
}
ProcessInfo *GetFreeProcessInfo() {
for (size_t i = 0; i < ProcessCountMax; i++) {
ProcessInfo *info = &g_process_infos[i];
if (!info->in_use) {
return info;
}
}
return nullptr;
}
}
/* RO Manager API. */
Result PinTitle(PinId *out, const ncm::TitleLocation &loc) {
*out = InvalidPinId;
ProcessInfo *info = GetFreeProcessInfo();
if (info == nullptr) {
return ResultLoaderTooManyProcesses;
}
static u64 s_cur_pin_id = 1;
std::memset(info, 0, sizeof(*info));
info->pin_id = { s_cur_pin_id++ };
info->loc = loc;
info->in_use = true;
*out = info->pin_id;
return ResultSuccess;
}
Result UnpinTitle(PinId id) {
ProcessInfo *info = GetProcessInfo(id);
if (info == nullptr) {
return ResultLoaderNotPinned;
}
info->in_use = false;
return ResultSuccess;
}
Result GetTitleLocation(ncm::TitleLocation *out, PinId id) {
ProcessInfo *info = GetProcessInfo(id);
if (info == nullptr) {
return ResultLoaderNotPinned;
}
*out = info->loc;
return ResultSuccess;
}
Result RegisterProcess(PinId id, u64 process_id, ncm::TitleId title_id) {
ProcessInfo *info = GetProcessInfo(id);
if (info == nullptr) {
return ResultLoaderNotPinned;
}
info->title_id = title_id;
info->process_id = process_id;
return ResultSuccess;
}
Result RegisterModule(PinId id, const u8 *build_id, uintptr_t address, size_t size) {
ProcessInfo *info = GetProcessInfo(id);
if (info == nullptr) {
return ResultLoaderNotPinned;
}
/* Nintendo doesn't actually care about successful allocation. */
for (size_t i = 0; i < ModuleCountMax; i++) {
ModuleInfo *module = &info->modules[i];
if (!module->in_use) {
continue;
}
std::memcpy(module->info.build_id, build_id, sizeof(module->info.build_id));
module->info.base_address = address;
module->info.size = size;
break;
}
return ResultSuccess;
}
Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, u64 process_id) {
const ProcessInfo *info = GetProcessInfo(process_id);
if (info == nullptr) {
return ResultLoaderNotPinned;
}
size_t count = 0;
for (size_t i = 0; i < ModuleCountMax && count < max_out_count; i++) {
const ModuleInfo *module = &info->modules[i];
if (!module->in_use) {
continue;
}
out[count++] = module->info;
}
*out_count = static_cast<u32>(count);
return ResultSuccess;
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
#include <stratosphere/ldr.hpp>
namespace sts::ldr::ro {
/* RO Manager API. */
Result PinTitle(PinId *out, const ncm::TitleLocation &loc);
Result UnpinTitle(PinId id);
Result GetTitleLocation(ncm::TitleLocation *out, PinId id);
Result RegisterProcess(PinId id, u64 process_id, ncm::TitleId title_id);
Result RegisterModule(PinId id, const u8 *build_id, uintptr_t address, size_t size);
Result GetProcessModuleInfo(u32 *out_count, ModuleInfo *out, size_t max_out_count, u64 process_id);
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
#include "ldr_shell.hpp"
#include "ldr_launch_queue.hpp"
#include "ldr_content_management.hpp"
Result ShellService::AddTitleToLaunchQueue(u64 tid, InPointer<char> args, u32 args_size) {
if (args.num_elements < args_size) args_size = args.num_elements;
return LaunchQueue::Add(tid, args.pointer, args_size);
}
void ShellService::ClearLaunchQueue() {
LaunchQueue::Clear();
}
/* SetExternalContentSource extension */
Result ShellService::SetExternalContentSource(Out<MovedHandle> out, u64 tid) {
Handle server_h;
Handle client_h;
R_TRY(svcCreateSession(&server_h, &client_h, 0, 0));
Service service;
serviceCreate(&service, client_h);
ContentManagement::SetExternalContentSource(tid, FsFileSystem {service});
out.SetValue(server_h);
return ResultSuccess;
}
void ShellService::ClearExternalContentSource(u64 tid) {
ContentManagement::ClearExternalContentSource(tid);
}

View file

@ -1,45 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <switch.h>
#include <stratosphere.hpp>
enum ShellServiceCmd {
Shell_Cmd_AddTitleToLaunchQueue = 0,
Shell_Cmd_ClearLaunchQueue = 1,
Shell_Cmd_AtmosphereSetExternalContentSource = 65000,
Shell_Cmd_AtmosphereClearExternalContentSource = 65001,
};
class ShellService final : public IServiceObject {
private:
/* Actual commands. */
Result AddTitleToLaunchQueue(u64 tid, InPointer<char> args, u32 args_size);
void ClearLaunchQueue();
/* Atmosphere commands. */
Result SetExternalContentSource(Out<MovedHandle> out, u64 tid);
void ClearExternalContentSource(u64 tid);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<Shell_Cmd_AddTitleToLaunchQueue, &ShellService::AddTitleToLaunchQueue>(),
MakeServiceCommandMeta<Shell_Cmd_ClearLaunchQueue, &ShellService::ClearLaunchQueue>(),
MakeServiceCommandMeta<Shell_Cmd_AtmosphereSetExternalContentSource, &ShellService::SetExternalContentSource>(),
MakeServiceCommandMeta<Shell_Cmd_AtmosphereClearExternalContentSource, &ShellService::ClearExternalContentSource>(),
};
};

File diff suppressed because it is too large Load diff

View file

@ -1,569 +0,0 @@
/*
* LZ4 - Fast LZ compression algorithm
* Header File
* Copyright (C) 2011-2017, Yann Collet.
BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
You can contact the author at :
- LZ4 homepage : http://www.lz4.org
- LZ4 source repository : https://github.com/lz4/lz4
*/
#if defined (__cplusplus)
extern "C" {
#endif
#ifndef LZ4_H_2983827168210
#define LZ4_H_2983827168210
/* --- Dependency --- */
#include <stddef.h> /* size_t */
/**
Introduction
LZ4 is lossless compression algorithm, providing compression speed at 400 MB/s per core,
scalable with multi-cores CPU. It features an extremely fast decoder, with speed in
multiple GB/s per core, typically reaching RAM speed limits on multi-core systems.
The LZ4 compression library provides in-memory compression and decompression functions.
Compression can be done in:
- a single step (described as Simple Functions)
- a single step, reusing a context (described in Advanced Functions)
- unbounded multiple steps (described as Streaming compression)
lz4.h provides block compression functions. It gives full buffer control to user.
Decompressing an lz4-compressed block also requires metadata (such as compressed size).
Each application is free to encode such metadata in whichever way it wants.
An additional format, called LZ4 frame specification (doc/lz4_Frame_format.md),
take care of encoding standard metadata alongside LZ4-compressed blocks.
If your application requires interoperability, it's recommended to use it.
A library is provided to take care of it, see lz4frame.h.
*/
/*^***************************************************************
* Export parameters
*****************************************************************/
/*
* LZ4_DLL_EXPORT :
* Enable exporting of functions when building a Windows DLL
* LZ4LIB_VISIBILITY :
* Control library symbols visibility.
*/
#ifndef LZ4LIB_VISIBILITY
# if defined(__GNUC__) && (__GNUC__ >= 4)
# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default")))
# else
# define LZ4LIB_VISIBILITY
# endif
#endif
#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1)
# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY
#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1)
# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
#else
# define LZ4LIB_API LZ4LIB_VISIBILITY
#endif
/*------ Version ------*/
#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */
#define LZ4_VERSION_MINOR 8 /* for new (non-breaking) interface capabilities */
#define LZ4_VERSION_RELEASE 2 /* for tweaks, bug-fixes, or development */
#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE)
#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE
#define LZ4_QUOTE(str) #str
#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str)
#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION)
LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */
LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; unseful to check dll version */
/*-************************************
* Tuning parameter
**************************************/
/*!
* LZ4_MEMORY_USAGE :
* Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.)
* Increasing memory usage improves compression ratio
* Reduced memory usage may improve speed, thanks to cache effect
* Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache
*/
#ifndef LZ4_MEMORY_USAGE
# define LZ4_MEMORY_USAGE 14
#endif
/*-************************************
* Simple Functions
**************************************/
/*! LZ4_compress_default() :
Compresses 'srcSize' bytes from buffer 'src'
into already allocated 'dst' buffer of size 'dstCapacity'.
Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize).
It also runs faster, so it's a recommended setting.
If the function cannot compress 'src' into a more limited 'dst' budget,
compression stops *immediately*, and the function result is zero.
Note : as a consequence, 'dst' content is not valid.
Note 2 : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer).
srcSize : max supported value is LZ4_MAX_INPUT_SIZE.
dstCapacity : size of buffer 'dst' (which must be already allocated)
return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity)
or 0 if compression fails */
LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity);
/*! LZ4_decompress_safe() :
compressedSize : is the exact complete size of the compressed block.
dstCapacity : is the size of destination buffer, which must be already allocated.
return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity)
If destination buffer is not large enough, decoding will stop and output an error code (negative value).
If the source stream is detected malformed, the function will stop decoding and return a negative result.
This function is protected against malicious data packets.
*/
LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity);
/*-************************************
* Advanced Functions
**************************************/
#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */
#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16)
/*!
LZ4_compressBound() :
Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible)
This function is primarily useful for memory allocation purposes (destination buffer size).
Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example).
Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize)
inputSize : max supported value is LZ4_MAX_INPUT_SIZE
return : maximum output size in a "worst case" scenario
or 0, if input size is incorrect (too large or negative)
*/
LZ4LIB_API int LZ4_compressBound(int inputSize);
/*!
LZ4_compress_fast() :
Same as LZ4_compress_default(), but allows selection of "acceleration" factor.
The larger the acceleration value, the faster the algorithm, but also the lesser the compression.
It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed.
An acceleration value of "1" is the same as regular LZ4_compress_default()
Values <= 0 will be replaced by ACCELERATION_DEFAULT (currently == 1, see lz4.c).
*/
LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*!
LZ4_compress_fast_extState() :
Same compression function, just using an externally allocated memory space to store compression state.
Use LZ4_sizeofState() to know how much memory must be allocated,
and allocate it on 8-bytes boundaries (using malloc() typically).
Then, provide it as 'void* state' to compression function.
*/
LZ4LIB_API int LZ4_sizeofState(void);
LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*!
LZ4_compress_destSize() :
Reverse the logic : compresses as much data as possible from 'src' buffer
into already allocated buffer 'dst' of size 'targetDestSize'.
This function either compresses the entire 'src' content into 'dst' if it's large enough,
or fill 'dst' buffer completely with as much data as possible from 'src'.
*srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'.
New value is necessarily <= old value.
return : Nb bytes written into 'dst' (necessarily <= targetDestSize)
or 0 if compression fails
*/
LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize);
/*!
LZ4_decompress_fast() : **unsafe!**
This function is a bit faster than LZ4_decompress_safe(),
but doesn't provide any security guarantee.
originalSize : is the uncompressed size to regenerate
Destination buffer must be already allocated, and its size must be >= 'originalSize' bytes.
return : number of bytes read from source buffer (== compressed size).
If the source stream is detected malformed, the function stops decoding and return a negative result.
note : This function respects memory boundaries for *properly formed* compressed data.
However, it does not provide any protection against malicious input.
It also doesn't know 'src' size, and implies it's >= compressed size.
Use this function in trusted environment **only**.
*/
LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize);
/*!
LZ4_decompress_safe_partial() :
This function decompress a compressed block of size 'srcSize' at position 'src'
into destination buffer 'dst' of size 'dstCapacity'.
The function will decompress a minimum of 'targetOutputSize' bytes, and stop after that.
However, it's not accurate, and may write more than 'targetOutputSize' (but always <= dstCapacity).
@return : the number of bytes decoded in the destination buffer (necessarily <= dstCapacity)
Note : this number can also be < targetOutputSize, if compressed block contains less data.
Therefore, always control how many bytes were decoded.
If source stream is detected malformed, function returns a negative result.
This function is protected against malicious data packets.
*/
LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity);
/*-*********************************************
* Streaming Compression Functions
***********************************************/
typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */
/*! LZ4_createStream() and LZ4_freeStream() :
* LZ4_createStream() will allocate and initialize an `LZ4_stream_t` structure.
* LZ4_freeStream() releases its memory.
*/
LZ4LIB_API LZ4_stream_t* LZ4_createStream(void);
LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr);
/*! LZ4_resetStream() :
* An LZ4_stream_t structure can be allocated once and re-used multiple times.
* Use this function to start compressing a new stream.
*/
LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr);
/*! LZ4_loadDict() :
* Use this function to load a static dictionary into LZ4_stream_t.
* Any previous data will be forgotten, only 'dictionary' will remain in memory.
* Loading a size of 0 is allowed, and is the same as reset.
* @return : dictionary size, in bytes (necessarily <= 64 KB)
*/
LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize);
/*! LZ4_compress_fast_continue() :
* Compress 'src' content using data from previously compressed blocks, for better compression ratio.
* 'dst' buffer must be already allocated.
* If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster.
*
* Important : The previous 64KB of compressed data is assumed to remain present and unmodified in memory!
*
* Special 1 : When input is a double-buffer, they can have any size, including < 64 KB.
* Make sure that buffers are separated by at least one byte.
* This way, each block only depends on previous block.
* Special 2 : If input buffer is a ring-buffer, it can have any size, including < 64 KB.
*
* @return : size of compressed block
* or 0 if there is an error (typically, cannot fit into 'dst').
* After an error, the stream status is invalid, it can only be reset or freed.
*/
LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*! LZ4_saveDict() :
* If last 64KB data cannot be guaranteed to remain available at its current memory location,
* save it into a safer place (char* safeBuffer).
* This is schematically equivalent to a memcpy() followed by LZ4_loadDict(),
* but is much faster, because LZ4_saveDict() doesn't need to rebuild tables.
* @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error.
*/
LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize);
/*-**********************************************
* Streaming Decompression Functions
* Bufferless synchronous API
************************************************/
typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* incomplete type (defined later) */
/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() :
* creation / destruction of streaming decompression tracking structure.
* A tracking structure can be re-used multiple times sequentially. */
LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void);
LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream);
/*! LZ4_setStreamDecode() :
* An LZ4_streamDecode_t structure can be allocated once and re-used multiple times.
* Use this function to start decompression of a new stream of blocks.
* A dictionary can optionnally be set. Use NULL or size 0 for a reset order.
* @return : 1 if OK, 0 if error
*/
LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize);
/*! LZ4_decompress_*_continue() :
* These decoding functions allow decompression of consecutive blocks in "streaming" mode.
* A block is an unsplittable entity, it must be presented entirely to a decompression function.
* Decompression functions only accept one block at a time.
* The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded.
* If less than 64KB of data has been decoded all the data must be present.
*
* Special : if application sets a ring buffer for decompression, it must respect one of the following conditions :
* - Exactly same size as encoding buffer, with same update rule (block boundaries at same positions)
* In which case, the decoding & encoding ring buffer can have any size, including very small ones ( < 64 KB).
* - Larger than encoding buffer, by a minimum of maxBlockSize more bytes.
* maxBlockSize is implementation dependent. It's the maximum size of any single block.
* In which case, encoding and decoding buffers do not need to be synchronized,
* and encoding ring buffer can have any size, including small ones ( < 64 KB).
* - _At least_ 64 KB + 8 bytes + maxBlockSize.
* In which case, encoding and decoding buffers do not need to be synchronized,
* and encoding ring buffer can have any size, including larger than decoding buffer.
* Whenever these conditions are not possible, save the last 64KB of decoded data into a safe buffer,
* and indicate where it is saved using LZ4_setStreamDecode() before decompressing next block.
*/
LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity);
LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize);
/*! LZ4_decompress_*_usingDict() :
* These decoding functions work the same as
* a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue()
* They are stand-alone, and don't need an LZ4_streamDecode_t structure.
*/
LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize);
LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize);
/*^**********************************************
* !!!!!! STATIC LINKING ONLY !!!!!!
***********************************************/
/*-************************************
* Unstable declarations
**************************************
* Declarations in this section should be considered unstable.
* Use at your own peril, etc., etc.
* They may be removed in the future.
* Their signatures may change.
**************************************/
#ifdef LZ4_STATIC_LINKING_ONLY
/*! LZ4_resetStream_fast() :
* When an LZ4_stream_t is known to be in a internally coherent state,
* it can often be prepared for a new compression with almost no work, only
* sometimes falling back to the full, expensive reset that is always required
* when the stream is in an indeterminate state (i.e., the reset performed by
* LZ4_resetStream()).
*
* LZ4_streams are guaranteed to be in a valid state when:
* - returned from LZ4_createStream()
* - reset by LZ4_resetStream()
* - memset(stream, 0, sizeof(LZ4_stream_t))
* - the stream was in a valid state and was reset by LZ4_resetStream_fast()
* - the stream was in a valid state and was then used in any compression call
* that returned success
* - the stream was in an indeterminate state and was used in a compression
* call that fully reset the state (LZ4_compress_fast_extState()) and that
* returned success
*/
LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr);
/*! LZ4_compress_fast_extState_fastReset() :
* A variant of LZ4_compress_fast_extState().
*
* Using this variant avoids an expensive initialization step. It is only safe
* to call if the state buffer is known to be correctly initialized already
* (see above comment on LZ4_resetStream_fast() for a definition of "correctly
* initialized"). From a high level, the difference is that this function
* initializes the provided state with a call to LZ4_resetStream_fast() while
* LZ4_compress_fast_extState() starts with a call to LZ4_resetStream().
*/
LZ4LIB_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*! LZ4_attach_dictionary() :
* This is an experimental API that allows for the efficient use of a
* static dictionary many times.
*
* Rather than re-loading the dictionary buffer into a working context before
* each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a
* working LZ4_stream_t, this function introduces a no-copy setup mechanism,
* in which the working stream references the dictionary stream in-place.
*
* Several assumptions are made about the state of the dictionary stream.
* Currently, only streams which have been prepared by LZ4_loadDict() should
* be expected to work.
*
* Alternatively, the provided dictionary stream pointer may be NULL, in which
* case any existing dictionary stream is unset.
*
* If a dictionary is provided, it replaces any pre-existing stream history.
* The dictionary contents are the only history that can be referenced and
* logically immediately precede the data compressed in the first subsequent
* compression call.
*
* The dictionary will only remain attached to the working stream through the
* first compression call, at the end of which it is cleared. The dictionary
* stream (and source buffer) must remain in-place / accessible / unchanged
* through the completion of the first compression call on the stream.
*/
LZ4LIB_API void LZ4_attach_dictionary(LZ4_stream_t *working_stream, const LZ4_stream_t *dictionary_stream);
#endif
/*-************************************
* Private definitions
**************************************
* Do not use these definitions.
* They are exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`.
* Using these definitions will expose code to API and/or ABI break in future versions of the library.
**************************************/
#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2)
#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE)
#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */
#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
#include <stdint.h>
typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
struct LZ4_stream_t_internal {
uint32_t hashTable[LZ4_HASH_SIZE_U32];
uint32_t currentOffset;
uint16_t initCheck;
uint16_t tableType;
const uint8_t* dictionary;
const LZ4_stream_t_internal* dictCtx;
uint32_t dictSize;
};
typedef struct {
const uint8_t* externalDict;
size_t extDictSize;
const uint8_t* prefixEnd;
size_t prefixSize;
} LZ4_streamDecode_t_internal;
#else
typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
struct LZ4_stream_t_internal {
unsigned int hashTable[LZ4_HASH_SIZE_U32];
unsigned int currentOffset;
unsigned short initCheck;
unsigned short tableType;
const unsigned char* dictionary;
const LZ4_stream_t_internal* dictCtx;
unsigned int dictSize;
};
typedef struct {
const unsigned char* externalDict;
size_t extDictSize;
const unsigned char* prefixEnd;
size_t prefixSize;
} LZ4_streamDecode_t_internal;
#endif
/*!
* LZ4_stream_t :
* information structure to track an LZ4 stream.
* init this structure before first use.
* note : only use in association with static linking !
* this definition is not API/ABI safe,
* it may change in a future version !
*/
#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4)
#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long))
union LZ4_stream_u {
unsigned long long table[LZ4_STREAMSIZE_U64];
LZ4_stream_t_internal internal_donotuse;
} ; /* previously typedef'd to LZ4_stream_t */
/*!
* LZ4_streamDecode_t :
* information structure to track an LZ4 stream during decompression.
* init this structure using LZ4_setStreamDecode (or memset()) before first use
* note : only use in association with static linking !
* this definition is not API/ABI safe,
* and may change in a future version !
*/
#define LZ4_STREAMDECODESIZE_U64 4
#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long))
union LZ4_streamDecode_u {
unsigned long long table[LZ4_STREAMDECODESIZE_U64];
LZ4_streamDecode_t_internal internal_donotuse;
} ; /* previously typedef'd to LZ4_streamDecode_t */
/*-************************************
* Obsolete Functions
**************************************/
/*! Deprecation warnings
Should deprecation warnings be a problem,
it is generally possible to disable them,
typically with -Wno-deprecated-declarations for gcc
or _CRT_SECURE_NO_WARNINGS in Visual.
Otherwise, it's also possible to define LZ4_DISABLE_DEPRECATE_WARNINGS */
#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS
# define LZ4_DEPRECATED(message) /* disable deprecation warnings */
#else
# define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */
# define LZ4_DEPRECATED(message) [[deprecated(message)]]
# elif (LZ4_GCC_VERSION >= 405) || defined(__clang__)
# define LZ4_DEPRECATED(message) __attribute__((deprecated(message)))
# elif (LZ4_GCC_VERSION >= 301)
# define LZ4_DEPRECATED(message) __attribute__((deprecated))
# elif defined(_MSC_VER)
# define LZ4_DEPRECATED(message) __declspec(deprecated(message))
# else
# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler")
# define LZ4_DEPRECATED(message)
# endif
#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */
/* Obsolete compression functions */
LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* source, char* dest, int sourceSize);
LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* source, char* dest, int sourceSize, int maxOutputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize);
/* Obsolete decompression functions */
LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize);
LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize);
/* Obsolete streaming functions; degraded functionality; do not use!
*
* In order to perform streaming compression, these functions depended on data
* that is no longer tracked in the state. They have been preserved as well as
* possible: using them will still produce a correct output. However, they don't
* actually retain any history between compression calls. The compression ratio
* achieved will therefore be no better than compressing each chunk
* independently.
*/
LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer);
LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void);
LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer);
LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state);
/* Obsolete streaming decoding functions */
LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize);
LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize);
#endif /* LZ4_H_2983827168210 */
#if defined (__cplusplus)
}
#endif

View file

@ -29,7 +29,6 @@
#include "pm_registration.hpp"
#include "pm_boot_mode.hpp"
static std::vector<u64> g_launched_titles;
static bool IsHexadecimal(const char *str) {
while (*str) {
@ -42,23 +41,11 @@ static bool IsHexadecimal(const char *str) {
return true;
}
static bool HasLaunchedTitle(u64 title_id) {
return std::find(g_launched_titles.begin(), g_launched_titles.end(), title_id) != g_launched_titles.end();
}
static void SetLaunchedTitle(u64 title_id) {
g_launched_titles.push_back(title_id);
}
static void ClearLaunchedTitles() {
g_launched_titles.clear();
}
static void LaunchTitle(u64 title_id, FsStorageId storage_id, u32 launch_flags, u64 *pid) {
u64 local_pid = 0;
/* Don't launch a title twice during boot2. */
if (HasLaunchedTitle(title_id)) {
if (Registration::HasLaunchedTitle(title_id)) {
return;
}
@ -80,8 +67,6 @@ static void LaunchTitle(u64 title_id, FsStorageId storage_id, u32 launch_flags,
if (pid) {
*pid = local_pid;
}
SetLaunchedTitle(title_id);
}
static bool GetGpioPadLow(GpioPadName pad) {
@ -199,9 +184,6 @@ void EmbeddedBoot2::Main() {
/* Wait until fs.mitm has installed itself. We want this to happen as early as possible. */
WaitForMitm("fsp-srv");
/* Clear titles. */
ClearLaunchedTitles();
/* psc, bus, pcv is the minimal set of required titles to get SD card. */
/* bus depends on pcie, and pcv depends on settings. */
/* Launch psc. */
@ -260,7 +242,7 @@ void EmbeddedBoot2::Main() {
while ((ent = readdir(titles_dir)) != NULL) {
if (strlen(ent->d_name) == 0x10 && IsHexadecimal(ent->d_name)) {
u64 title_id = (u64)strtoul(ent->d_name, NULL, 16);
if (HasLaunchedTitle(title_id)) {
if (Registration::HasLaunchedTitle(title_id)) {
continue;
}
char title_path[FS_MAX_PATH] = {0};
@ -290,7 +272,4 @@ void EmbeddedBoot2::Main() {
/* We no longer need the SD card. */
fsdevUnmountAll();
/* Free the memory used to track what boot2 launches. */
ClearLaunchedTitles();
}

View file

@ -15,6 +15,9 @@
*/
#include <switch.h>
#include <stratosphere/ldr.hpp>
#include <stratosphere/ldr/ldr_pm_api.hpp>
#include "pm_registration.hpp"
#include "pm_info.hpp"
@ -22,9 +25,26 @@ Result InformationService::GetTitleId(Out<u64> tid, u64 pid) {
std::scoped_lock<ProcessList &> lk(Registration::GetProcessList());
std::shared_ptr<Registration::Process> proc = Registration::GetProcess(pid);
if (proc != NULL) {
if (proc == NULL) {
return ResultPmProcessNotFound;
}
tid.SetValue(proc->tid_sid.title_id);
return ResultSuccess;
}
Result InformationService::AtmosphereGetProcessId(Out<u64> pid, u64 tid) {
std::scoped_lock<ProcessList &> lk(Registration::GetProcessList());
std::shared_ptr<Registration::Process> proc = Registration::GetProcessByTitleId(tid);
if (proc == nullptr) {
return ResultPmProcessNotFound;
}
pid.SetValue(proc->pid);
return ResultSuccess;
}
Result InformationService::AtmosphereHasLaunchedTitle(Out<bool> out, u64 tid) {
return sts::ldr::pm::HasLaunchedTitle(out.GetPointer(), sts::ncm::TitleId{tid});
}

View file

@ -20,14 +20,23 @@
enum InformationCmd {
Information_Cmd_GetTitleId = 0,
Information_Cmd_AtmosphereGetProcessId = 65000,
Information_Cmd_AtmosphereHasCreatedTitle = 65001,
};
class InformationService final : public IServiceObject {
private:
/* Actual commands. */
Result GetTitleId(Out<u64> tid, u64 pid);
/* Atmosphere commands. */
Result AtmosphereGetProcessId(Out<u64> pid, u64 tid);
Result AtmosphereHasLaunchedTitle(Out<bool> out, u64 tid);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<Information_Cmd_GetTitleId, &InformationService::GetTitleId>(),
MakeServiceCommandMeta<Information_Cmd_AtmosphereGetProcessId, &InformationService::AtmosphereGetProcessId>(),
MakeServiceCommandMeta<Information_Cmd_AtmosphereHasCreatedTitle, &InformationService::AtmosphereHasLaunchedTitle>(),
};
};

View file

@ -36,7 +36,7 @@ extern "C" {
u32 __nx_applet_type = AppletType_None;
#define INNER_HEAP_SIZE 0x30000
#define INNER_HEAP_SIZE 0x40000
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
@ -100,12 +100,12 @@ void __appInit(void) {
DoWithSmSession([&]() {
R_ASSERT(fsprInitialize());
R_ASSERT(smManagerInitialize());
/* This works around a bug with process permissions on < 4.0.0. */
RegisterPrivilegedProcessesWithFs();
/* Use AMS manager extension to tell SM that FS has been worked around. */
R_ASSERT(smManagerInitialize());
R_ASSERT(sts::sm::manager::EndInitialDefers());
R_ASSERT(lrInitialize());
@ -145,7 +145,7 @@ int main(int argc, char **argv)
s_server_manager.AddWaitable(new ServiceServer<ShellService>("pm:shell", 3));
s_server_manager.AddWaitable(new ServiceServer<DebugMonitorService>("pm:dmnt", 3));
s_server_manager.AddWaitable(new ServiceServer<BootModeService>("pm:bm", 6));
s_server_manager.AddWaitable(new ServiceServer<InformationService>("pm:info", 3));
s_server_manager.AddWaitable(new ServiceServer<InformationService>("pm:info", 19));
/* Loop forever, servicing our services. */
s_server_manager.Process();

View file

@ -13,9 +13,12 @@
* 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 <atomic>
#include <switch.h>
#include <stratosphere.hpp>
#include <atomic>
#include <stratosphere/ldr.hpp>
#include <stratosphere/ldr/ldr_pm_api.hpp>
#include "pm_registration.hpp"
#include "pm_resource_limits.hpp"
@ -492,6 +495,12 @@ Handle Registration::GetBootFinishedEventHandle() {
return g_boot_finished_event->GetHandle();
}
bool Registration::HasLaunchedTitle(u64 title_id) {
bool has_launched = false;
R_ASSERT(sts::ldr::pm::HasLaunchedTitle(&has_launched, sts::ncm::TitleId{title_id}));
return has_launched;
}
void Registration::SignalBootFinished() {
g_boot_finished_event->Signal();
}

View file

@ -204,6 +204,8 @@ class Registration {
static Result LaunchProcess(u64 title_id, FsStorageId storage_id, u64 launch_flags, u64 *out_pid);
static Result LaunchProcessByTidSid(TidSid tid_sid, u64 launch_flags, u64 *out_pid);
static bool HasLaunchedTitle(u64 title_id);
static void SignalBootFinished();
static bool HasApplicationProcess(std::shared_ptr<Process> *out = nullptr);