From 18ee6eb2e6356bcd4ba6762cc438afa0df5a7adc Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Mon, 21 Jan 2019 01:09:06 -0800 Subject: [PATCH] set.mitm: Parse settings from SD card (closes #268) --- stratosphere/set_mitm/Makefile | 2 +- stratosphere/set_mitm/source/ini.c | 269 +++++++++++++++ stratosphere/set_mitm/source/ini.h | 130 ++++++++ stratosphere/set_mitm/source/setmitm_main.cpp | 6 +- .../set_mitm/source/setsys_mitm_service.cpp | 40 ++- .../set_mitm/source/setsys_settings_items.cpp | 305 ++++++++++++++++++ .../set_mitm/source/setsys_settings_items.hpp | 36 +++ stratosphere/set_mitm/source/setsys_shim.c | 50 ++- stratosphere/set_mitm/source/setsys_shim.h | 1 + 9 files changed, 834 insertions(+), 5 deletions(-) create mode 100644 stratosphere/set_mitm/source/ini.c create mode 100644 stratosphere/set_mitm/source/ini.h create mode 100644 stratosphere/set_mitm/source/setsys_settings_items.cpp create mode 100644 stratosphere/set_mitm/source/setsys_settings_items.hpp diff --git a/stratosphere/set_mitm/Makefile b/stratosphere/set_mitm/Makefile index 71c014dc5..2d3bf2862 100644 --- a/stratosphere/set_mitm/Makefile +++ b/stratosphere/set_mitm/Makefile @@ -31,7 +31,7 @@ DATA := data INCLUDES := include ../../common/include EXEFS_SRC := exefs_src -DEFINES := -DDISABLE_IPC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\" +DEFINES := -DDISABLE_IPC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\" -DINI_MAX_LINE=768 #--------------------------------------------------------------------------------- # options for code generation diff --git a/stratosphere/set_mitm/source/ini.c b/stratosphere/set_mitm/source/ini.c new file mode 100644 index 000000000..426430c9b --- /dev/null +++ b/stratosphere/set_mitm/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 72 +#define MAX_NAME 72 + +/* 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/set_mitm/source/ini.h b/stratosphere/set_mitm/source/ini.h new file mode 100644 index 000000000..f45ba40ba --- /dev/null +++ b/stratosphere/set_mitm/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/set_mitm/source/setmitm_main.cpp b/stratosphere/set_mitm/source/setmitm_main.cpp index bfba144e4..e2f821e64 100644 --- a/stratosphere/set_mitm/source/setmitm_main.cpp +++ b/stratosphere/set_mitm/source/setmitm_main.cpp @@ -24,13 +24,14 @@ #include #include "setsys_mitm_service.hpp" +#include "setsys_settings_items.hpp" extern "C" { extern u32 __start__; u32 __nx_applet_type = AppletType_None; - #define INNER_HEAP_SIZE 0x20000 + #define INNER_HEAP_SIZE 0x40000 size_t nx_inner_heap_size = INNER_HEAP_SIZE; char nx_inner_heap[INNER_HEAP_SIZE]; @@ -80,6 +81,9 @@ using SetMitmManager = WaitableManager; int main(int argc, char **argv) { consoleDebugInit(debugDevice_SVC); + + /* Load settings from SD card. */ + SettingsItemManager::RefreshConfiguration(); /* TODO: What's a good timeout value to use here? */ auto server_manager = new SetMitmManager(1); diff --git a/stratosphere/set_mitm/source/setsys_mitm_service.cpp b/stratosphere/set_mitm/source/setsys_mitm_service.cpp index 33db6bef0..e12e3d5a0 100644 --- a/stratosphere/set_mitm/source/setsys_mitm_service.cpp +++ b/stratosphere/set_mitm/source/setsys_mitm_service.cpp @@ -19,6 +19,7 @@ #include #include "setsys_mitm_service.hpp" #include "setsys_firmware_version.hpp" +#include "setsys_settings_items.hpp" void SetSysMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) { /* No commands need postprocessing. */ @@ -44,6 +45,16 @@ Result SetSysMitmService::GetSettingsItemValueSize(Out out_size, InPointer< char name[SET_MAX_NAME_SIZE] = {0}; char key[SET_MAX_NAME_SIZE] = {0}; + Result rc = SettingsItemManager::ValidateName(in_name.pointer); + if (R_FAILED(rc)) { + return rc; + } + + rc = SettingsItemManager::ValidateKey(in_key.pointer); + if (R_FAILED(rc)) { + return rc; + } + if (in_name.num_elements < SET_MAX_NAME_SIZE) { strncpy(name, in_name.pointer, in_name.num_elements); } else { @@ -55,14 +66,33 @@ Result SetSysMitmService::GetSettingsItemValueSize(Out out_size, InPointer< } else { strncpy(key, in_key.pointer, SET_MAX_NAME_SIZE-1); } + + rc = SettingsItemManager::GetValueSize(name, key, out_size.GetPointer()); + if (R_FAILED(rc)) { + rc = setsysGetSettingsItemValueSize(name, key, out_size.GetPointer()); + } - return setsysGetSettingsItemValueSize(name, key, out_size.GetPointer()); + return rc; } Result SetSysMitmService::GetSettingsItemValue(Out out_size, OutBuffer out_value, InPointer in_name, InPointer in_key) { char name[SET_MAX_NAME_SIZE] = {0}; char key[SET_MAX_NAME_SIZE] = {0}; + Result rc = SettingsItemManager::ValidateName(in_name.pointer); + if (R_FAILED(rc)) { + return rc; + } + + rc = SettingsItemManager::ValidateKey(in_key.pointer); + if (R_FAILED(rc)) { + return rc; + } + + if (out_value.buffer == nullptr) { + return 0x19A69; + } + if (in_name.num_elements < SET_MAX_NAME_SIZE) { strncpy(name, in_name.pointer, in_name.num_elements); } else { @@ -74,8 +104,14 @@ Result SetSysMitmService::GetSettingsItemValue(Out out_size, OutBuffer } else { strncpy(key, in_key.pointer, SET_MAX_NAME_SIZE-1); } + + rc = SettingsItemManager::GetValue(name, key, out_value.buffer, out_value.num_elements, out_size.GetPointer()); + + if (R_FAILED(rc)) { + rc = setsysGetSettingsItemValueFwd(this->forward_service.get(), name, key, out_value.buffer, out_value.num_elements, out_size.GetPointer()); + } - return setsysGetSettingsItemValue(name, key, out_value.buffer, out_value.num_elements); + return rc; } Result SetSysMitmService::GetEdid(OutPointerWithServerSize out) { diff --git a/stratosphere/set_mitm/source/setsys_settings_items.cpp b/stratosphere/set_mitm/source/setsys_settings_items.cpp new file mode 100644 index 000000000..9f56ceb4d --- /dev/null +++ b/stratosphere/set_mitm/source/setsys_settings_items.cpp @@ -0,0 +1,305 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include "setsys_settings_items.hpp" +#include "ini.h" + +struct SettingsItemValue { + size_t size; + u8 *data; +}; + +std::map g_settings_items; + +static bool g_threw_fatal = false; +static HosThread g_fatal_thread; + +static void FatalThreadFunc(void *arg) { + Result rc = (Result)((uintptr_t)arg); + fatalSimple(rc); +} + +static bool IsCorrectFormat(const char *str, size_t len) { + if (len > 0 && str[len - 1] == '.') { + return false; + } + + for (size_t i = 0; i < len; i++) { + const char c = *(str++); + + if ('a' <= c && c <= 'z') { + continue; + } + + if ('0' <= c && c <= '9') { + continue; + } + + if (c == '-' || c == '.' || c == '_') { + continue; + } + + return false; + } + + return true; +} + +Result SettingsItemManager::ValidateName(const char *name, size_t max_size) { + if (name == nullptr) { + return 0x19269; + } + + const size_t name_len = strnlen(name, std::min(max_size, MaxNameLength + 1)); + if (name_len == 0) { + return 0x1BA69; + } else if (name_len > MaxNameLength) { + return 0x1E269; + } + + if (!IsCorrectFormat(name, name_len)) { + return 0x20A69; + } + + return 0x0; +} + +Result SettingsItemManager::ValidateName(const char *name) { + return ValidateName(name, MaxNameLength + 1); +} + +Result SettingsItemManager::ValidateKey(const char *key, size_t max_size) { + if (key == nullptr) { + return 0x19469; + } + + const size_t key_len = strnlen(key, std::min(max_size, MaxKeyLength + 1)); + if (key_len == 0) { + return 0x1BC69; + } else if (key_len > MaxKeyLength) { + return 0x1E469; + } + + if (!IsCorrectFormat(key, key_len)) { + return 0x20C69; + } + + return 0x0; +} + +Result SettingsItemManager::ValidateKey(const char *key) { + return ValidateKey(key, MaxKeyLength + 1); +} + +static bool IsHexadecimal(const char *str) { + while (*str) { + if (isxdigit(*str)) { + str++; + } else { + return false; + } + } + return true; +} + +static char hextoi(char c) { + if ('a' <= c && c <= 'f') return c - 'a' + 0xA; + if ('A' <= c && c <= 'F') return c - 'A' + 0xA; + if ('0' <= c && c <= '9') return c - '0'; + return 0; +} + +static Result ParseValue(const char *name, const char *key, const char *val_tup) { + const char *delimiter = strchr(val_tup, '!'); + const char *value_str = delimiter + 1; + const char *type = val_tup; + + if (delimiter == NULL) { + return 0x20E69; + } + + while (isspace(*type) && type != delimiter) { + type++; + } + + size_t type_len = delimiter - type; + size_t value_len = strlen(value_str); + if (delimiter == NULL || value_len == 0 || type_len == 0) { + return 0x20E69; + } + + std::string kv = std::string(name) + "!" + std::string(key); + SettingsItemValue value; + + if (strncasecmp(type, "str", type_len) == 0 || strncasecmp(type, "string", type_len) == 0) { + /* String */ + value.size = value_len + 1; + value.data = reinterpret_cast(strdup(value_str)); + if (value.data == nullptr) { + return 0xCC69; + } + } else if (strncasecmp(type, "hex", type_len) == 0 || strncasecmp(type, "bytes", type_len) == 0) { + /* hex */ + if (value_len % 2 || !IsHexadecimal(value_str)) { + return 0x20E69; + } + value.size = value_len / 2; + u8 *data = reinterpret_cast(malloc(value.size)); + if (data == nullptr) { + return 0xCC69; + } + + memset(data, 0, value.size); + for (size_t i = 0; i < value_len; i++) { + data[i >> 1] |= hextoi(value_str[i]) << (4 * (i & 1)); + } + + value.data = data; + } else if (strncasecmp(type, "u8", type_len) == 0) { + /* u8 */ + value.size = sizeof(u8); + u8 *data = reinterpret_cast(malloc(value.size)); + if (data == nullptr) { + return 0xCC69; + } + *data = (u8)(strtoul(value_str, nullptr, 0)); + value.data = reinterpret_cast(data); + } else if (strncasecmp(type, "u16", type_len) == 0) { + /* u16 */ + value.size = sizeof(u16); + u16 *data = reinterpret_cast(malloc(value.size)); + if (data == nullptr) { + return 0xCC69; + } + *data = (u16)(strtoul(value_str, nullptr, 0)); + value.data = reinterpret_cast(data); + } else if (strncasecmp(type, "u32", type_len) == 0) { + /* u32 */ + value.size = sizeof(u32); + u32 *data = reinterpret_cast(malloc(value.size)); + if (data == nullptr) { + return 0xCC69; + } + *data = (u32)(strtoul(value_str, nullptr, 0)); + value.data = reinterpret_cast(data); + } else if (strncasecmp(type, "u64", type_len) == 0) { + /* u64 */ + value.size = sizeof(u64); + u64 *data = reinterpret_cast(malloc(value.size)); + if (data == nullptr) { + return 0xCC69; + } + *data = (u64)(strtoul(value_str, nullptr, 0)); + value.data = reinterpret_cast(data); + } else { + return 0x20E69; + } + + g_settings_items[kv] = value; + return 0x0; +} + +static int SettingsItemIniHandler(void *user, const char *name, const char *key, const char *value) { + Result rc = *(reinterpret_cast(user)); + ON_SCOPE_EXIT { *(reinterpret_cast(user)) = rc; }; + + if (R_SUCCEEDED(rc)) { + rc = SettingsItemManager::ValidateName(name); + } + if (R_SUCCEEDED(rc)) { + rc = SettingsItemManager::ValidateKey(name); + } + if (R_SUCCEEDED(rc)) { + rc = ParseValue(name, key, value); + } + + return R_SUCCEEDED(rc) ? 1 : 0; +} + +void SettingsItemManager::RefreshConfiguration() { + /* Mount the SD Card. */ + Result rc = fsInitialize(); + if (R_SUCCEEDED(rc)) { + rc = fsdevMountSdmc(); + } + if (R_FAILED(rc)) { + fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_FS)); + } + + /* When we're done, we should stop talking to FS. */ + ON_SCOPE_EXIT { + fsdevUnmountAll(); + fsExit(); + }; + + /* Open, parse config file. */ + { + FILE *config = fopen("sdmc:/atmosphere/system_settings.ini", "r"); + if (config == NULL) { + return; + } + ON_SCOPE_EXIT { fclose(config); }; + + Result rc = 0; + ini_parse_file(config, SettingsItemIniHandler, &rc); + + /* Report error if we encountered one. */ + if (R_FAILED(rc) && !g_threw_fatal) { + g_threw_fatal = true; + g_fatal_thread.Initialize(&FatalThreadFunc, reinterpret_cast(rc), 0x1000, 49); + g_fatal_thread.Start(); + } + } +} + +Result SettingsItemManager::GetValueSize(const char *name, const char *key, u64 *out_size) { + std::string kv = std::string(name) + "!" + std::string(key); + + auto it = g_settings_items.find(kv); + if (it == g_settings_items.end()) { + return 0x1669; + } + + *out_size = it->second.size; + return 0x0; +} + +Result SettingsItemManager::GetValue(const char *name, const char *key, void *out, size_t max_size, u64 *out_size) { + std::string kv = std::string(name) + "!" + std::string(key); + + auto it = g_settings_items.find(kv); + if (it == g_settings_items.end()) { + return 0x1669; + } + + size_t copy_size = it->second.size; + if (max_size < copy_size) { + copy_size = max_size; + } + *out_size = copy_size; + + memcpy(out, it->second.data, copy_size); + return 0x0; +} diff --git a/stratosphere/set_mitm/source/setsys_settings_items.hpp b/stratosphere/set_mitm/source/setsys_settings_items.hpp new file mode 100644 index 000000000..47abe11b2 --- /dev/null +++ b/stratosphere/set_mitm/source/setsys_settings_items.hpp @@ -0,0 +1,36 @@ +/* + * 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 +#include + + +class SettingsItemManager { + public: + static constexpr size_t MaxNameLength = 64; + static constexpr size_t MaxKeyLength = 64; + public: + static Result ValidateName(const char *name, size_t max_size); + static Result ValidateName(const char *name); + + static Result ValidateKey(const char *key, size_t max_size); + static Result ValidateKey(const char *key); + + static void RefreshConfiguration(); + static Result GetValueSize(const char *name, const char *key, u64 *out_size); + static Result GetValue(const char *name, const char *key, void *out, size_t max_size, u64 *out_size); +}; diff --git a/stratosphere/set_mitm/source/setsys_shim.c b/stratosphere/set_mitm/source/setsys_shim.c index 4edae7004..652649405 100644 --- a/stratosphere/set_mitm/source/setsys_shim.c +++ b/stratosphere/set_mitm/source/setsys_shim.c @@ -13,7 +13,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + +#include #include #include "setsys_shim.h" @@ -50,3 +51,50 @@ Result setsysGetEdidFwd(Service* s, SetSysEdid* out) { return rc; } + +Result setsysGetSettingsItemValueFwd(Service *s, const char *name, const char *item_key, void *value_out, size_t value_out_size, u64 *size_out) { + char send_name[SET_MAX_NAME_SIZE]; + char send_item_key[SET_MAX_NAME_SIZE]; + + memset(send_name, 0, SET_MAX_NAME_SIZE); + memset(send_item_key, 0, SET_MAX_NAME_SIZE); + strncpy(send_name, name, SET_MAX_NAME_SIZE-1); + strncpy(send_item_key, item_key, SET_MAX_NAME_SIZE-1); + + IpcCommand c; + ipcInitialize(&c); + ipcAddSendStatic(&c, send_name, SET_MAX_NAME_SIZE, 0); + ipcAddSendStatic(&c, send_item_key, SET_MAX_NAME_SIZE, 0); + ipcAddRecvBuffer(&c, value_out, value_out_size, 0); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 38; + + Result rc = serviceIpcDispatch(s); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + u64 size_out; + } *resp; + + serviceIpcParse(s, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + if (R_SUCCEEDED(rc)) { + *size_out = resp->size_out; + } + } + + return rc; +} diff --git a/stratosphere/set_mitm/source/setsys_shim.h b/stratosphere/set_mitm/source/setsys_shim.h index 6f475ea46..c2b5f73da 100644 --- a/stratosphere/set_mitm/source/setsys_shim.h +++ b/stratosphere/set_mitm/source/setsys_shim.h @@ -17,6 +17,7 @@ typedef struct { /* Command forwarders. */ Result setsysGetEdidFwd(Service* s, SetSysEdid* out); +Result setsysGetSettingsItemValueFwd(Service* s, const char *name, const char *item_key, void *value_out, size_t value_out_size, u64 *size_out); #ifdef __cplusplus }