mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-11-25 11:00:18 +00:00
loader: completely rewrite.
This commit is contained in:
parent
9217e4c5f9
commit
61fcf5e0f4
49 changed files with 2523 additions and 5817 deletions
|
@ -37,7 +37,7 @@ static std::atomic_bool g_has_hid_session = false;
|
||||||
|
|
||||||
/* Content override support variables/types */
|
/* Content override support variables/types */
|
||||||
static OverrideKey g_default_override_key = {
|
static OverrideKey g_default_override_key = {
|
||||||
.key_combination = KEY_R,
|
.key_combination = KEY_L,
|
||||||
.override_by_default = true
|
.override_by_default = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -49,11 +49,11 @@ struct HblOverrideConfig {
|
||||||
|
|
||||||
static HblOverrideConfig g_hbl_override_config = {
|
static HblOverrideConfig g_hbl_override_config = {
|
||||||
.override_key = {
|
.override_key = {
|
||||||
.key_combination = KEY_L,
|
.key_combination = KEY_R,
|
||||||
.override_by_default = true
|
.override_by_default = false
|
||||||
},
|
},
|
||||||
.title_id = TitleId_AppletPhotoViewer,
|
.title_id = TitleId_AppletPhotoViewer,
|
||||||
.override_any_app = false
|
.override_any_app = true
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Static buffer for loader.ini contents at runtime. */
|
/* Static buffer for loader.ini contents at runtime. */
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit cf5c6cdad9ec4066d763c3317e98f7027d3172a6
|
Subproject commit e37089d167ff2cd8009924bfa32ab179db918376
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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__ */
|
|
77
stratosphere/loader/source/ldr_arguments.cpp
Normal file
77
stratosphere/loader/source/ldr_arguments.cpp
Normal 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 *GetArgumentInfo(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 *GetFreeArgumentInfo() {
|
||||||
|
return GetArgumentInfo(FreeTitleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* API. */
|
||||||
|
const ArgumentInfo *Get(ncm::TitleId title_id) {
|
||||||
|
return GetArgumentInfo(title_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Set(ncm::TitleId title_id, const void *args, size_t args_size) {
|
||||||
|
if (args_size >= ArgumentSizeMax) {
|
||||||
|
return ResultLoaderTooLongArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentInfo *arg_info = GetArgumentInfo(title_id);
|
||||||
|
if (arg_info == nullptr) {
|
||||||
|
arg_info = GetFreeArgumentInfo();
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,27 +16,22 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include <stratosphere/ldr.hpp>
|
||||||
|
|
||||||
#define LAUNCH_QUEUE_SIZE (10)
|
namespace sts::ldr::args {
|
||||||
#define LAUNCH_QUEUE_FULL (-1)
|
|
||||||
|
|
||||||
#define LAUNCH_QUEUE_ARG_SIZE_MAX (0x8000)
|
constexpr size_t ArgumentSizeMax = 0x8000;
|
||||||
|
|
||||||
class LaunchQueue {
|
struct ArgumentInfo {
|
||||||
public:
|
ncm::TitleId title_id;
|
||||||
struct LaunchItem {
|
size_t args_size;
|
||||||
u64 tid;
|
u8 args[ArgumentSizeMax];
|
||||||
u64 arg_size;
|
};
|
||||||
char args[LAUNCH_QUEUE_ARG_SIZE_MAX];
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
|
||||||
};
|
|
428
stratosphere/loader/source/ldr_capabilities.cpp
Normal file
428
stratosphere/loader/source/ldr_capabilities.cpp
Normal file
|
@ -0,0 +1,428 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
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 restrict = Decode(kac[i]);
|
||||||
|
|
||||||
|
if (this->GetMinimumThreadPriority() < restrict.GetMinimumThreadPriority() ||
|
||||||
|
this->GetMaximumThreadPriority() > restrict.GetMaximumThreadPriority() ||
|
||||||
|
this->GetMinimumThreadPriority() > this->GetMaximumThreadPriority()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->GetMinimumCoreId() < restrict.GetMinimumCoreId() ||
|
||||||
|
this->GetMaximumCoreId() > restrict.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 restrict = Decode(kac[i]);
|
||||||
|
|
||||||
|
if (this->GetIndex() == restrict.GetIndex() && this->GetMask() == restrict.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 restrict = Decode(kac[i]);
|
||||||
|
i++;
|
||||||
|
if (i >= kac_count || GetCapabilityId(kac[i]) != this->GetId()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto restrict_next = Decode(kac[i]);
|
||||||
|
const u32 restrict_start = restrict.GetAddressSize();
|
||||||
|
const u32 restrict_size = restrict_next.GetAddressSize();
|
||||||
|
const u32 restrict_end = restrict_start + restrict_size;
|
||||||
|
|
||||||
|
if (restrict_size >= SizeMax) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->GetFlag() == restrict.GetFlag() && next.GetFlag() == restrict_next.GetFlag()) {
|
||||||
|
if (restrict_start <= start && start <= restrict_end && end <= restrict_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 restrict = Decode(kac[i]);
|
||||||
|
|
||||||
|
if (this->GetValue() == restrict.GetValue()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_CAPABILITY_CLASS(InterruptPair,
|
||||||
|
Result Validate(const u32 *kac, size_t kac_count) const;
|
||||||
|
|
||||||
|
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 restrict = Decode(kac[i]);
|
||||||
|
|
||||||
|
if (restrict.GetInterruptId0() == EmptyInterruptId && restrict.GetInterruptId1() == EmptyInterruptId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restrict.GetInterruptId0() == id || restrict.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 restrict = Decode(kac[i]);
|
||||||
|
|
||||||
|
return restrict.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 restrict = Decode(kac[i]);
|
||||||
|
|
||||||
|
return restrict.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 restrict = Decode(kac[i]);
|
||||||
|
|
||||||
|
return this->GetSize() <= restrict.GetSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_CAPABILITY_CLASS(DebugFlags,
|
||||||
|
bool GetAllowDebug() const {
|
||||||
|
return ((this->GetValue() >> 0) & 1) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetForceDebug() const {
|
||||||
|
return ((this->GetValue() >> 1) & 1) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 restrict = Decode(kac[i]);
|
||||||
|
|
||||||
|
return (restrict.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
stratosphere/loader/source/ldr_capabilities.hpp
Normal file
29
stratosphere/loader/source/ldr_capabilities.hpp
Normal 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 {
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
}
|
|
@ -13,459 +13,331 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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_content_management.hpp"
|
||||||
#include "ldr_hid.hpp"
|
#include "ldr_ecs.hpp"
|
||||||
#include "ldr_npdm.hpp"
|
|
||||||
|
|
||||||
#include "ini.h"
|
namespace sts::ldr {
|
||||||
|
|
||||||
static FsFileSystem g_CodeFileSystem = {};
|
namespace {
|
||||||
static FsFileSystem g_HblFileSystem = {};
|
|
||||||
|
|
||||||
static std::vector<u64> g_created_titles;
|
/* DeviceNames. */
|
||||||
static bool g_has_initialized_fs_dev = false;
|
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. */
|
constexpr const char *SdCardStorageMountPoint = "@Sdcard";
|
||||||
static bool g_mounted_hbl_nsp = false;
|
|
||||||
static char g_hbl_sd_path[FS_MAX_PATH+1] = "@Sdcard:/atmosphere/hbl.nsp\x00";
|
|
||||||
|
|
||||||
static OverrideKey g_default_override_key = {
|
/* Globals. */
|
||||||
.key_combination = KEY_L,
|
bool g_has_mounted_sd_card = false;
|
||||||
.override_by_default = true
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HblOverrideConfig {
|
ncm::TitleId g_should_override_title_id;
|
||||||
OverrideKey override_key;
|
bool g_should_override_hbl = false;
|
||||||
u64 title_id;
|
bool g_should_override_sd = false;
|
||||||
bool override_any_app;
|
|
||||||
};
|
|
||||||
|
|
||||||
static HblOverrideConfig g_hbl_override_config = {
|
/* Helpers. */
|
||||||
.override_key = {
|
inline void FixFileSystemPath(char *path) {
|
||||||
.key_combination = KEY_R,
|
for (size_t i = 0; i < FS_MAX_PATH && path[i]; i++) {
|
||||||
.override_by_default = true
|
if (path[i] == '\\') {
|
||||||
},
|
path[i] = '/';
|
||||||
.title_id = TitleId_AppletPhotoViewer,
|
}
|
||||||
.override_any_app = false
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/* Static buffer for loader.ini contents at runtime. */
|
inline const char *GetRelativePathStart(const char *relative_path) {
|
||||||
static char g_config_ini_data[0x800];
|
while (*relative_path == '/' || *relative_path == '\\') {
|
||||||
|
relative_path++;
|
||||||
|
}
|
||||||
|
return relative_path;
|
||||||
|
}
|
||||||
|
|
||||||
/* SetExternalContentSource extension */
|
void UpdateShouldOverrideCache(ncm::TitleId title_id) {
|
||||||
static std::map<u64, ContentManagement::ExternalContentSource> g_external_content_sources;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
Result ContentManagement::MountCode(u64 tid, FsStorageId sid) {
|
void InvalidateShouldOverrideCache() {
|
||||||
char path[FS_MAX_PATH] = {0};
|
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) == -1) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result MountHblFileSystem() {
|
||||||
|
char path[FS_MAX_PATH];
|
||||||
|
|
||||||
|
/* Print and fix path. */
|
||||||
|
std::snprintf(path, FS_MAX_PATH, "%s:/%s", SdCardStorageMountPoint, GetRelativePathStart(cfg::GetHblPath()));
|
||||||
|
FixFileSystemPath(path);
|
||||||
|
return MountNspFileSystem(HblFileSystemDeviceName, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result MountSdCardCodeFileSystem(ncm::TitleId title_id) {
|
||||||
|
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>(title_id));
|
||||||
|
FixFileSystemPath(path);
|
||||||
|
return MountNspFileSystem(CodeFileSystemDeviceName, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result 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();
|
||||||
|
}
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsMounted(const char *device_name) {
|
||||||
|
/* Allow nullptr device_name -- those are simply not openable. */
|
||||||
|
if (device_name == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH];
|
||||||
|
std::snprintf(path, FS_MAX_PATH, "%s:", device_name);
|
||||||
|
return FindDevice(device_name) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/* 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) {
|
ScopedCodeMount::~ScopedCodeMount() {
|
||||||
RefreshConfigurationData();
|
/* 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ShouldOverrideContentsWithSD(tid) && R_SUCCEEDED(MountCodeNspOnSd(tid))) {
|
Result MountCode(ScopedCodeMount &out, const ncm::TitleLocation &loc) {
|
||||||
|
ScopedCodeMount mount;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if we should override contents. */
|
||||||
|
if (ShouldOverrideWithHbl(loc.title_id)) {
|
||||||
|
/* Try to mount HBL. */
|
||||||
|
if (R_SUCCEEDED(MountHblFileSystem())) {
|
||||||
|
mount.SetHblMounted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ShouldOverrideWithSd(loc.title_id)) {
|
||||||
|
/* Try to mount Code NSP on SD. */
|
||||||
|
if (R_SUCCEEDED(MountSdCardCodeFileSystem(loc.title_id))) {
|
||||||
|
mount.SetCodeMounted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we haven't already mounted code, mount it. */
|
||||||
|
if (!mount.IsCodeMounted()) {
|
||||||
|
R_TRY(MountCodeFileSystem(loc));
|
||||||
|
mount.SetCodeMounted();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set out to scoped holder. */
|
||||||
|
out = std::move(mount);
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
R_TRY(ResolveContentPath(path, tid, sid));
|
Result OpenCodeFile(FILE *&out, ncm::TitleId title_id, const char *relative_path) {
|
||||||
|
FILE *f = nullptr;
|
||||||
|
const char *ecs_device_name = ecs::Get(title_id);
|
||||||
|
|
||||||
/* Fix up path. */
|
if (IsMounted(ecs_device_name)) {
|
||||||
for (unsigned int i = 0; i < FS_MAX_PATH && path[i] != '\x00'; i++) {
|
/* First priority: Open from external content. */
|
||||||
if (path[i] == '\\') {
|
f = OpenFile(ecs_device_name, relative_path);
|
||||||
path[i] = '/';
|
} else if (ShouldOverrideWithHbl(title_id)) {
|
||||||
|
/* Next, try to mount 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we fail, try the original exefs. */
|
||||||
|
if (f == nullptr) {
|
||||||
|
f = OpenBaseExefsFile(title_id, relative_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Always re-initialize fsp-ldr, in case it's closed */
|
/* If nothing worked, we failed to find the path. */
|
||||||
DoWithSmSession([&]() {
|
if (f == nullptr) {
|
||||||
R_ASSERT(fsldrInitialize());
|
return ResultFsPathNotFound;
|
||||||
});
|
|
||||||
ON_SCOPE_EXIT { fsldrExit(); };
|
|
||||||
|
|
||||||
R_TRY(fsldrOpenCodeFileSystem(tid, path, &g_CodeFileSystem));
|
|
||||||
|
|
||||||
fsdevMountDevice("code", g_CodeFileSystem);
|
|
||||||
TryMountHblNspOnSd();
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result ContentManagement::UnmountCode() {
|
|
||||||
if (g_mounted_hbl_nsp) {
|
|
||||||
fsdevUnmountDevice("hbl");
|
|
||||||
g_mounted_hbl_nsp = false;
|
|
||||||
}
|
|
||||||
fsdevUnmountDevice("code");
|
|
||||||
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] = '/';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out = f;
|
||||||
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
if (g_has_initialized_fs_dev && !g_mounted_hbl_nsp && R_SUCCEEDED(fsOpenFileSystemWithId(&g_HblFileSystem, 0, FsFileSystemType_ApplicationPackage, path))) {
|
|
||||||
fsdevMountDevice("hbl", g_HblFileSystem);
|
Result OpenCodeFileFromBaseExefs(FILE *&out, ncm::TitleId title_id, const char *relative_path) {
|
||||||
g_mounted_hbl_nsp = true;
|
/* Open the file. */
|
||||||
|
FILE *f = OpenBaseExefsFile(title_id, relative_path);
|
||||||
|
if (f == nullptr) {
|
||||||
|
return ResultFsPathNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = f;
|
||||||
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Result ContentManagement::MountCodeNspOnSd(u64 tid) {
|
/* Redirection API. */
|
||||||
char path[FS_MAX_PATH+1] = {0};
|
Result ResolveContentPath(char *out_path, const ncm::TitleLocation &loc) {
|
||||||
snprintf(path, FS_MAX_PATH, "@Sdcard:/atmosphere/titles/%016lx/exefs.nsp", tid);
|
char path[FS_MAX_PATH];
|
||||||
|
|
||||||
R_TRY(fsOpenFileSystemWithId(&g_CodeFileSystem, 0, FsFileSystemType_ApplicationPackage, path));
|
/* Try to get the path from the registered resolver. */
|
||||||
fsdevMountDevice("code", g_CodeFileSystem);
|
LrRegisteredLocationResolver reg;
|
||||||
TryMountHblNspOnSd();
|
R_TRY(lrOpenRegisteredLocationResolver(®));
|
||||||
|
ON_SCOPE_EXIT { serviceClose(®.s); };
|
||||||
|
|
||||||
return ResultSuccess;
|
R_TRY_CATCH(lrRegLrResolveProgramPath(®, 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(static_cast<FsStorageId>(loc.storage_id), &lr));
|
||||||
|
ON_SCOPE_EXIT { serviceClose(&lr.s); };
|
||||||
|
|
||||||
Result ContentManagement::MountCodeForTidSid(Registration::TidSid *tid_sid) {
|
R_TRY(lrLrResolveProgramPath(&lr, static_cast<u64>(loc.title_id), path));
|
||||||
return MountCode(tid_sid->title_id, tid_sid->storage_id);
|
}
|
||||||
}
|
} R_END_TRY_CATCH;
|
||||||
|
|
||||||
Result ContentManagement::ResolveContentPath(char *out_path, u64 tid, FsStorageId sid) {
|
std::strncpy(out_path, path, FS_MAX_PATH);
|
||||||
char path[FS_MAX_PATH] = {0};
|
out_path[FS_MAX_PATH - 1] = '\0';
|
||||||
|
FixFileSystemPath(out_path);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
/* Try to get the path from the registered resolver. */
|
Result RedirectContentPath(const char *path, const ncm::TitleLocation &loc) {
|
||||||
LrRegisteredLocationResolver reg;
|
LrLocationResolver lr;
|
||||||
R_TRY(lrOpenRegisteredLocationResolver(®));
|
R_TRY(lrOpenLocationResolver(static_cast<FsStorageId>(loc.storage_id), &lr));
|
||||||
ON_SCOPE_EXIT { serviceClose(®.s); };
|
ON_SCOPE_EXIT { serviceClose(&lr.s); };
|
||||||
|
|
||||||
R_TRY_CATCH(lrRegLrResolveProgramPath(®, tid, path)) {
|
return lrLrRedirectProgramPath(&lr, 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));
|
|
||||||
ON_SCOPE_EXIT { serviceClose(&lr.s); };
|
|
||||||
|
|
||||||
R_TRY(lrLrResolveProgramPath(&lr, tid, path));
|
Result RedirectHtmlDocumentPathForHbl(const ncm::TitleLocation &loc) {
|
||||||
|
char path[FS_MAX_PATH];
|
||||||
|
|
||||||
strncpy(out_path, path, FS_MAX_PATH);
|
/* Open a locaiton resolver. */
|
||||||
|
LrLocationResolver lr;
|
||||||
|
R_TRY(lrOpenLocationResolver(static_cast<FsStorageId>(loc.storage_id), &lr));
|
||||||
|
ON_SCOPE_EXIT { serviceClose(&lr.s); };
|
||||||
|
|
||||||
|
/* 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;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
} R_END_TRY_CATCH;
|
|
||||||
|
|
||||||
strncpy(out_path, path, FS_MAX_PATH);
|
/* We just need to set this to any valid NCA path. Let's use the executable path. */
|
||||||
return ResultSuccess;
|
R_TRY(lrLrResolveProgramPath(&lr, static_cast<u64>(loc.title_id), path));
|
||||||
}
|
R_TRY(lrLrRedirectApplicationHtmlDocumentPath(&lr, static_cast<u64>(loc.title_id), path));
|
||||||
|
|
||||||
Result ContentManagement::ResolveContentPathForTidSid(char *out_path, Registration::TidSid *tid_sid) {
|
return ResultSuccess;
|
||||||
return ResolveContentPath(out_path, tid_sid->title_id, tid_sid->storage_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result ContentManagement::RedirectContentPath(const char *path, u64 tid, FsStorageId sid) {
|
|
||||||
LrLocationResolver lr;
|
|
||||||
R_TRY(lrOpenLocationResolver(sid, &lr));
|
|
||||||
ON_SCOPE_EXIT { serviceClose(&lr.s); };
|
|
||||||
|
|
||||||
return lrLrRedirectProgramPath(&lr, tid, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result ContentManagement::RedirectContentPathForTidSid(const char *path, Registration::TidSid *tid_sid) {
|
|
||||||
return RedirectContentPath(path, tid_sid->title_id, tid_sid->storage_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContentManagement::RedirectHtmlDocumentPathForHbl(u64 tid, FsStorageId sid) {
|
|
||||||
LrLocationResolver lr;
|
|
||||||
char path[FS_MAX_PATH] = {0};
|
|
||||||
|
|
||||||
/* Open resolver. */
|
|
||||||
if (R_FAILED(lrOpenLocationResolver(sid, &lr))) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure close on exit. */
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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));
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,55 +16,64 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include <stratosphere/ldr.hpp>
|
||||||
|
|
||||||
#include "ldr_registration.hpp"
|
namespace sts::ldr {
|
||||||
|
|
||||||
struct OverrideKey {
|
/* Utility reference to make code mounting automatic. */
|
||||||
u64 key_combination;
|
class ScopedCodeMount {
|
||||||
bool override_by_default;
|
NON_COPYABLE(ScopedCodeMount);
|
||||||
};
|
private:
|
||||||
|
bool is_code_mounted;
|
||||||
|
bool is_hbl_mounted;
|
||||||
|
public:
|
||||||
|
ScopedCodeMount() : is_code_mounted(false), is_hbl_mounted(false) { /* ... */ }
|
||||||
|
ScopedCodeMount(bool c, bool h) : is_code_mounted(c), is_hbl_mounted(h) { /* ... */ }
|
||||||
|
~ScopedCodeMount();
|
||||||
|
|
||||||
class ContentManagement {
|
ScopedCodeMount(ScopedCodeMount&& rhs) {
|
||||||
public:
|
this->is_code_mounted = rhs.is_code_mounted;
|
||||||
static Result MountCode(u64 tid, FsStorageId sid);
|
this->is_hbl_mounted = rhs.is_hbl_mounted;
|
||||||
static Result MountCodeNspOnSd(u64 tid);
|
rhs.is_code_mounted = false;
|
||||||
static void TryMountHblNspOnSd();
|
rhs.is_hbl_mounted = false;
|
||||||
static Result UnmountCode();
|
}
|
||||||
static Result MountCodeForTidSid(Registration::TidSid *tid_sid);
|
|
||||||
|
|
||||||
static Result ResolveContentPath(char *out_path, u64 tid, FsStorageId sid);
|
ScopedCodeMount& operator=(ScopedCodeMount&& rhs) {
|
||||||
static Result RedirectContentPath(const char *path, u64 tid, FsStorageId sid);
|
rhs.Swap(*this);
|
||||||
static Result ResolveContentPathForTidSid(char *out_path, Registration::TidSid *tid_sid);
|
return *this;
|
||||||
static Result RedirectContentPathForTidSid(const char *path, Registration::TidSid *tid_sid);
|
}
|
||||||
|
|
||||||
static void RedirectHtmlDocumentPathForHbl(u64 tid, FsStorageId sid);
|
void Swap(ScopedCodeMount& rhs) {
|
||||||
|
std::swap(this->is_code_mounted, rhs.is_code_mounted);
|
||||||
|
std::swap(this->is_hbl_mounted, rhs.is_hbl_mounted);
|
||||||
|
}
|
||||||
|
|
||||||
static bool HasCreatedTitle(u64 tid);
|
void SetCodeMounted() {
|
||||||
static void SetCreatedTitle(u64 tid);
|
this->is_code_mounted = true;
|
||||||
static void RefreshConfigurationData();
|
}
|
||||||
static void TryMountSdCard();
|
|
||||||
|
|
||||||
static OverrideKey GetTitleOverrideKey(u64 tid);
|
void SetHblMounted() {
|
||||||
static bool ShouldOverrideContentsWithSD(u64 tid);
|
this->is_hbl_mounted = true;
|
||||||
static bool ShouldOverrideContentsWithHBL(u64 tid);
|
}
|
||||||
|
|
||||||
/* SetExternalContentSource extension */
|
bool IsCodeMounted() const {
|
||||||
class ExternalContentSource {
|
return this->is_code_mounted;
|
||||||
public:
|
}
|
||||||
static void GenerateMountpointName(u64 tid, char *out, size_t max_length);
|
|
||||||
|
|
||||||
ExternalContentSource(u64 tid, const char *mountpoint);
|
bool IsHblMounted() const {
|
||||||
~ExternalContentSource();
|
return this->is_hbl_mounted;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
ExternalContentSource(const ExternalContentSource &other) = delete;
|
/* Content Management API. */
|
||||||
ExternalContentSource(ExternalContentSource &&other) = delete;
|
Result MountCode(ScopedCodeMount &out, const ncm::TitleLocation &loc);
|
||||||
ExternalContentSource &operator=(const ExternalContentSource &other) = delete;
|
Result OpenCodeFile(FILE *&out, ncm::TitleId title_id, const char *relative_path);
|
||||||
ExternalContentSource &operator=(ExternalContentSource &&other) = delete;
|
Result OpenCodeFileFromBaseExefs(FILE *&out, ncm::TitleId title_id, const char *relative_path);
|
||||||
|
|
||||||
const u64 tid;
|
/* Redirection API. */
|
||||||
char mountpoint[32];
|
Result ResolveContentPath(char *out_path, const ncm::TitleLocation &loc);
|
||||||
};
|
Result RedirectContentPath(const char *path, const ncm::TitleLocation &loc);
|
||||||
static ExternalContentSource *GetExternalContentSource(u64 tid); /* returns nullptr if no ECS is set */
|
Result RedirectHtmlDocumentPathForHbl(const ncm::TitleLocation &loc);
|
||||||
static Result SetExternalContentSource(u64 tid, FsFileSystem filesystem); /* takes ownership of filesystem */
|
|
||||||
static void ClearExternalContentSource(u64 tid);
|
}
|
||||||
};
|
|
||||||
|
|
|
@ -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());
|
|
||||||
}
|
|
|
@ -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>(),
|
|
||||||
};
|
|
||||||
};
|
|
110
stratosphere/loader/source/ldr_ecs.cpp
Normal file
110
stratosphere/loader/source/ldr_ecs.cpp
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
{
|
||||||
|
Handle s_h, c_h;
|
||||||
|
R_TRY(svcCreateSession(&s_h, &c_h, 0, 0));
|
||||||
|
server.Reset(s_h);
|
||||||
|
client.Reset(c_h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
|
auto it = g_map.find(static_cast<u64>(title_id));
|
||||||
|
if (it != g_map.end()) {
|
||||||
|
g_map.erase(it);
|
||||||
|
}
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
stratosphere/loader/source/ldr_ecs.hpp
Normal file
29
stratosphere/loader/source/ldr_ecs.hpp
Normal 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);
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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];
|
|
||||||
}
|
|
51
stratosphere/loader/source/ldr_launch_record.cpp
Normal file
51
stratosphere/loader/source/ldr_launch_record.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,8 +16,13 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include <stratosphere/ldr.hpp>
|
||||||
|
|
||||||
class HidManagement {
|
namespace sts::ldr {
|
||||||
public:
|
|
||||||
static Result GetKeysHeld(u64 *keys);
|
/* Launch Record API. */
|
||||||
};
|
bool HasLaunchedTitle(ncm::TitleId title_id);
|
||||||
|
void SetLaunchedTitle(ncm::TitleId title_id);
|
||||||
|
|
||||||
|
}
|
104
stratosphere/loader/source/ldr_loader_service.cpp
Normal file
104
stratosphere/loader/source/ldr_loader_service.cpp
Normal 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<int>::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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
116
stratosphere/loader/source/ldr_loader_service.hpp
Normal file
116
stratosphere/loader/source/ldr_loader_service.hpp
Normal 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>(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,10 +22,10 @@
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <atmosphere.h>
|
#include <atmosphere.h>
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
|
#include <stratosphere/ncm.hpp>
|
||||||
|
#include <stratosphere/ldr.hpp>
|
||||||
|
|
||||||
#include "ldr_process_manager.hpp"
|
#include "ldr_loader_service.hpp"
|
||||||
#include "ldr_debug_monitor.hpp"
|
|
||||||
#include "ldr_shell.hpp"
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
extern u32 __start__;
|
extern u32 __start__;
|
||||||
|
@ -68,14 +68,13 @@ void __libnx_initheap(void) {
|
||||||
void __appInit(void) {
|
void __appInit(void) {
|
||||||
SetFirmwareVersionForLibnx();
|
SetFirmwareVersionForLibnx();
|
||||||
|
|
||||||
/* Initialize services we need (TODO: SPL) */
|
/* Initialize services we need. */
|
||||||
DoWithSmSession([&]() {
|
DoWithSmSession([&]() {
|
||||||
R_ASSERT(fsInitialize());
|
R_ASSERT(fsInitialize());
|
||||||
R_ASSERT(lrInitialize());
|
R_ASSERT(lrInitialize());
|
||||||
R_ASSERT(fsldrInitialize());
|
R_ASSERT(fsldrInitialize());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION);
|
CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,14 +94,12 @@ struct LoaderServerOptions {
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
consoleDebugInit(debugDevice_SVC);
|
|
||||||
|
|
||||||
static auto s_server_manager = WaitableManager<LoaderServerOptions>(1);
|
static auto s_server_manager = WaitableManager<LoaderServerOptions>(1);
|
||||||
|
|
||||||
/* Add services to manager. */
|
/* Add services to manager. */
|
||||||
s_server_manager.AddWaitable(new ServiceServer<ProcessManagerService>("ldr:pm", 1));
|
s_server_manager.AddWaitable(new ServiceServer<sts::ldr::pm::ProcessManagerInterface>("ldr:pm", 1));
|
||||||
s_server_manager.AddWaitable(new ServiceServer<ShellService>("ldr:shel", 3));
|
s_server_manager.AddWaitable(new ServiceServer<sts::ldr::shell::ShellInterface>("ldr:shel", 3));
|
||||||
s_server_manager.AddWaitable(new ServiceServer<DebugMonitorService>("ldr:dmnt", 2));
|
s_server_manager.AddWaitable(new ServiceServer<sts::ldr::dmnt::DebugMonitorInterface>("ldr:dmnt", 2));
|
||||||
|
|
||||||
/* Loop forever, servicing our services. */
|
/* Loop forever, servicing our services. */
|
||||||
s_server_manager.Process();
|
s_server_manager.Process();
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
217
stratosphere/loader/source/ldr_meta.cpp
Normal file
217
stratosphere/loader/source/ldr_meta.cpp
Normal 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 = GetProgramInfoFlags(o_meta->aci_kac, o_meta->aci->kac_size);
|
||||||
|
SetProgramInfoFlags(program_info_flags, meta->acid_kac, meta->acid->kac_size);
|
||||||
|
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 = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
43
stratosphere/loader/source/ldr_meta.hpp
Normal file
43
stratosphere/loader/source/ldr_meta.hpp
Normal 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();
|
||||||
|
|
||||||
|
}
|
|
@ -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 = {};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
};
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
};
|
|
|
@ -14,174 +14,30 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <dirent.h>
|
#include <stratosphere/patcher.hpp>
|
||||||
#include <ctype.h>
|
#include <stratosphere/ro.hpp>
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include "ldr_patcher.hpp"
|
#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"
|
namespace {
|
||||||
#define IPS_TAIL "EOF"
|
|
||||||
|
|
||||||
#define IPS32_MAGIC "IPS32"
|
constexpr const char *NsoPatchesDirectory = "exefs_patches";
|
||||||
#define IPS32_TAIL "EEOF"
|
|
||||||
|
|
||||||
static inline u8 HexNybbleToU8(const char nybble) {
|
/* Exefs patches want to prevent modification of header, */
|
||||||
if ('0' <= nybble && nybble <= '9') {
|
/* and also want to adjust offset relative to mapped location. */
|
||||||
return nybble - '0';
|
constexpr size_t NsoPatchesProtectedSize = sizeof(NsoHeader);
|
||||||
} else if ('a' <= nybble && nybble <= 'f') {
|
constexpr size_t NsoPatchesProtectedOffset = sizeof(NsoHeader);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read build id from name. */
|
/* Apply IPS patches. */
|
||||||
u8 build_id_from_name[0x20] = {0};
|
void LocateAndApplyIpsPatchesToModule(const u8 *build_id, uintptr_t mapped_nso, size_t mapped_size) {
|
||||||
for (unsigned int name_ofs = 0, id_ofs = 0; name_ofs < name_len - 4; id_ofs++) {
|
ro::ModuleId module_id;
|
||||||
build_id_from_name[id_ofs] |= HexNybbleToU8(name[name_ofs++]) << 4;
|
std::memcpy(&module_id.build_id, build_id, sizeof(module_id.build_id));
|
||||||
build_id_from_name[id_ofs] |= HexNybbleToU8(name[name_ofs++]);
|
sts::patcher::LocateAndApplyIpsPatchesToModule(NsoPatchesDirectory, NsoPatchesProtectedSize, NsoPatchesProtectedOffset, &module_id, reinterpret_cast<u8 *>(mapped_nso), mapped_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -16,11 +16,11 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <cstdio>
|
#include <stratosphere/ldr.hpp>
|
||||||
|
|
||||||
#include "ldr_nso.hpp"
|
namespace sts::ldr {
|
||||||
|
|
||||||
class PatchUtils {
|
/* Apply IPS patches. */
|
||||||
public:
|
void LocateAndApplyIpsPatchesToModule(const u8 *build_id, uintptr_t mapped_nso, size_t mapped_size);
|
||||||
static void ApplyPatches(const NsoUtils::NsoHeader *header, u8 *mapped_nso, size_t size);
|
|
||||||
};
|
}
|
|
@ -14,248 +14,698 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <switch.h>
|
#include <limits>
|
||||||
#include <algorithm>
|
#include <stratosphere/map.hpp>
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere/rnd.hpp>
|
||||||
|
#include <stratosphere/util.hpp>
|
||||||
|
|
||||||
#include "ldr_process_creation.hpp"
|
#include "ldr_capabilities.hpp"
|
||||||
#include "ldr_registration.hpp"
|
|
||||||
#include "ldr_launch_queue.hpp"
|
|
||||||
#include "ldr_content_management.hpp"
|
#include "ldr_content_management.hpp"
|
||||||
#include "ldr_npdm.hpp"
|
#include "ldr_launch_record.hpp"
|
||||||
#include "ldr_nso.hpp"
|
#include "ldr_meta.hpp"
|
||||||
|
#include "ldr_patcher.hpp"
|
||||||
|
#include "ldr_process_creation.hpp"
|
||||||
|
#include "ldr_ro_manager.hpp"
|
||||||
|
|
||||||
|
/* 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),
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static inline bool IsDisallowedVersion810(const u64 title_id, const u32 version) {
|
|
||||||
return version == 0 &&
|
|
||||||
(title_id == TitleId_Settings ||
|
|
||||||
title_id == TitleId_Bus ||
|
|
||||||
title_id == TitleId_Audio ||
|
|
||||||
title_id == TitleId_NvServices ||
|
|
||||||
title_id == TitleId_Ns ||
|
|
||||||
title_id == TitleId_Ssl ||
|
|
||||||
title_id == TitleId_Es ||
|
|
||||||
title_id == TitleId_Creport ||
|
|
||||||
title_id == TitleId_Ro);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ProcessCreation::ValidateProcessVersion(u64 title_id, u32 version) {
|
namespace sts::ldr {
|
||||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_810) {
|
|
||||||
return ResultSuccess;
|
namespace {
|
||||||
} else {
|
|
||||||
#ifdef LDR_VALIDATE_PROCESS_VERSION
|
/* Convenience defines. */
|
||||||
if (IsDisallowedVersion810(title_id, version)) {
|
constexpr size_t BaseAddressAlignment = 0x200000;
|
||||||
return ResultLoaderInvalidVersion;
|
constexpr size_t SystemResourceSizeAlignment = 0x200000;
|
||||||
} else {
|
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 = GetProgramInfoFlags(meta->acid_kac, meta->acid->kac_size);
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
return ResultSuccess;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result ProcessCreation::InitializeProcessInfo(NpdmUtils::NpdmInfo *npdm, Handle reslimit_h, u64 arg_flags, ProcessInfo *out_proc_info) {
|
bool IsApplet(const Meta *meta) {
|
||||||
/* Initialize a ProcessInfo using an npdm. */
|
return (GetProgramInfoFlags(meta->aci_kac, meta->aci->kac_size) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet;
|
||||||
*out_proc_info = {};
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* 3.0.0+ System Resource Size. */
|
bool IsApplication(const Meta *meta) {
|
||||||
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_300)) {
|
return (GetProgramInfoFlags(meta->aci_kac, meta->aci->kac_size) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
|
||||||
if (npdm->header->system_resource_size & 0x1FFFFF) {
|
|
||||||
return ResultLoaderInvalidSize;
|
|
||||||
}
|
}
|
||||||
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. */
|
Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) {
|
||||||
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500)) {
|
return static_cast<Npdm::AddressSpaceType>((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift);
|
||||||
u32 pool_partition_id = (npdm->acid->flags >> 2) & 0xF;
|
}
|
||||||
switch (pool_partition_id) {
|
|
||||||
case 0: /* Application. */
|
Acid::PoolPartition GetPoolPartition(const Meta *meta) {
|
||||||
if ((application_type & 3) == 2) {
|
return static_cast<Acid::PoolPartition>((meta->acid->flags & Acid::AcidFlag_PoolPartitionMask) >> Acid::AcidFlag_PoolPartitionShift);
|
||||||
out_proc_info->process_flags |= 0x80;
|
}
|
||||||
|
|
||||||
|
constexpr bool IsDisallowedVersion810(const u64 title_id, const u32 version) {
|
||||||
|
return version == 0 &&
|
||||||
|
(title_id == TitleId_Settings ||
|
||||||
|
title_id == TitleId_Bus ||
|
||||||
|
title_id == TitleId_Audio ||
|
||||||
|
title_id == TitleId_NvServices ||
|
||||||
|
title_id == TitleId_Ns ||
|
||||||
|
title_id == TitleId_Ssl ||
|
||||||
|
title_id == TitleId_Es ||
|
||||||
|
title_id == TitleId_Creport ||
|
||||||
|
title_id == TitleId_Ro);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ValidateTitleVersion(ncm::TitleId title_id, u32 version) {
|
||||||
|
if (GetRuntimeFirmwareVersion() < FirmwareVersion_810) {
|
||||||
|
return ResultSuccess;
|
||||||
|
} else {
|
||||||
|
#ifdef LDR_VALIDATE_PROCESS_VERSION
|
||||||
|
if (IsDisallowedVersion810(static_cast<u64>(title_id), version)) {
|
||||||
|
return ResultLoaderInvalidVersion;
|
||||||
|
} else {
|
||||||
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
break;
|
#else
|
||||||
case 1: /* Applet. */
|
return ResultSuccess;
|
||||||
out_proc_info->process_flags |= 0x80;
|
#endif
|
||||||
break;
|
|
||||||
case 2: /* Sysmodule. */
|
|
||||||
out_proc_info->process_flags |= 0x100;
|
|
||||||
break;
|
|
||||||
case 3: /* nvservices. */
|
|
||||||
out_proc_info->process_flags |= 0x180;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return ResultLoaderInvalidMeta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/* Get the process from the registration queue. */
|
|
||||||
target_process = Registration::GetProcess(index);
|
|
||||||
if (target_process == nullptr) {
|
|
||||||
return ResultLoaderProcessNotRegistered;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
} else {
|
|
||||||
if (R_SUCCEEDED(ContentManagement::MountCodeNspOnSd(target_process->tid_sid.title_id))) {
|
|
||||||
mounted_code = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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. */
|
Result LoadNsoHeaders(ncm::TitleId title_id, NsoHeader *nso_headers, bool *has_nso) {
|
||||||
R_TRY(NpdmUtils::LoadNpdmFromCache(target_process->tid_sid.title_id, &npdm_info));
|
/* Clear NSOs. */
|
||||||
|
std::memset(nso_headers, 0, sizeof(*nso_headers) * Nso_Count);
|
||||||
|
std::memset(has_nso, 0, sizeof(*has_nso) * Nso_Count);
|
||||||
|
|
||||||
/* Validate version. */
|
for (size_t i = 0; i < Nso_Count; i++) {
|
||||||
R_TRY(ValidateProcessVersion(target_process->tid_sid.title_id, npdm_info.header->version));
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Validate the title we're loading is what we expect. */
|
return ResultSuccess;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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));
|
|
||||||
|
|
||||||
/* Read in all NSO headers, see what NSOs are present. */
|
|
||||||
R_TRY(NsoUtils::LoadNsoHeaders(npdm_info.aci0->title_id));
|
|
||||||
|
|
||||||
/* Validate that the set of NSOs to be loaded is correct. */
|
|
||||||
R_TRY(NsoUtils::ValidateNsoLoadSet());
|
|
||||||
|
|
||||||
/* Initialize the ProcessInfo. */
|
|
||||||
R_TRY(ProcessCreation::InitializeProcessInfo(&npdm_info, reslimit_h, arg_flags, &process_info));
|
|
||||||
|
|
||||||
/* 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));
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
|
|
||||||
/* 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);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/* Load all NSOs into Process memory, and set permissions accordingly. */
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R_TRY(NsoUtils::LoadNsosIntoProcessMemory(process_h, npdm_info.aci0->title_id, &nso_extents, launch_args, launch_args_size));
|
Result ValidateNsoHeaders(const NsoHeader *nso_headers, const bool *has_nso) {
|
||||||
}
|
/* We must always have a main. */
|
||||||
|
if (!has_nso[Nso_Main]) {
|
||||||
|
return ResultLoaderInvalidNso;
|
||||||
|
}
|
||||||
|
|
||||||
/* Update the list of registered processes with the new process. */
|
/* If we don't have an RTLD, we must only have a main. */
|
||||||
svcGetProcessId(&process_id, process_h);
|
if (!has_nso[Nso_Rtld]) {
|
||||||
bool is_64_bit_addspace;
|
for (size_t i = Nso_Main + 1; i < Nso_Count; i++) {
|
||||||
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_200)) {
|
if (has_nso[i]) {
|
||||||
is_64_bit_addspace = (((npdm_info.header->mmu_flags >> 1) & 5) | 2) == 3;
|
return ResultLoaderInvalidNso;
|
||||||
} 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++) {
|
/* All NSOs must have zero text offset. */
|
||||||
if (NsoUtils::IsNsoPresent(i)) {
|
for (size_t i = 0; i < Nso_Count; i++) {
|
||||||
Registration::AddModuleInfo(index, nso_extents.nso_addresses[i], nso_extents.nso_sizes[i], NsoUtils::GetNsoBuildId(i));
|
if (nso_headers[i].text_dst_offset != 0) {
|
||||||
|
return ResultLoaderInvalidNso;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 capacilities. */
|
||||||
|
R_TRY(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 Npdm::AddressSpaceType_64BitDeprecated:
|
||||||
|
flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
|
||||||
|
break;
|
||||||
|
case Npdm::AddressSpaceType_32BitWithoutAlias:
|
||||||
|
flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
|
||||||
|
break;
|
||||||
|
case Npdm::AddressSpaceType_64Bit:
|
||||||
|
flags |= svc::CreateProcessFlag_AddressSpace64Bit;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
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 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));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 {
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (total_size > aslr_size) {
|
||||||
|
return ResultKernelOutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set out. */
|
||||||
|
aslr_start += aslr_slide;
|
||||||
|
for (size_t i = 0; i < Nso_Count; i++) {
|
||||||
|
if (has_nso[i]) {
|
||||||
|
out->nso_address[i] += aslr_start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (out->args_address) {
|
||||||
|
out->args_address += aslr_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_cpi->code_address = aslr_start;
|
||||||
|
out_cpi->code_num_pages = total_size >> 12;
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
/* Decide on an NSO layout. */
|
||||||
|
R_TRY(DecideAddressSpaceLayout(out, &cpi, nso_headers, has_nso, arg_info));
|
||||||
|
|
||||||
|
/* Actually create process. const_cast necessary because libnx doesn't declare svcCreateProcess with const u32*. */
|
||||||
|
return svcCreateProcess(out->process_handle.GetPointer(), &cpi, const_cast<u32 *>(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate size. */
|
||||||
|
if (file_size > segment->size || file_size > std::numeric_limits<int>::max() || segment->size > std::numeric_limits<int>::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. */
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send the pid/tid pair to anyone interested in man-in-the-middle-attacking it. */
|
/* Process Creation API. */
|
||||||
Registration::AssociatePidTidForMitM(index);
|
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);
|
||||||
|
|
||||||
/* If HBL, override HTML document path. */
|
{
|
||||||
if (ContentManagement::ShouldOverrideContentsWithHBL(target_process->tid_sid.title_id)) {
|
/* Mount code. */
|
||||||
ContentManagement::RedirectHtmlDocumentPathForHbl(target_process->tid_sid.title_id, target_process->tid_sid.storage_id);
|
ScopedCodeMount mount;
|
||||||
|
R_TRY(MountCode(mount, loc));
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
|
{
|
||||||
|
/* Nintendo doesn't validate this result, but we will. */
|
||||||
|
u64 process_id;
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Move the process handle to output. */
|
||||||
|
*out = info.process_handle.Move();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note that we've created the title. */
|
||||||
|
SetLaunchedTitle(loc.title_id);
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ECS is a one-shot operation, but we don't clear on failure. */
|
Result GetProgramInfo(ProgramInfo *out, const ncm::TitleLocation &loc) {
|
||||||
ContentManagement::ClearExternalContentSource(target_process->tid_sid.title_id);
|
Meta meta;
|
||||||
|
|
||||||
/* Cancel the process handle guard. */
|
/* Load Meta. */
|
||||||
proc_handle_guard.Cancel();
|
{
|
||||||
|
ScopedCodeMount mount;
|
||||||
|
R_TRY(MountCode(mount, loc));
|
||||||
|
R_TRY(LoadMeta(&meta, loc.title_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetProgramInfoFromMeta(out, &meta);
|
||||||
|
}
|
||||||
|
|
||||||
/* Write process handle to output. */
|
|
||||||
*out_process_h = process_h;
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,26 +16,15 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include <stratosphere/ldr.hpp>
|
||||||
|
|
||||||
#include "ldr_registration.hpp"
|
#include "ldr_arguments.hpp"
|
||||||
#include "ldr_launch_queue.hpp"
|
|
||||||
#include "ldr_npdm.hpp"
|
|
||||||
|
|
||||||
/* Utilities for Process Creation, for Loader. */
|
namespace sts::ldr {
|
||||||
|
|
||||||
class ProcessCreation {
|
/* Process Creation API. */
|
||||||
public:
|
Result CreateProcess(Handle *out, PinId pin_id, const ncm::TitleLocation &loc, const char *path, u32 flags, Handle reslimit_h);
|
||||||
struct ProcessInfo {
|
Result GetProgramInfo(ProgramInfo *out, const ncm::TitleLocation &loc);
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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>(),
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
};
|
|
172
stratosphere/loader/source/ldr_ro_manager.cpp
Normal file
172
stratosphere/loader/source/ldr_ro_manager.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
stratosphere/loader/source/ldr_ro_manager.hpp
Normal file
32
stratosphere/loader/source/ldr_ro_manager.hpp
Normal 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);
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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
|
@ -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
|
|
|
@ -29,7 +29,6 @@
|
||||||
#include "pm_registration.hpp"
|
#include "pm_registration.hpp"
|
||||||
#include "pm_boot_mode.hpp"
|
#include "pm_boot_mode.hpp"
|
||||||
|
|
||||||
static std::vector<u64> g_launched_titles;
|
|
||||||
|
|
||||||
static bool IsHexadecimal(const char *str) {
|
static bool IsHexadecimal(const char *str) {
|
||||||
while (*str) {
|
while (*str) {
|
||||||
|
@ -42,23 +41,11 @@ static bool IsHexadecimal(const char *str) {
|
||||||
return true;
|
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) {
|
static void LaunchTitle(u64 title_id, FsStorageId storage_id, u32 launch_flags, u64 *pid) {
|
||||||
u64 local_pid = 0;
|
u64 local_pid = 0;
|
||||||
|
|
||||||
/* Don't launch a title twice during boot2. */
|
/* Don't launch a title twice during boot2. */
|
||||||
if (HasLaunchedTitle(title_id)) {
|
if (Registration::HasLaunchedTitle(title_id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,8 +67,6 @@ static void LaunchTitle(u64 title_id, FsStorageId storage_id, u32 launch_flags,
|
||||||
if (pid) {
|
if (pid) {
|
||||||
*pid = local_pid;
|
*pid = local_pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetLaunchedTitle(title_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool GetGpioPadLow(GpioPadName pad) {
|
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. */
|
/* Wait until fs.mitm has installed itself. We want this to happen as early as possible. */
|
||||||
WaitForMitm("fsp-srv");
|
WaitForMitm("fsp-srv");
|
||||||
|
|
||||||
/* Clear titles. */
|
|
||||||
ClearLaunchedTitles();
|
|
||||||
|
|
||||||
/* psc, bus, pcv is the minimal set of required titles to get SD card. */
|
/* psc, bus, pcv is the minimal set of required titles to get SD card. */
|
||||||
/* bus depends on pcie, and pcv depends on settings. */
|
/* bus depends on pcie, and pcv depends on settings. */
|
||||||
/* Launch psc. */
|
/* Launch psc. */
|
||||||
|
@ -260,7 +242,7 @@ void EmbeddedBoot2::Main() {
|
||||||
while ((ent = readdir(titles_dir)) != NULL) {
|
while ((ent = readdir(titles_dir)) != NULL) {
|
||||||
if (strlen(ent->d_name) == 0x10 && IsHexadecimal(ent->d_name)) {
|
if (strlen(ent->d_name) == 0x10 && IsHexadecimal(ent->d_name)) {
|
||||||
u64 title_id = (u64)strtoul(ent->d_name, NULL, 16);
|
u64 title_id = (u64)strtoul(ent->d_name, NULL, 16);
|
||||||
if (HasLaunchedTitle(title_id)) {
|
if (Registration::HasLaunchedTitle(title_id)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
char title_path[FS_MAX_PATH] = {0};
|
char title_path[FS_MAX_PATH] = {0};
|
||||||
|
@ -290,7 +272,4 @@ void EmbeddedBoot2::Main() {
|
||||||
|
|
||||||
/* We no longer need the SD card. */
|
/* We no longer need the SD card. */
|
||||||
fsdevUnmountAll();
|
fsdevUnmountAll();
|
||||||
|
|
||||||
/* Free the memory used to track what boot2 launches. */
|
|
||||||
ClearLaunchedTitles();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
|
#include <stratosphere/ldr.hpp>
|
||||||
|
#include <stratosphere/ldr/ldr_pm_api.hpp>
|
||||||
|
|
||||||
#include "pm_registration.hpp"
|
#include "pm_registration.hpp"
|
||||||
#include "pm_info.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::scoped_lock<ProcessList &> lk(Registration::GetProcessList());
|
||||||
|
|
||||||
std::shared_ptr<Registration::Process> proc = Registration::GetProcess(pid);
|
std::shared_ptr<Registration::Process> proc = Registration::GetProcess(pid);
|
||||||
if (proc != NULL) {
|
if (proc == NULL) {
|
||||||
tid.SetValue(proc->tid_sid.title_id);
|
return ResultPmProcessNotFound;
|
||||||
return ResultSuccess;
|
|
||||||
}
|
}
|
||||||
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});
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,14 +20,23 @@
|
||||||
|
|
||||||
enum InformationCmd {
|
enum InformationCmd {
|
||||||
Information_Cmd_GetTitleId = 0,
|
Information_Cmd_GetTitleId = 0,
|
||||||
|
|
||||||
|
Information_Cmd_AtmosphereGetProcessId = 65000,
|
||||||
|
Information_Cmd_AtmosphereHasCreatedTitle = 65001,
|
||||||
};
|
};
|
||||||
|
|
||||||
class InformationService final : public IServiceObject {
|
class InformationService final : public IServiceObject {
|
||||||
private:
|
private:
|
||||||
/* Actual commands. */
|
/* Actual commands. */
|
||||||
Result GetTitleId(Out<u64> tid, u64 pid);
|
Result GetTitleId(Out<u64> tid, u64 pid);
|
||||||
|
|
||||||
|
/* Atmosphere commands. */
|
||||||
|
Result AtmosphereGetProcessId(Out<u64> pid, u64 tid);
|
||||||
|
Result AtmosphereHasLaunchedTitle(Out<bool> out, u64 tid);
|
||||||
public:
|
public:
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||||
MakeServiceCommandMeta<Information_Cmd_GetTitleId, &InformationService::GetTitleId>(),
|
MakeServiceCommandMeta<Information_Cmd_GetTitleId, &InformationService::GetTitleId>(),
|
||||||
|
MakeServiceCommandMeta<Information_Cmd_AtmosphereGetProcessId, &InformationService::AtmosphereGetProcessId>(),
|
||||||
|
MakeServiceCommandMeta<Information_Cmd_AtmosphereHasCreatedTitle, &InformationService::AtmosphereHasLaunchedTitle>(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,7 +36,7 @@ extern "C" {
|
||||||
|
|
||||||
u32 __nx_applet_type = AppletType_None;
|
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;
|
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
|
||||||
char nx_inner_heap[INNER_HEAP_SIZE];
|
char nx_inner_heap[INNER_HEAP_SIZE];
|
||||||
|
|
||||||
|
@ -100,12 +100,12 @@ void __appInit(void) {
|
||||||
|
|
||||||
DoWithSmSession([&]() {
|
DoWithSmSession([&]() {
|
||||||
R_ASSERT(fsprInitialize());
|
R_ASSERT(fsprInitialize());
|
||||||
R_ASSERT(smManagerInitialize());
|
|
||||||
|
|
||||||
/* This works around a bug with process permissions on < 4.0.0. */
|
/* This works around a bug with process permissions on < 4.0.0. */
|
||||||
RegisterPrivilegedProcessesWithFs();
|
RegisterPrivilegedProcessesWithFs();
|
||||||
|
|
||||||
/* Use AMS manager extension to tell SM that FS has been worked around. */
|
/* Use AMS manager extension to tell SM that FS has been worked around. */
|
||||||
|
R_ASSERT(smManagerInitialize());
|
||||||
R_ASSERT(sts::sm::manager::EndInitialDefers());
|
R_ASSERT(sts::sm::manager::EndInitialDefers());
|
||||||
|
|
||||||
R_ASSERT(lrInitialize());
|
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<ShellService>("pm:shell", 3));
|
||||||
s_server_manager.AddWaitable(new ServiceServer<DebugMonitorService>("pm:dmnt", 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<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. */
|
/* Loop forever, servicing our services. */
|
||||||
s_server_manager.Process();
|
s_server_manager.Process();
|
||||||
|
|
|
@ -13,9 +13,12 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
#include <atomic>
|
#include <stratosphere/ldr.hpp>
|
||||||
|
#include <stratosphere/ldr/ldr_pm_api.hpp>
|
||||||
|
|
||||||
#include "pm_registration.hpp"
|
#include "pm_registration.hpp"
|
||||||
#include "pm_resource_limits.hpp"
|
#include "pm_resource_limits.hpp"
|
||||||
|
@ -492,6 +495,12 @@ Handle Registration::GetBootFinishedEventHandle() {
|
||||||
return g_boot_finished_event->GetHandle();
|
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() {
|
void Registration::SignalBootFinished() {
|
||||||
g_boot_finished_event->Signal();
|
g_boot_finished_event->Signal();
|
||||||
}
|
}
|
||||||
|
|
|
@ -204,6 +204,8 @@ class Registration {
|
||||||
static Result LaunchProcess(u64 title_id, FsStorageId storage_id, u64 launch_flags, u64 *out_pid);
|
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 Result LaunchProcessByTidSid(TidSid tid_sid, u64 launch_flags, u64 *out_pid);
|
||||||
|
|
||||||
|
static bool HasLaunchedTitle(u64 title_id);
|
||||||
|
|
||||||
static void SignalBootFinished();
|
static void SignalBootFinished();
|
||||||
|
|
||||||
static bool HasApplicationProcess(std::shared_ptr<Process> *out = nullptr);
|
static bool HasApplicationProcess(std::shared_ptr<Process> *out = nullptr);
|
||||||
|
|
Loading…
Reference in a new issue