diff --git a/stratosphere/loader/source/ini.c b/stratosphere/loader/source/ini.c new file mode 100644 index 000000000..63626c72d --- /dev/null +++ b/stratosphere/loader/source/ini.c @@ -0,0 +1,269 @@ +/* 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 +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#include +#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); +} diff --git a/stratosphere/loader/source/ini.h b/stratosphere/loader/source/ini.h new file mode 100644 index 000000000..f45ba40ba --- /dev/null +++ b/stratosphere/loader/source/ini.h @@ -0,0 +1,130 @@ +/* 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 + +/* 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__ */ diff --git a/stratosphere/loader/source/ldr_content_management.cpp b/stratosphere/loader/source/ldr_content_management.cpp index c7ca985b0..c822d3281 100644 --- a/stratosphere/loader/source/ldr_content_management.cpp +++ b/stratosphere/loader/source/ldr_content_management.cpp @@ -14,19 +14,32 @@ * along with this program. If not, see . */ +#include #include -#include +#include #include #include #include "ldr_registration.hpp" #include "ldr_content_management.hpp" +#include "ldr_hid.hpp" + + +#include "ini.h" static FsFileSystem g_CodeFileSystem = {0}; +static FsFileSystem g_HblFileSystem = {0}; static std::vector g_created_titles; static bool g_has_initialized_fs_dev = false; +/* Default to Key R, hold disables override, HBL at atmosphere/hbl.nsp. */ +static bool g_mounted_hbl_nsp = false; +static char g_hbl_sd_path[FS_MAX_PATH+1] = "@Sdcard:/atmosphere/hbl.nsp\x00"; +static u64 g_override_key_combination = KEY_R; +static bool g_override_by_default = true; +static u64 g_override_hbl_tid = 0x010000000000100D; + Result ContentManagement::MountCode(u64 tid, FsStorageId sid) { char path[FS_MAX_PATH] = {0}; Result rc; @@ -36,10 +49,9 @@ Result ContentManagement::MountCode(u64 tid, FsStorageId sid) { TryMountSdCard(); } - if (kernelAbove200() && g_has_initialized_fs_dev && R_SUCCEEDED(MountCodeNspOnSd(tid))) { + if (ShouldOverrideContents() && R_SUCCEEDED(MountCodeNspOnSd(tid))) { return 0x0; } - if (R_FAILED(rc = ResolveContentPath(path, tid, sid))) { return rc; @@ -63,23 +75,44 @@ Result ContentManagement::MountCode(u64 tid, FsStorageId sid) { } fsdevMountDevice("code", g_CodeFileSystem); + TryMountHblNspOnSd(); fsldrExit(); return rc; } Result ContentManagement::UnmountCode() { + if (g_mounted_hbl_nsp) { + fsdevUnmountDevice("hbl"); + g_mounted_hbl_nsp = false; + } fsdevUnmountDevice("code"); return 0; } + +void ContentManagement::TryMountHblNspOnSd() { + char path[FS_MAX_PATH+1] = {0}; + strncpy(path, g_hbl_sd_path, FS_MAX_PATH); + for (unsigned int i = 0; i < FS_MAX_PATH && path[i] != '\x00'; i++) { + if (path[i] == '\\') { + path[i] = '/'; + } + } + if (g_has_initialized_fs_dev && !g_mounted_hbl_nsp && R_SUCCEEDED(fsOpenFileSystemWithId(&g_HblFileSystem, 0, FsFileSystemType_ApplicationPackage, path))) { + fsdevMountDevice("hbl", g_HblFileSystem); + g_mounted_hbl_nsp = true; + } +} + Result ContentManagement::MountCodeNspOnSd(u64 tid) { char path[FS_MAX_PATH+1] = {0}; snprintf(path, FS_MAX_PATH, "@Sdcard:/atmosphere/titles/%016lx/exefs.nsp", tid); Result rc = fsOpenFileSystemWithId(&g_CodeFileSystem, 0, FsFileSystemType_ApplicationPackage, path); - if (R_SUCCEEDED(rc)) { + if (R_SUCCEEDED(rc)) { fsdevMountDevice("code", g_CodeFileSystem); + TryMountHblNspOnSd(); } return rc; @@ -158,6 +191,94 @@ void ContentManagement::SetCreatedTitle(u64 tid) { } } +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, "config") == 0) { + if (strcasecmp(name, "hbl_tid") == 0) { + u64 override_tid = strtoul(value, NULL, 16); + if (override_tid != 0) { + g_override_hbl_tid = override_tid; + } + } else if (strcasecmp(name, "hbl_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) { + if (value[0] == '!') { + g_override_by_default = true; + value++; + } else { + g_override_by_default = false; + } + + if (strcasecmp(value, "A") == 0) { + g_override_key_combination = KEY_A; + } else if (strcasecmp(value, "B") == 0) { + g_override_key_combination = KEY_B; + } else if (strcasecmp(value, "X") == 0) { + g_override_key_combination = KEY_X; + } else if (strcasecmp(value, "Y") == 0) { + g_override_key_combination = KEY_Y; + } else if (strcasecmp(value, "LS") == 0) { + g_override_key_combination = KEY_LSTICK; + } else if (strcasecmp(value, "RS") == 0) { + g_override_key_combination = KEY_RSTICK; + } else if (strcasecmp(value, "L") == 0) { + g_override_key_combination = KEY_L; + } else if (strcasecmp(value, "R") == 0) { + g_override_key_combination = KEY_R; + } else if (strcasecmp(value, "ZL") == 0) { + g_override_key_combination = KEY_ZL; + } else if (strcasecmp(value, "ZR") == 0) { + g_override_key_combination = KEY_ZR; + } else if (strcasecmp(value, "PLUS") == 0) { + g_override_key_combination = KEY_PLUS; + } else if (strcasecmp(value, "MINUS") == 0) { + g_override_key_combination = KEY_MINUS; + } else if (strcasecmp(value, "DLEFT") == 0) { + g_override_key_combination = KEY_DLEFT; + } else if (strcasecmp(value, "DUP") == 0) { + g_override_key_combination = KEY_DUP; + } else if (strcasecmp(value, "DRIGHT") == 0) { + g_override_key_combination = KEY_DRIGHT; + } else if (strcasecmp(value, "DDOWN") == 0) { + g_override_key_combination = KEY_DDOWN; + } else if (strcasecmp(value, "SL") == 0) { + g_override_key_combination = KEY_SL; + } else if (strcasecmp(value, "SR") == 0) { + g_override_key_combination = KEY_SR; + } else { + g_override_key_combination = 0; + } + } + } else { + return 0; + } + return 1; +} + +void ContentManagement::LoadConfiguration(FILE *config) { + if (config == NULL) { + return; + } + + char *config_str = new char[0x800]; + if (config_str != NULL) { + /* Read in string. */ + std::fill(config_str, config_str + FS_MAX_PATH, 0); + fread(config_str, 1, 0x7FF, config); + config_str[strlen(config_str)] = 0x0; + + ini_parse_string(config_str, LoaderIniHandler, NULL); + + delete[] config_str; + } + + fclose(config); +} + void ContentManagement::TryMountSdCard() { /* Mount SD card, if psc, bus, and pcv have been created. */ if (!g_has_initialized_fs_dev && HasCreatedTitle(0x0100000000000021) && HasCreatedTitle(0x010000000000000A) && HasCreatedTitle(0x010000000000001A)) { @@ -172,7 +293,23 @@ void ContentManagement::TryMountSdCard() { } if (R_SUCCEEDED(fsdevMountSdmc())) { + ContentManagement::LoadConfiguration(fopen("sdmc:/atmosphere/loader.ini", "r")); g_has_initialized_fs_dev = true; } } } + +bool ContentManagement::ShouldReplaceWithHBL(u64 tid) { + return g_mounted_hbl_nsp && tid == g_override_hbl_tid; +} + +bool ContentManagement::ShouldOverrideContents() { + if (HasCreatedTitle(0x0100000000001000)) { + u64 kDown = 0; + bool keys_triggered = (R_SUCCEEDED(HidManagement::GetKeysDown(&kDown)) && ((kDown & g_override_key_combination) != 0)); + return g_has_initialized_fs_dev && (g_override_by_default ^ keys_triggered); + } else { + /* Always redirect before qlaunch. */ + return g_has_initialized_fs_dev; + } +} \ No newline at end of file diff --git a/stratosphere/loader/source/ldr_content_management.hpp b/stratosphere/loader/source/ldr_content_management.hpp index f99eb1fcf..c241092a4 100644 --- a/stratosphere/loader/source/ldr_content_management.hpp +++ b/stratosphere/loader/source/ldr_content_management.hpp @@ -23,6 +23,7 @@ class ContentManagement { public: static Result MountCode(u64 tid, FsStorageId sid); static Result MountCodeNspOnSd(u64 tid); + static void TryMountHblNspOnSd(); static Result UnmountCode(); static Result MountCodeForTidSid(Registration::TidSid *tid_sid); @@ -33,5 +34,9 @@ class ContentManagement { static bool HasCreatedTitle(u64 tid); static void SetCreatedTitle(u64 tid); + static void LoadConfiguration(FILE *config); static void TryMountSdCard(); + + static bool ShouldReplaceWithHBL(u64 tid); + static bool ShouldOverrideContents(); }; diff --git a/stratosphere/loader/source/ldr_hid.cpp b/stratosphere/loader/source/ldr_hid.cpp new file mode 100644 index 000000000..8bad9377b --- /dev/null +++ b/stratosphere/loader/source/ldr_hid.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include + +#include "ldr_content_management.hpp" +#include "ldr_hid.hpp" + +static bool g_has_opened_hid_session = false; + +Result HidManagement::GetKeysDown(u64 *keys) { + if (!ContentManagement::HasCreatedTitle(0x0100000000000013) || R_FAILED(hidInitialize())) { + return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID); + } + + hidScanInput(); + *keys = hidKeysDown(CONTROLLER_P1_AUTO); + + hidExit(); + return 0x0; +} \ No newline at end of file diff --git a/stratosphere/loader/source/ldr_hid.hpp b/stratosphere/loader/source/ldr_hid.hpp new file mode 100644 index 000000000..b5529fd20 --- /dev/null +++ b/stratosphere/loader/source/ldr_hid.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 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 . + */ + +#pragma once +#include + +class HidManagement { + public: + static Result GetKeysDown(u64 *keys); +}; diff --git a/stratosphere/loader/source/ldr_npdm.cpp b/stratosphere/loader/source/ldr_npdm.cpp index e455fc4b4..8e882bd21 100644 --- a/stratosphere/loader/source/ldr_npdm.cpp +++ b/stratosphere/loader/source/ldr_npdm.cpp @@ -19,8 +19,10 @@ #include #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) { @@ -31,6 +33,12 @@ Result NpdmUtils::LoadNpdmFromCache(u64 tid, NpdmInfo *out) { return 0; } +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"); @@ -45,20 +53,23 @@ FILE *NpdmUtils::OpenNpdmFromSdCard(u64 title_id) { FILE *NpdmUtils::OpenNpdm(u64 title_id) { - FILE *f_out = OpenNpdmFromSdCard(title_id); - if (f_out != NULL) { - return f_out; + if (ContentManagement::ShouldOverrideContents()) { + if (ContentManagement::ShouldReplaceWithHBL(title_id)) { + return OpenNpdmFromHBL(); + } + FILE *f_out = OpenNpdmFromSdCard(title_id); + if (f_out != NULL) { + return f_out; + } } return OpenNpdmFromExeFS(); } -Result NpdmUtils::LoadNpdm(u64 tid, NpdmInfo *out) { +Result NpdmUtils::LoadNpdmInternal(FILE *f_npdm, NpdmUtils::NpdmCache *cache) { Result rc; - g_npdm_cache.info = (const NpdmUtils::NpdmInfo){0}; - - FILE *f_npdm = OpenNpdm(tid); - + cache->info = (const NpdmUtils::NpdmInfo){0}; + rc = 0x202; if (f_npdm == NULL) { /* For generic "Couldn't open the file" error, just say the file doesn't exist. */ @@ -70,7 +81,7 @@ Result NpdmUtils::LoadNpdm(u64 tid, NpdmInfo *out) { fseek(f_npdm, 0, SEEK_SET); rc = 0x609; - if ((npdm_size > sizeof(g_npdm_cache.buffer)) || (fread(g_npdm_cache.buffer, 1, npdm_size, f_npdm) != npdm_size)) { + if ((npdm_size > sizeof(cache->buffer)) || (fread(cache->buffer, 1, npdm_size, f_npdm) != npdm_size)) { fclose(f_npdm); return rc; } @@ -83,8 +94,8 @@ Result NpdmUtils::LoadNpdm(u64 tid, NpdmInfo *out) { } /* For ease of access... */ - g_npdm_cache.info.header = (NpdmUtils::NpdmHeader *)(g_npdm_cache.buffer); - NpdmInfo *info = &g_npdm_cache.info; + cache->info.header = (NpdmUtils::NpdmHeader *)(cache->buffer); + NpdmInfo *info = &cache->info; if (info->header->magic != MAGIC_META) { return rc; @@ -98,7 +109,7 @@ Result NpdmUtils::LoadNpdm(u64 tid, NpdmInfo *out) { return rc; } - info->aci0 = (NpdmAci0 *)(g_npdm_cache.buffer + info->header->aci0_offset); + info->aci0 = (NpdmAci0 *)(cache->buffer + info->header->aci0_offset); if (info->aci0->magic != MAGIC_ACI0) { return rc; @@ -126,7 +137,7 @@ Result NpdmUtils::LoadNpdm(u64 tid, NpdmInfo *out) { return rc; } - info->acid = (NpdmAcid *)(g_npdm_cache.buffer + info->header->acid_offset); + info->acid = (NpdmAcid *)(cache->buffer + info->header->acid_offset); if (info->acid->magic != MAGIC_ACID) { return rc; @@ -152,7 +163,48 @@ Result NpdmUtils::LoadNpdm(u64 tid, NpdmInfo *out) { info->acid_kac = (void *)((uintptr_t)info->acid + info->acid->kac_offset); + rc = 0; + return rc; +} + +Result NpdmUtils::LoadNpdm(u64 tid, NpdmInfo *out) { + Result rc; + + rc = LoadNpdmInternal(OpenNpdm(tid), &g_npdm_cache); + if (R_FAILED(rc)) { + return rc; + } + + 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::ShouldOverrideContents() && ContentManagement::ShouldReplaceWithHBL(tid) + && R_SUCCEEDED(LoadNpdmInternal(OpenNpdmFromExeFS(), &g_original_npdm_cache))) { + NpdmInfo *original_info = &g_original_npdm_cache.info; + /* Fix pool partition. */ + if (kernelAbove500()) { + info->acid->flags = (info->acid->flags & 0xFFFFFFF9) | (original_info->acid->flags & 0x00000006); + } + /* Fix application type. */ + const u32 original_application_type = GetApplicationType((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; diff --git a/stratosphere/loader/source/ldr_npdm.hpp b/stratosphere/loader/source/ldr_npdm.hpp index 28fc465ef..3a7e721ee 100644 --- a/stratosphere/loader/source/ldr_npdm.hpp +++ b/stratosphere/loader/source/ldr_npdm.hpp @@ -50,7 +50,7 @@ class NpdmUtils { u32 magic; u32 size; u32 _0x208; - u32 is_retail; + u32 flags; u64 title_id_range_min; u64 title_id_range_max; u32 fac_offset; @@ -101,9 +101,12 @@ class NpdmUtils { static Result ValidateCapabilities(u32 *acid_caps, size_t num_acid_caps, u32 *aci0_caps, size_t num_aci0_caps); + 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); + private: + static Result LoadNpdmInternal(FILE *f_npdm, NpdmCache *cache); }; \ No newline at end of file diff --git a/stratosphere/loader/source/ldr_nso.cpp b/stratosphere/loader/source/ldr_nso.cpp index 893d662c3..451093e7c 100644 --- a/stratosphere/loader/source/ldr_nso.cpp +++ b/stratosphere/loader/source/ldr_nso.cpp @@ -24,12 +24,19 @@ #include "ldr_map.hpp" #include "ldr_random.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::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)); @@ -54,14 +61,18 @@ bool NsoUtils::CheckNsoStubbed(unsigned int index, u64 title_id) { } FILE *NsoUtils::OpenNso(unsigned int index, u64 title_id) { - FILE *f_out = OpenNsoFromSdCard(index, title_id); - if (f_out != NULL) { - return f_out; - } else if (CheckNsoStubbed(index, title_id)) { - return NULL; - } else { - return OpenNsoFromExeFS(index); + if (ContentManagement::ShouldOverrideContents()) { + if (ContentManagement::ShouldReplaceWithHBL(title_id)) { + return OpenNsoFromHBL(index); + } + FILE *f_out = OpenNsoFromSdCard(index, title_id); + if (f_out != NULL) { + return f_out; + } else if (CheckNsoStubbed(index, title_id)) { + return NULL; + } } + return OpenNsoFromExeFS(index); } bool NsoUtils::IsNsoPresent(unsigned int index) { diff --git a/stratosphere/loader/source/ldr_nso.hpp b/stratosphere/loader/source/ldr_nso.hpp index 94a18223a..364943346 100644 --- a/stratosphere/loader/source/ldr_nso.hpp +++ b/stratosphere/loader/source/ldr_nso.hpp @@ -97,6 +97,7 @@ class NsoUtils { } } + 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); diff --git a/stratosphere/loader/source/ldr_process_creation.cpp b/stratosphere/loader/source/ldr_process_creation.cpp index 3f3d38348..40d9d716d 100644 --- a/stratosphere/loader/source/ldr_process_creation.cpp +++ b/stratosphere/loader/source/ldr_process_creation.cpp @@ -24,15 +24,6 @@ #include "ldr_npdm.hpp" #include "ldr_nso.hpp" -extern "C" { - - bool __attribute__((weak)) kernelAbove600(void) { - u64 tmp; - return (svcGetInfo(&tmp, 21, INVALID_HANDLE, 0) != 0xF001); - } - -} - Result ProcessCreation::InitializeProcessInfo(NpdmUtils::NpdmInfo *npdm, Handle reslimit_h, u64 arg_flags, ProcessInfo *out_proc_info) { /* Initialize a ProcessInfo using an npdm. */ *out_proc_info = (const ProcessCreation::ProcessInfo){0}; @@ -87,7 +78,7 @@ Result ProcessCreation::InitializeProcessInfo(NpdmUtils::NpdmInfo *npdm, Handle /* 5.0.0+ Pool Partition. */ if (kernelAbove500()) { - u32 pool_partition_id = (npdm->acid->is_retail >> 2) & 0xF; + u32 pool_partition_id = (npdm->acid->flags >> 2) & 0xF; switch (pool_partition_id) { case 0: /* Application. */ if ((application_type & 3) == 2) {