From 37d35770283fff3568d01422983823338c38d047 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Mon, 4 Mar 2019 06:55:37 -0800 Subject: [PATCH] dmnt: allow disabling cheats via title-specific button combo --- common/defaults/loader.ini | 3 +- docs/modules/pm.md | 2 +- stratosphere/ams_mitm/source/utils.cpp | 2 +- .../dmnt/source/dmnt_cheat_manager.cpp | 86 +++++- .../dmnt/source/dmnt_cheat_manager.hpp | 1 + .../dmnt/source/dmnt_cheat_service.cpp | 10 + .../dmnt/source/dmnt_cheat_service.hpp | 3 + stratosphere/dmnt/source/dmnt_cheat_types.hpp | 1 + stratosphere/dmnt/source/dmnt_cheat_vm.cpp | 10 +- stratosphere/dmnt/source/dmnt_config.cpp | 157 ++++++++++ stratosphere/dmnt/source/dmnt_config.hpp | 31 ++ stratosphere/dmnt/source/dmnt_hid.cpp | 32 +++ stratosphere/dmnt/source/dmnt_hid.hpp | 23 ++ stratosphere/dmnt/source/dmnt_main.cpp | 4 + stratosphere/dmnt/source/ini.c | 269 ++++++++++++++++++ stratosphere/dmnt/source/ini.h | 130 +++++++++ stratosphere/dmnt/source/pm_shim.c | 12 +- stratosphere/dmnt/source/pm_shim.h | 2 +- .../loader/source/ldr_content_management.cpp | 4 +- stratosphere/pm/source/pm_debug_monitor.cpp | 5 +- stratosphere/pm/source/pm_debug_monitor.hpp | 6 +- 21 files changed, 775 insertions(+), 18 deletions(-) create mode 100644 stratosphere/dmnt/source/dmnt_config.cpp create mode 100644 stratosphere/dmnt/source/dmnt_config.hpp create mode 100644 stratosphere/dmnt/source/dmnt_hid.cpp create mode 100644 stratosphere/dmnt/source/dmnt_hid.hpp create mode 100644 stratosphere/dmnt/source/ini.c create mode 100644 stratosphere/dmnt/source/ini.h diff --git a/common/defaults/loader.ini b/common/defaults/loader.ini index a1df9025b..d488f21b4 100644 --- a/common/defaults/loader.ini +++ b/common/defaults/loader.ini @@ -4,4 +4,5 @@ path=atmosphere/hbl.nsp override_key=!R [default_config] -override_key=!L \ No newline at end of file +override_key=!L +cheat_enable_key=!L \ No newline at end of file diff --git a/docs/modules/pm.md b/docs/modules/pm.md index fe00ee18a..288fdf355 100644 --- a/docs/modules/pm.md +++ b/docs/modules/pm.md @@ -14,7 +14,7 @@ The SwIPC definition for this command follows. ``` interface nn::pm::detail::IDebugMonitorInterface is pm:dmnt { ... - [65000] AtmosphereGetProcessHandle(u64 pid) -> handle process_handle; + [65000] AtmosphereGetProcessInfo(u64 pid) -> handle process_handle, u64 title_id, u64 storage_id; } ``` diff --git a/stratosphere/ams_mitm/source/utils.cpp b/stratosphere/ams_mitm/source/utils.cpp index 916bf9bf1..fef0957e8 100644 --- a/stratosphere/ams_mitm/source/utils.cpp +++ b/stratosphere/ams_mitm/source/utils.cpp @@ -49,7 +49,7 @@ struct HblOverrideConfig { static HblOverrideConfig g_hbl_override_config = { .override_key = { - .key_combination = KEY_R, + .key_combination = KEY_L, .override_by_default = true }, .title_id = 0x010000000000100D, diff --git a/stratosphere/dmnt/source/dmnt_cheat_manager.cpp b/stratosphere/dmnt/source/dmnt_cheat_manager.cpp index 40be7a440..52556dbee 100644 --- a/stratosphere/dmnt/source/dmnt_cheat_manager.cpp +++ b/stratosphere/dmnt/source/dmnt_cheat_manager.cpp @@ -17,6 +17,7 @@ #include #include "dmnt_cheat_manager.hpp" #include "dmnt_cheat_vm.hpp" +#include "dmnt_config.hpp" #include "pm_shim.h" static HosMutex g_cheat_lock; @@ -206,6 +207,82 @@ static void StartDebugProcess(u64 pid) { } } +Result DmntCheatManager::ForceOpenCheatProcess() { + std::scoped_lock lk(g_cheat_lock); + Result rc; + + if (HasActiveCheatProcess()) { + return 0; + } + + /* Get the current application process ID. */ + if (R_FAILED((rc = pmdmntGetApplicationPid(&g_cheat_process_metadata.process_id)))) { + return rc; + } + ON_SCOPE_EXIT { if (R_FAILED(rc)) { g_cheat_process_metadata.process_id = 0; } }; + + /* Get process handle, use it to learn memory extents. */ + { + Handle proc_h = 0; + ON_SCOPE_EXIT { if (proc_h != 0) { svcCloseHandle(proc_h); } }; + + if (R_FAILED((rc = pmdmntAtmosphereGetProcessInfo(&proc_h, &g_cheat_process_metadata.title_id, nullptr, g_cheat_process_metadata.process_id)))) { + return rc; + } + + /* Get memory extents. */ + PopulateMemoryExtents(&g_cheat_process_metadata.heap_extents, proc_h, 4, 5); + PopulateMemoryExtents(&g_cheat_process_metadata.alias_extents, proc_h, 2, 3); + if (kernelAbove200()) { + PopulateMemoryExtents(&g_cheat_process_metadata.address_space_extents, proc_h, 12, 13); + } else { + g_cheat_process_metadata.address_space_extents.base = 0x08000000UL; + g_cheat_process_metadata.address_space_extents.size = 0x78000000UL; + } + } + + /* Get module information from Loader. */ + { + LoaderModuleInfo proc_modules[2]; + u32 num_modules; + if (R_FAILED((rc = ldrDmntGetModuleInfos(g_cheat_process_metadata.process_id, proc_modules, sizeof(proc_modules), &num_modules)))) { + return rc; + } + + /* All applications must have two modules. */ + /* However, this is a force-open, so we will accept one module. */ + /* Poor HBL, I guess... */ + LoaderModuleInfo *proc_module; + if (num_modules == 2) { + proc_module = &proc_modules[1]; + } else if (num_modules == 1) { + proc_module = &proc_modules[0]; + } else { + rc = ResultDmntCheatNotAttached; + return rc; + } + + g_cheat_process_metadata.main_nso_extents.base = proc_module->base_address; + g_cheat_process_metadata.main_nso_extents.size = proc_module->size; + memcpy(g_cheat_process_metadata.main_nso_build_id, proc_module->build_id, sizeof(g_cheat_process_metadata.main_nso_build_id)); + } + + /* TODO: Read cheats off the SD. */ + + /* Open a debug handle. */ + if (R_FAILED((rc = svcDebugActiveProcess(&g_cheat_process_debug_hnd, g_cheat_process_metadata.process_id)))) { + return rc; + } + + /* Continue debug events, etc. */ + ContinueCheatProcess(); + + /* Signal to our fans. */ + g_cheat_process_event->Signal(); + + return rc; +} + void DmntCheatManager::OnNewApplicationLaunch() { std::scoped_lock lk(g_cheat_lock); Result rc; @@ -223,7 +300,7 @@ void DmntCheatManager::OnNewApplicationLaunch() { Handle proc_h = 0; ON_SCOPE_EXIT { if (proc_h != 0) { svcCloseHandle(proc_h); } }; - if (R_FAILED((rc = pmdmntAtmosphereGetProcessHandle(&proc_h, g_cheat_process_metadata.process_id)))) { + if (R_FAILED((rc = pmdmntAtmosphereGetProcessInfo(&proc_h, &g_cheat_process_metadata.title_id, nullptr, g_cheat_process_metadata.process_id)))) { fatalSimple(rc); } @@ -238,6 +315,13 @@ void DmntCheatManager::OnNewApplicationLaunch() { } } + /* Check if we should skip based on keys down. */ + if (!DmntConfigManager::HasCheatEnableButton(g_cheat_process_metadata.title_id)) { + StartDebugProcess(g_cheat_process_metadata.process_id); + g_cheat_process_metadata.process_id = 0; + return; + } + /* Get module information from Loader. */ { LoaderModuleInfo proc_modules[2]; diff --git a/stratosphere/dmnt/source/dmnt_cheat_manager.hpp b/stratosphere/dmnt/source/dmnt_cheat_manager.hpp index c46a5f86b..47408b9b9 100644 --- a/stratosphere/dmnt/source/dmnt_cheat_manager.hpp +++ b/stratosphere/dmnt/source/dmnt_cheat_manager.hpp @@ -34,6 +34,7 @@ class DmntCheatManager { static bool GetHasActiveCheatProcess(); static Handle GetCheatProcessEventHandle(); static Result GetCheatProcessMetadata(CheatProcessMetadata *out); + static Result ForceOpenCheatProcess(); static Result ReadCheatProcessMemoryForVm(u64 proc_addr, void *out_data, size_t size); static Result WriteCheatProcessMemoryForVm(u64 proc_addr, const void *data, size_t size); diff --git a/stratosphere/dmnt/source/dmnt_cheat_service.cpp b/stratosphere/dmnt/source/dmnt_cheat_service.cpp index af41d8041..521a12c85 100644 --- a/stratosphere/dmnt/source/dmnt_cheat_service.cpp +++ b/stratosphere/dmnt/source/dmnt_cheat_service.cpp @@ -30,6 +30,16 @@ Result DmntCheatService::GetCheatProcessMetadata(Out out_m return DmntCheatManager::GetCheatProcessMetadata(out_metadata.GetPointer()); } +Result DmntCheatService::ForceOpenCheatProcess() { + Result rc = DmntCheatManager::ForceOpenCheatProcess(); + + if (R_FAILED(rc)) { + rc = ResultDmntCheatNotAttached; + } + + return rc; +} + Result DmntCheatService::GetCheatProcessMappingCount(Out out_count) { return DmntCheatManager::GetCheatProcessMappingCount(out_count.GetPointer()); diff --git a/stratosphere/dmnt/source/dmnt_cheat_service.hpp b/stratosphere/dmnt/source/dmnt_cheat_service.hpp index 6a95dc0e9..a921465a0 100644 --- a/stratosphere/dmnt/source/dmnt_cheat_service.hpp +++ b/stratosphere/dmnt/source/dmnt_cheat_service.hpp @@ -25,6 +25,7 @@ enum DmntCheatCmd { DmntCheat_Cmd_HasCheatProcess = 65000, DmntCheat_Cmd_GetCheatProcessEvent = 65001, DmntCheat_Cmd_GetCheatProcessMetadata = 65002, + DmntCheat_Cmd_ForceOpenCheatProcess = 65003, /* Interact with Memory */ DmntCheat_Cmd_GetCheatProcessMappingCount = 65100, @@ -52,6 +53,7 @@ class DmntCheatService final : public IServiceObject { void HasCheatProcess(Out out); void GetCheatProcessEvent(Out out_event); Result GetCheatProcessMetadata(Out out_metadata); + Result ForceOpenCheatProcess(); Result GetCheatProcessMappingCount(Out out_count); Result GetCheatProcessMappings(OutBuffer mappings, Out out_count, u64 offset); @@ -75,6 +77,7 @@ class DmntCheatService final : public IServiceObject { MakeServiceCommandMeta(), MakeServiceCommandMeta(), MakeServiceCommandMeta(), + MakeServiceCommandMeta(), MakeServiceCommandMeta(), MakeServiceCommandMeta(), diff --git a/stratosphere/dmnt/source/dmnt_cheat_types.hpp b/stratosphere/dmnt/source/dmnt_cheat_types.hpp index 73979badf..133baf425 100644 --- a/stratosphere/dmnt/source/dmnt_cheat_types.hpp +++ b/stratosphere/dmnt/source/dmnt_cheat_types.hpp @@ -27,6 +27,7 @@ struct MemoryRegionExtents { struct CheatProcessMetadata { u64 process_id; + u64 title_id; MemoryRegionExtents main_nso_extents; MemoryRegionExtents heap_extents; MemoryRegionExtents alias_extents; diff --git a/stratosphere/dmnt/source/dmnt_cheat_vm.cpp b/stratosphere/dmnt/source/dmnt_cheat_vm.cpp index 9f056f562..821ae75e4 100644 --- a/stratosphere/dmnt/source/dmnt_cheat_vm.cpp +++ b/stratosphere/dmnt/source/dmnt_cheat_vm.cpp @@ -18,6 +18,7 @@ #include "dmnt_cheat_types.hpp" #include "dmnt_cheat_vm.hpp" #include "dmnt_cheat_manager.hpp" +#include "dmnt_hid.hpp" void DmntCheatVm::OpenDebugLogFile() { @@ -356,15 +357,16 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { CheatVmOpcode cur_opcode; u64 kDown = 0; - /* TODO: Get Keys down. */ - + /* Get Keys down. */ + HidManagement::GetKeysDown(&kDown); this->OpenDebugLogFile(); ON_SCOPE_EXIT { this->CloseDebugLogFile(); }; this->LogToDebugFile("Started VM execution.\n"); - this->LogToDebugFile("Main NSO: %012lx\n", metadata->main_nso_extents.base); - this->LogToDebugFile("Heap: %012lx\n", metadata->main_nso_extents.base); + this->LogToDebugFile("Main NSO: %012lx\n", metadata->main_nso_extents.base); + this->LogToDebugFile("Heap: %012lx\n", metadata->main_nso_extents.base); + this->LogToDebugFile("Keys Down: %08x\n", (u32)(kDown & 0x0FFFFFFF)); /* Clear VM state. */ this->ResetState(); diff --git a/stratosphere/dmnt/source/dmnt_config.cpp b/stratosphere/dmnt/source/dmnt_config.cpp new file mode 100644 index 000000000..cd4026a50 --- /dev/null +++ b/stratosphere/dmnt/source/dmnt_config.cpp @@ -0,0 +1,157 @@ +/* + * 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 "dmnt_hid.hpp" +#include "dmnt_config.hpp" +#include "ini.h" + +/* Support variables. */ +static OverrideKey g_default_cheat_enable_key = { + .key_combination = KEY_L, + .override_by_default = true +}; + +/* Static buffer for loader.ini contents at runtime. */ +static char g_config_ini_data[0x800]; + +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 DmntIniHandler(void *user, const char *section, const char *name, const char *value) { + /* Taken and modified, with love, from Rajkosto's implementation. */ + if (strcasecmp(section, "default_config") == 0) { + if (strcasecmp(name, "cheat_enable_key") == 0) { + g_default_cheat_enable_key = ParseOverrideKey(value); + } + } else { + return 0; + } + return 1; +} + +static int DmntTitleSpecificIniHandler(void *user, const char *section, const char *name, const char *value) { + /* We'll output an override key when relevant. */ + OverrideKey *user_cfg = reinterpret_cast(user); + + if (strcasecmp(section, "override_config") == 0) { + if (strcasecmp(name, "cheat_enable_key") == 0) { + *user_cfg = ParseOverrideKey(value); + } + } else { + return 0; + } + return 1; +} + +void DmntConfigManager::RefreshConfiguration() { + FILE *config = fopen("sdmc:/atmosphere/loader.ini", "r"); + if (config == NULL) { + return; + } + + memset(g_config_ini_data, 0, sizeof(g_config_ini_data)); + fread(g_config_ini_data, 1, sizeof(g_config_ini_data) - 1, config); + fclose(config); + + ini_parse_string(g_config_ini_data, DmntIniHandler, NULL); +} + +OverrideKey DmntConfigManager::GetTitleCheatEnableKey(u64 tid) { + OverrideKey cfg = g_default_cheat_enable_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, DmntTitleSpecificIniHandler, &cfg); + } + + return cfg; +} + +static bool HasOverrideKey(OverrideKey *cfg) { + u64 kDown = 0; + bool keys_triggered = (R_SUCCEEDED(HidManagement::GetKeysDown(&kDown)) && ((kDown & cfg->key_combination) != 0)); + return (cfg->override_by_default ^ keys_triggered); +} + +bool DmntConfigManager::HasCheatEnableButton(u64 tid) { + /* Unconditionally refresh loader.ini contents. */ + RefreshConfiguration(); + + OverrideKey title_cfg = GetTitleCheatEnableKey(tid); + return HasOverrideKey(&title_cfg); +} diff --git a/stratosphere/dmnt/source/dmnt_config.hpp b/stratosphere/dmnt/source/dmnt_config.hpp new file mode 100644 index 000000000..b1ebed733 --- /dev/null +++ b/stratosphere/dmnt/source/dmnt_config.hpp @@ -0,0 +1,31 @@ +/* + * 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 + +struct OverrideKey { + u64 key_combination; + bool override_by_default; +}; + +class DmntConfigManager { + public: + static void RefreshConfiguration(); + + static OverrideKey GetTitleCheatEnableKey(u64 tid); + static bool HasCheatEnableButton(u64 tid); +}; diff --git a/stratosphere/dmnt/source/dmnt_hid.cpp b/stratosphere/dmnt/source/dmnt_hid.cpp new file mode 100644 index 000000000..46c9eee24 --- /dev/null +++ b/stratosphere/dmnt/source/dmnt_hid.cpp @@ -0,0 +1,32 @@ +/* + * 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 "dmnt_hid.hpp" + +Result HidManagement::GetKeysDown(u64 *keys) { + if (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/dmnt/source/dmnt_hid.hpp b/stratosphere/dmnt/source/dmnt_hid.hpp new file mode 100644 index 000000000..b5529fd20 --- /dev/null +++ b/stratosphere/dmnt/source/dmnt_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/dmnt/source/dmnt_main.cpp b/stratosphere/dmnt/source/dmnt_main.cpp index bea0dfffb..2eaf1dbb5 100644 --- a/stratosphere/dmnt/source/dmnt_main.cpp +++ b/stratosphere/dmnt/source/dmnt_main.cpp @@ -26,6 +26,7 @@ #include "dmnt_service.hpp" #include "dmnt_cheat_service.hpp" #include "dmnt_cheat_manager.hpp" +#include "dmnt_config.hpp" extern "C" { extern u32 __start__; @@ -128,6 +129,9 @@ int main(int argc, char **argv) { consoleDebugInit(debugDevice_SVC); + /* Initialize configuration manager. */ + DmntConfigManager::RefreshConfiguration(); + /* Start cheat manager. */ DmntCheatManager::InitializeCheatManager(); diff --git a/stratosphere/dmnt/source/ini.c b/stratosphere/dmnt/source/ini.c new file mode 100644 index 000000000..63626c72d --- /dev/null +++ b/stratosphere/dmnt/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/dmnt/source/ini.h b/stratosphere/dmnt/source/ini.h new file mode 100644 index 000000000..f45ba40ba --- /dev/null +++ b/stratosphere/dmnt/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/dmnt/source/pm_shim.c b/stratosphere/dmnt/source/pm_shim.c index 027265817..9b4b5ff87 100644 --- a/stratosphere/dmnt/source/pm_shim.c +++ b/stratosphere/dmnt/source/pm_shim.c @@ -18,7 +18,7 @@ #include "pm_shim.h" /* Atmosphere extension commands. */ -Result pmdmntAtmosphereGetProcessHandle(Handle* out, u64 pid) { +Result pmdmntAtmosphereGetProcessInfo(Handle* out, u64 *tid_out, FsStorageId *sid_out, u64 pid) { IpcCommand c; ipcInitialize(&c); Service *s = pmdmntGetServiceSession(); @@ -42,6 +42,8 @@ Result pmdmntAtmosphereGetProcessHandle(Handle* out, u64 pid) { struct { u64 magic; u64 result; + u64 title_id; + FsStorageId storage_id; } *resp; serviceIpcParse(s, &r, sizeof(*resp)); @@ -50,7 +52,13 @@ Result pmdmntAtmosphereGetProcessHandle(Handle* out, u64 pid) { rc = resp->result; if (R_SUCCEEDED(rc)) { - *out = r.Handles[0]; + if (out) { + *out = r.Handles[0]; + } else { + svcCloseHandle(r.Handles[0]); + } + if (tid_out) *tid_out = resp->title_id; + if (sid_out) *sid_out = resp->storage_id; } } diff --git a/stratosphere/dmnt/source/pm_shim.h b/stratosphere/dmnt/source/pm_shim.h index ae2ca9403..76d4e68b9 100644 --- a/stratosphere/dmnt/source/pm_shim.h +++ b/stratosphere/dmnt/source/pm_shim.h @@ -12,7 +12,7 @@ extern "C" { #endif /* Atmosphere extension commands. */ -Result pmdmntAtmosphereGetProcessHandle(Handle* out, u64 pid); +Result pmdmntAtmosphereGetProcessInfo(Handle* out, u64 *tid_out, FsStorageId *sid_out, u64 pid); #ifdef __cplusplus } diff --git a/stratosphere/loader/source/ldr_content_management.cpp b/stratosphere/loader/source/ldr_content_management.cpp index 346846714..4b121c948 100644 --- a/stratosphere/loader/source/ldr_content_management.cpp +++ b/stratosphere/loader/source/ldr_content_management.cpp @@ -40,7 +40,7 @@ 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 = { - .key_combination = KEY_R, + .key_combination = KEY_L, .override_by_default = true }; @@ -321,7 +321,7 @@ static int LoaderTitleSpecificIniHandler(void *user, const char *section, const return 1; } -void ContentManagement::RefreshConfigurationData() { +void ContentManagement::RefreshConfigurationData() { FILE *config = fopen("sdmc:/atmosphere/loader.ini", "r"); if (config == NULL) { return; diff --git a/stratosphere/pm/source/pm_debug_monitor.cpp b/stratosphere/pm/source/pm_debug_monitor.cpp index a9d4312d2..69f9eaab9 100644 --- a/stratosphere/pm/source/pm_debug_monitor.cpp +++ b/stratosphere/pm/source/pm_debug_monitor.cpp @@ -76,10 +76,11 @@ Result DebugMonitorService::DisableDebug(u32 which) { return Registration::DisableDebug(which); } -Result DebugMonitorService::AtmosphereGetProcessHandle(Out proc_hand, u64 pid) { +Result DebugMonitorService::AtmosphereGetProcessInfo(Out proc_hand, Out tid_sid, u64 pid) { auto proc = Registration::GetProcess(pid); - if(proc != nullptr) { + if (proc != nullptr) { proc_hand.SetValue(proc->handle); + tid_sid.SetValue(proc->tid_sid); return 0; } return 0x20F; diff --git a/stratosphere/pm/source/pm_debug_monitor.hpp b/stratosphere/pm/source/pm_debug_monitor.hpp index 88445bc60..22f6d02af 100644 --- a/stratosphere/pm/source/pm_debug_monitor.hpp +++ b/stratosphere/pm/source/pm_debug_monitor.hpp @@ -38,7 +38,7 @@ enum DmntCmd { Dmnt_Cmd_6X_DisableDebug = 6, - Dmnt_Cmd_AtmosphereGetProcessHandle = 65000, + Dmnt_Cmd_AtmosphereGetProcessInfo = 65000, Dmnt_Cmd_AtmosphereGetCurrentLimitInfo = 65001, }; @@ -55,7 +55,7 @@ class DebugMonitorService final : public IServiceObject { Result DisableDebug(u32 which); /* Atmosphere commands. */ - Result AtmosphereGetProcessHandle(Out proc_hand, u64 pid); + Result AtmosphereGetProcessInfo(Out proc_hand, Out tid_sid, u64 pid); Result AtmosphereGetCurrentLimitInfo(Out cur_val, Out lim_val, u32 category, u32 resource); public: DEFINE_SERVICE_DISPATCH_TABLE { @@ -80,7 +80,7 @@ class DebugMonitorService final : public IServiceObject { MakeServiceCommandMeta(), /* Atmosphere extensions. */ - MakeServiceCommandMeta(), + MakeServiceCommandMeta(), MakeServiceCommandMeta(), }; };