mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-05-30 14:35:17 -04:00
git subrepo clone https://github.com/Atmosphere-NX/libstratosphere stratosphere/libstratosphere
subrepo: subdir: "stratosphere/libstratosphere" merged: "0c5dab80" upstream: origin: "https://github.com/Atmosphere-NX/libstratosphere" branch: "master" commit: "0c5dab80" git-subrepo: version: "0.4.0" origin: "https://github.com/ingydotnet/git-subrepo" commit: "5d6aba9"
This commit is contained in:
parent
3ea9f444db
commit
f534d3498e
166 changed files with 20474 additions and 0 deletions
76
stratosphere/libstratosphere/source/bpc_ams.c
Normal file
76
stratosphere/libstratosphere/source/bpc_ams.c
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 <switch/arm/atomics.h>
|
||||
#include <stratosphere/services/bpc_ams.h>
|
||||
|
||||
static Service g_bpcAmsSrv;
|
||||
static u64 g_bpcAmsAmsRefcnt;
|
||||
|
||||
Result bpcAmsInitialize(void) {
|
||||
atomicIncrement64(&g_bpcAmsAmsRefcnt);
|
||||
|
||||
if (serviceIsActive(&g_bpcAmsSrv)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Handle h;
|
||||
Result rc = svcConnectToNamedPort(&h, "bpc:ams");
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
serviceCreate(&g_bpcAmsSrv, h);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void bpcAmsExit(void) {
|
||||
if (atomicDecrement64(&g_bpcAmsAmsRefcnt) == 0)
|
||||
serviceClose(&g_bpcAmsSrv);
|
||||
}
|
||||
|
||||
Result bpcAmsRebootToFatalError(AtmosphereFatalErrorContext *ctx) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendBuffer(&c, ctx, sizeof(*ctx), BufferType_Normal);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_bpcAmsSrv, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65000;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_bpcAmsSrv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_bpcAmsSrv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
74
stratosphere/libstratosphere/source/cfg/cfg_flags.cpp
Normal file
74
stratosphere/libstratosphere/source/cfg/cfg_flags.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 <stratosphere/cfg.hpp>
|
||||
|
||||
namespace sts::cfg {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Helper. */
|
||||
bool HasFlagFile(const char *flag_path) {
|
||||
/* All flags are not present until the SD card is. */
|
||||
if (!IsSdCardInitialized()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Mount the SD card. */
|
||||
FsFileSystem sd_fs = {};
|
||||
if (R_FAILED(fsMountSdcard(&sd_fs))) {
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT { serviceClose(&sd_fs.s); };
|
||||
|
||||
/* Open the file. */
|
||||
FsFile flag_file;
|
||||
if (R_FAILED(fsFsOpenFile(&sd_fs, flag_path, FS_OPEN_READ, &flag_file))) {
|
||||
return false;
|
||||
}
|
||||
fsFileClose(&flag_file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Flag utilities. */
|
||||
bool HasFlag(ncm::TitleId title_id, const char *flag) {
|
||||
return HasTitleSpecificFlag(title_id, flag) || (IsHblTitleId(title_id) && HasHblFlag(flag));
|
||||
}
|
||||
|
||||
bool HasTitleSpecificFlag(ncm::TitleId title_id, const char *flag) {
|
||||
char title_flag[FS_MAX_PATH];
|
||||
std::snprintf(title_flag, sizeof(title_flag) - 1, "/atmosphere/titles/%016lx/flags/%s.flag", static_cast<u64>(title_id), flag);
|
||||
return HasFlagFile(title_flag);
|
||||
}
|
||||
|
||||
bool HasGlobalFlag(const char *flag) {
|
||||
char title_flag[FS_MAX_PATH];
|
||||
std::snprintf(title_flag, sizeof(title_flag) - 1, "/atmosphere/flags/%s.flag", flag);
|
||||
return HasFlagFile(title_flag);
|
||||
}
|
||||
|
||||
bool HasHblFlag(const char *flag) {
|
||||
char hbl_flag[0x100];
|
||||
std::snprintf(hbl_flag, sizeof(hbl_flag) - 1, "hbl_%s", flag);
|
||||
return HasGlobalFlag(hbl_flag);
|
||||
}
|
||||
|
||||
}
|
302
stratosphere/libstratosphere/source/cfg/cfg_override.cpp
Normal file
302
stratosphere/libstratosphere/source/cfg/cfg_override.cpp
Normal file
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* 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 <cstring>
|
||||
#include <strings.h>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/cfg.hpp>
|
||||
#include <stratosphere/pm.hpp>
|
||||
#include <stratosphere/util.hpp>
|
||||
|
||||
namespace sts::cfg {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Types. */
|
||||
struct OverrideKey {
|
||||
u64 key_combination;
|
||||
bool override_by_default;
|
||||
};
|
||||
|
||||
struct HblOverrideConfig {
|
||||
OverrideKey override_key;
|
||||
ncm::TitleId title_id;
|
||||
bool override_any_app;
|
||||
};
|
||||
|
||||
struct TitleSpecificOverrideConfig {
|
||||
OverrideKey override_key;
|
||||
OverrideKey cheat_enable_key;
|
||||
};
|
||||
|
||||
/* Override globals. */
|
||||
OverrideKey g_default_override_key = {
|
||||
.key_combination = KEY_L,
|
||||
.override_by_default = true,
|
||||
};
|
||||
|
||||
OverrideKey g_default_cheat_enable_key = {
|
||||
.key_combination = KEY_L,
|
||||
.override_by_default = true,
|
||||
};
|
||||
|
||||
HblOverrideConfig g_hbl_override_config = {
|
||||
.override_key = {
|
||||
.key_combination = KEY_R,
|
||||
.override_by_default = false,
|
||||
},
|
||||
.title_id = ncm::TitleId::AppletPhotoViewer,
|
||||
.override_any_app = true,
|
||||
};
|
||||
|
||||
char g_hbl_sd_path[0x100] = "/atmosphere/hbl.nsp";
|
||||
|
||||
/* Helpers. */
|
||||
OverrideKey ParseOverrideKey(const char *value) {
|
||||
OverrideKey cfg = {};
|
||||
|
||||
/* Parse on by default. */
|
||||
if (value[0] == '!') {
|
||||
cfg.override_by_default = true;
|
||||
value++;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
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) {
|
||||
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++;
|
||||
}
|
||||
std::snprintf(g_hbl_sd_path, sizeof(g_hbl_sd_path) - 1, "/%s", value);
|
||||
g_hbl_sd_path[sizeof(g_hbl_sd_path) - 1] = '\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 if (strcasecmp(name, "cheat_enable_key") == 0) {
|
||||
g_default_cheat_enable_key = ParseOverrideKey(value);
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int TitleSpecificIniHandler(void *user, const char *section, const char *name, const char *value) {
|
||||
TitleSpecificOverrideConfig *config = reinterpret_cast<TitleSpecificOverrideConfig *>(user);
|
||||
|
||||
if (strcasecmp(section, "override_config") == 0) {
|
||||
if (strcasecmp(name, "override_key") == 0) {
|
||||
config->override_key = ParseOverrideKey(value);
|
||||
} else if (strcasecmp(name, "cheat_enable_key") == 0) {
|
||||
config->cheat_enable_key = ParseOverrideKey(value);
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool IsOverrideKeyHeld(OverrideKey *cfg) {
|
||||
u64 kHeld = 0;
|
||||
bool keys_triggered = (R_SUCCEEDED(hid::GetKeysHeld(&kHeld)) && ((kHeld & cfg->key_combination) != 0));
|
||||
return IsSdCardInitialized() && (cfg->override_by_default ^ keys_triggered);
|
||||
}
|
||||
|
||||
void ParseIniFile(util::ini::Handler handler, const char *path, void *user_ctx) {
|
||||
/* Mount the SD card. */
|
||||
FsFileSystem sd_fs = {};
|
||||
if (R_FAILED(fsMountSdcard(&sd_fs))) {
|
||||
return;
|
||||
}
|
||||
ON_SCOPE_EXIT { serviceClose(&sd_fs.s); };
|
||||
|
||||
/* Open the file. */
|
||||
FsFile config_file;
|
||||
if (R_FAILED(fsFsOpenFile(&sd_fs, path, FS_OPEN_READ, &config_file))) {
|
||||
return;
|
||||
}
|
||||
ON_SCOPE_EXIT { fsFileClose(&config_file); };
|
||||
|
||||
/* Parse the config. */
|
||||
util::ini::ParseFile(&config_file, user_ctx, handler);
|
||||
}
|
||||
|
||||
void RefreshLoaderConfiguration() {
|
||||
ParseIniFile(LoaderIniHandler, "/atmosphere/loader.ini", nullptr);
|
||||
}
|
||||
|
||||
TitleSpecificOverrideConfig GetTitleOverrideConfig(ncm::TitleId title_id) {
|
||||
char path[FS_MAX_PATH];
|
||||
std::snprintf(path, sizeof(path) - 1, "/atmosphere/titles/%016lx/config.ini", static_cast<u64>(title_id));
|
||||
|
||||
TitleSpecificOverrideConfig config = {
|
||||
.override_key = g_default_override_key,
|
||||
.cheat_enable_key = g_default_cheat_enable_key,
|
||||
};
|
||||
ParseIniFile(TitleSpecificIniHandler, path, &config);
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool IsHblOverrideKeyHeld(ncm::TitleId title_id) {
|
||||
/* If the SD card isn't initialized, we can't override. */
|
||||
if (!IsSdCardInitialized()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* For system modules and anything launched before the home menu, always override. */
|
||||
if (title_id < ncm::TitleId::AppletStart || !pm::info::HasLaunchedTitle(ncm::TitleId::AppletQlaunch)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Unconditionally refresh loader.ini contents. */
|
||||
RefreshLoaderConfiguration();
|
||||
|
||||
/* Check HBL config. */
|
||||
return IsHblTitleId(title_id) && IsOverrideKeyHeld(&g_hbl_override_config.override_key);
|
||||
}
|
||||
|
||||
bool IsTitleOverrideKeyHeld(ncm::TitleId title_id) {
|
||||
/* If the SD card isn't initialized, we can't override. */
|
||||
if (!IsSdCardInitialized()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* For system modules and anything launched before the home menu, always override. */
|
||||
if (title_id < ncm::TitleId::AppletStart || !pm::info::HasLaunchedTitle(ncm::TitleId::AppletQlaunch)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Unconditionally refresh loader.ini contents. */
|
||||
RefreshLoaderConfiguration();
|
||||
|
||||
TitleSpecificOverrideConfig title_cfg = GetTitleOverrideConfig(title_id);
|
||||
return IsOverrideKeyHeld(&title_cfg.override_key);
|
||||
}
|
||||
|
||||
void GetOverrideKeyHeldStatus(bool *out_hbl, bool *out_title, ncm::TitleId title_id) {
|
||||
|
||||
/* If the SD card isn't initialized, we can't override. */
|
||||
if (!IsSdCardInitialized()) {
|
||||
*out_hbl = false;
|
||||
*out_title = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* For system modules and anything launched before the home menu, always override. */
|
||||
if (title_id < ncm::TitleId::AppletStart || !pm::info::HasLaunchedTitle(ncm::TitleId::AppletQlaunch)) {
|
||||
*out_hbl = false;
|
||||
*out_title = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Unconditionally refresh loader.ini contents. */
|
||||
RefreshLoaderConfiguration();
|
||||
|
||||
/* Set HBL output. */
|
||||
*out_hbl = IsHblTitleId(title_id) && IsOverrideKeyHeld(&g_hbl_override_config.override_key);
|
||||
|
||||
/* Set title specific output. */
|
||||
TitleSpecificOverrideConfig title_cfg = GetTitleOverrideConfig(title_id);
|
||||
*out_title = IsOverrideKeyHeld(&title_cfg.override_key);
|
||||
}
|
||||
|
||||
bool IsCheatEnableKeyHeld(ncm::TitleId title_id) {
|
||||
/* If the SD card isn't initialized, don't apply cheats. */
|
||||
if (!IsSdCardInitialized()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't apply cheats to HBL. */
|
||||
if (IsHblOverrideKeyHeld(title_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TitleSpecificOverrideConfig title_cfg = GetTitleOverrideConfig(title_id);
|
||||
return IsOverrideKeyHeld(&title_cfg.cheat_enable_key);
|
||||
}
|
||||
|
||||
/* HBL Configuration utilities. */
|
||||
bool IsHblTitleId(ncm::TitleId title_id) {
|
||||
return (g_hbl_override_config.override_any_app && ncm::IsApplicationTitleId(title_id)) || (title_id == g_hbl_override_config.title_id);
|
||||
}
|
||||
|
||||
const char *GetHblPath() {
|
||||
return g_hbl_sd_path;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 <stratosphere/cfg.hpp>
|
||||
|
||||
namespace sts::cfg {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr u64 InitialProcessIdMinDeprecated = 0x00;
|
||||
constexpr u64 InitialProcessIdMaxDeprecated = 0x50;
|
||||
|
||||
/* Privileged process globals. */
|
||||
HosMutex g_lock;
|
||||
bool g_got_privileged_process_status = false;
|
||||
u64 g_min_initial_process_id = 0, g_max_initial_process_id = 0;
|
||||
u64 g_cur_process_id = 0;
|
||||
|
||||
/* SD card helpers. */
|
||||
void GetPrivilegedProcessIdRange(u64 *out_min, u64 *out_max) {
|
||||
u64 min = 0, max = 0;
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||
/* On 5.0.0+, we can get precise limits from svcGetSystemInfo. */
|
||||
R_ASSERT(svcGetSystemInfo(&min, SystemInfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Minimum));
|
||||
R_ASSERT(svcGetSystemInfo(&max, SystemInfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Maximum));
|
||||
} else if (GetRuntimeFirmwareVersion() >= FirmwareVersion_400) {
|
||||
/* On 4.0.0-4.1.0, we can get the precise limits from normal svcGetInfo. */
|
||||
R_ASSERT(svcGetInfo(&min, InfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Minimum));
|
||||
R_ASSERT(svcGetInfo(&max, InfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Maximum));
|
||||
} else {
|
||||
/* On < 4.0.0, we just use hardcoded extents. */
|
||||
min = InitialProcessIdMinDeprecated;
|
||||
max = InitialProcessIdMaxDeprecated;
|
||||
}
|
||||
|
||||
*out_min = min;
|
||||
*out_max = max;
|
||||
}
|
||||
|
||||
u64 GetCurrentProcessId() {
|
||||
u64 process_id = 0;
|
||||
R_ASSERT(svcGetProcessId(&process_id, CUR_PROCESS_HANDLE));
|
||||
return process_id;
|
||||
}
|
||||
|
||||
void GetPrivilegedProcessStatus() {
|
||||
GetPrivilegedProcessIdRange(&g_min_initial_process_id, &g_max_initial_process_id);
|
||||
g_cur_process_id = GetCurrentProcessId();
|
||||
g_got_privileged_process_status = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Privileged Process utilities. */
|
||||
bool IsInitialProcess() {
|
||||
std::scoped_lock<HosMutex> lk(g_lock);
|
||||
|
||||
/* If we've not detected, do detection. */
|
||||
if (!g_got_privileged_process_status) {
|
||||
GetPrivilegedProcessStatus();
|
||||
}
|
||||
|
||||
/* Determine if we're privileged, and return. */
|
||||
return g_min_initial_process_id <= g_cur_process_id && g_cur_process_id <= g_max_initial_process_id;
|
||||
}
|
||||
|
||||
void GetInitialProcessRange(u64 *out_min, u64 *out_max) {
|
||||
std::scoped_lock<HosMutex> lk(g_lock);
|
||||
|
||||
/* If we've not detected, do detection. */
|
||||
if (!g_got_privileged_process_status) {
|
||||
GetPrivilegedProcessStatus();
|
||||
}
|
||||
|
||||
*out_min = g_min_initial_process_id;
|
||||
*out_max = g_max_initial_process_id;
|
||||
}
|
||||
|
||||
}
|
82
stratosphere/libstratosphere/source/cfg/cfg_sd_card.cpp
Normal file
82
stratosphere/libstratosphere/source/cfg/cfg_sd_card.cpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 <stratosphere/cfg.hpp>
|
||||
#include <stratosphere/sm.hpp>
|
||||
|
||||
namespace sts::cfg {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr sm::ServiceName RequiredServicesForSdCardAccess[] = {
|
||||
sm::ServiceName::Encode("pcv"),
|
||||
sm::ServiceName::Encode("gpio"),
|
||||
sm::ServiceName::Encode("pinmux"),
|
||||
sm::ServiceName::Encode("psc:c")
|
||||
};
|
||||
constexpr size_t NumRequiredServicesForSdCardAccess = util::size(RequiredServicesForSdCardAccess);
|
||||
|
||||
/* SD card globals. */
|
||||
HosMutex g_sd_card_lock;
|
||||
bool g_sd_card_initialized = false;
|
||||
FsFileSystem g_sd_card_filesystem = {};
|
||||
|
||||
/* SD card helpers. */
|
||||
Result TryInitializeSdCard() {
|
||||
for (size_t i = 0; i < NumRequiredServicesForSdCardAccess; i++) {
|
||||
bool service_present = false;
|
||||
R_TRY(sm::HasService(&service_present, RequiredServicesForSdCardAccess[i]));
|
||||
if (!service_present) {
|
||||
return ResultFsSdCardNotPresent;
|
||||
}
|
||||
}
|
||||
R_ASSERT(fsMountSdcard(&g_sd_card_filesystem));
|
||||
g_sd_card_initialized = true;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void InitializeSdCard() {
|
||||
for (size_t i = 0; i < NumRequiredServicesForSdCardAccess; i++) {
|
||||
R_ASSERT(sm::WaitService(RequiredServicesForSdCardAccess[i]));
|
||||
}
|
||||
R_ASSERT(fsMountSdcard(&g_sd_card_filesystem));
|
||||
g_sd_card_initialized = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* SD card utilities. */
|
||||
bool IsSdCardInitialized() {
|
||||
std::scoped_lock<HosMutex> lk(g_sd_card_lock);
|
||||
|
||||
if (!g_sd_card_initialized) {
|
||||
if (R_SUCCEEDED(TryInitializeSdCard())) {
|
||||
g_sd_card_initialized = true;
|
||||
}
|
||||
}
|
||||
return g_sd_card_initialized;
|
||||
}
|
||||
|
||||
void WaitSdCardInitialized() {
|
||||
std::scoped_lock<HosMutex> lk(g_sd_card_lock);
|
||||
|
||||
InitializeSdCard();
|
||||
}
|
||||
|
||||
}
|
650
stratosphere/libstratosphere/source/dmntcht.c
Normal file
650
stratosphere/libstratosphere/source/dmntcht.c
Normal file
|
@ -0,0 +1,650 @@
|
|||
/*
|
||||
* 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 <switch/arm/atomics.h>
|
||||
#include <stratosphere/services/dmntcht.h>
|
||||
|
||||
static Service g_dmntchtService;
|
||||
static u64 g_refCnt;
|
||||
|
||||
static Result _dmntchtGetCount(u64 cmd_id, u64 *out_count);
|
||||
static Result _dmntchtGetEntries(u64 cmd_id, void *buffer, u64 buffer_size, u64 offset, u64 *out_count);
|
||||
|
||||
Result dmntchtInitialize(void) {
|
||||
atomicIncrement64(&g_refCnt);
|
||||
|
||||
if (serviceIsActive(&g_dmntchtService)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return smGetService(&g_dmntchtService, "dmnt:cht");
|
||||
}
|
||||
|
||||
void dmntchtExit(void) {
|
||||
if (atomicIncrement64(&g_refCnt) == 0) {
|
||||
serviceClose(&g_dmntchtService);
|
||||
}
|
||||
}
|
||||
|
||||
Service* dmntchtGetServiceSession(void) {
|
||||
return &g_dmntchtService;
|
||||
}
|
||||
|
||||
Result dmntchtHasCheatProcess(bool *out) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65000;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
bool out;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out) *out = resp->out;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtGetCheatProcessEvent(Event *event) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65001;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
eventLoadRemote(event, r.Handles[0], true);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtGetCheatProcessMetadata(DmntCheatProcessMetadata *out_metadata) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65002;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
DmntCheatProcessMetadata metadata;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out_metadata) *out_metadata = resp->metadata;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtForceOpenCheatProcess(void) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65003;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static Result _dmntchtGetCount(u64 cmd_id, u64 *out_count) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = cmd_id;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u64 count;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
*out_count = resp->count;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static Result _dmntchtGetEntries(u64 cmd_id, void *buffer, u64 buffer_size, u64 offset, u64 *out_count) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddRecvBuffer(&c, buffer, buffer_size, 0);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 offset;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = cmd_id;
|
||||
raw->offset = offset;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u64 count;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out_count) *out_count = resp->count;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtGetCheatProcessMappingCount(u64 *out_count) {
|
||||
return _dmntchtGetCount(65100, out_count);
|
||||
}
|
||||
|
||||
Result dmntchtGetCheatProcessMappings(MemoryInfo *buffer, u64 max_count, u64 offset, u64 *out_count) {
|
||||
return _dmntchtGetEntries(65101, buffer, sizeof(*buffer) * max_count, offset, out_count);
|
||||
}
|
||||
|
||||
Result dmntchtReadCheatProcessMemory(u64 address, void *buffer, size_t size) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddRecvBuffer(&c, buffer, size, 0);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 address;
|
||||
u64 size;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65102;
|
||||
raw->address = address;
|
||||
raw->size = size;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtWriteCheatProcessMemory(u64 address, const void *buffer, size_t size) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendBuffer(&c, buffer, size, 0);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 address;
|
||||
u64 size;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65103;
|
||||
raw->address = address;
|
||||
raw->size = size;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtQueryCheatProcessMemory(MemoryInfo *mem_info, u64 address){
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 address;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65104;
|
||||
raw->address = address;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
MemoryInfo mem_info;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (mem_info) *mem_info = resp->mem_info;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtGetCheatCount(u64 *out_count) {
|
||||
return _dmntchtGetCount(65200, out_count);
|
||||
}
|
||||
|
||||
Result dmntchtGetCheats(DmntCheatEntry *buffer, u64 max_count, u64 offset, u64 *out_count) {
|
||||
return _dmntchtGetEntries(65201, buffer, sizeof(*buffer) * max_count, offset, out_count);
|
||||
}
|
||||
|
||||
Result dmntchtGetCheatById(DmntCheatEntry *buffer, u32 cheat_id) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddRecvBuffer(&c, buffer, sizeof(*buffer), 0);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u32 cheat_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65202;
|
||||
raw->cheat_id = cheat_id;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtToggleCheat(u32 cheat_id) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u32 cheat_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65203;
|
||||
raw->cheat_id = cheat_id;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtAddCheat(DmntCheatDefinition *buffer, bool enabled, u32 *out_cheat_id) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendBuffer(&c, buffer, sizeof(*buffer), 0);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u8 enabled;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65204;
|
||||
raw->enabled = enabled;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u32 cheat_id;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out_cheat_id) *out_cheat_id = resp->cheat_id;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtRemoveCheat(u32 cheat_id) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u32 cheat_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65205;
|
||||
raw->cheat_id = cheat_id;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
Result dmntchtGetFrozenAddressCount(u64 *out_count) {
|
||||
return _dmntchtGetCount(65300, out_count);
|
||||
}
|
||||
|
||||
Result dmntchtGetFrozenAddresses(DmntFrozenAddressEntry *buffer, u64 max_count, u64 offset, u64 *out_count) {
|
||||
return _dmntchtGetEntries(65301, buffer, sizeof(*buffer) * max_count, offset, out_count);
|
||||
}
|
||||
|
||||
Result dmntchtGetFrozenAddress(DmntFrozenAddressEntry *out, u64 address) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 address;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65302;
|
||||
raw->address = address;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
DmntFrozenAddressEntry entry;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out) *out = resp->entry;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtEnableFrozenAddress(u64 address, u64 width, u64 *out_value) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 address;
|
||||
u64 width;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65303;
|
||||
raw->address = address;
|
||||
raw->width = width;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u64 value;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out_value) *out_value = resp->value;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntchtDisableFrozenAddress(u64 address) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 address;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntchtService, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65304;
|
||||
raw->address = address;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntchtService);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u64 value;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntchtService, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
143
stratosphere/libstratosphere/source/emummc_utilities.cpp
Normal file
143
stratosphere/libstratosphere/source/emummc_utilities.cpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
/* EFS0 */
|
||||
static constexpr u32 EmummcStorageMagic = 0x30534645;
|
||||
static constexpr size_t EmummcMaxDirLength = 0x7F;
|
||||
|
||||
struct EmummcBaseConfig {
|
||||
u32 magic;
|
||||
u32 type;
|
||||
u32 id;
|
||||
u32 fs_version;
|
||||
};
|
||||
|
||||
struct EmummcPartitionConfig {
|
||||
u64 start_sector;
|
||||
};
|
||||
|
||||
struct EmummcFileConfig {
|
||||
char path[EmummcMaxDirLength+1];
|
||||
};
|
||||
|
||||
struct ExoEmummcConfig {
|
||||
EmummcBaseConfig base_cfg;
|
||||
union {
|
||||
EmummcPartitionConfig partition_cfg;
|
||||
EmummcFileConfig file_cfg;
|
||||
};
|
||||
char emu_dir_path[EmummcMaxDirLength+1];
|
||||
};
|
||||
|
||||
enum EmummcType {
|
||||
EmummcType_Emmc = 0,
|
||||
EmummcType_Sd,
|
||||
EmummcType_SdFile,
|
||||
|
||||
EmummcType_Max,
|
||||
};
|
||||
|
||||
static bool g_IsEmummc = false;
|
||||
static bool g_HasCached = false;
|
||||
static Mutex g_Mutex;
|
||||
static ExoEmummcConfig g_exo_emummc_config;
|
||||
|
||||
static void _CacheValues(void)
|
||||
{
|
||||
if (__atomic_load_n(&g_HasCached, __ATOMIC_SEQ_CST))
|
||||
return;
|
||||
|
||||
mutexLock(&g_Mutex);
|
||||
|
||||
if (g_HasCached) {
|
||||
mutexUnlock(&g_Mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
static struct {
|
||||
char file_path[EmummcMaxDirLength+1];
|
||||
char nintendo_path[EmummcMaxDirLength+1];
|
||||
} __attribute__((aligned(0x1000))) paths;
|
||||
|
||||
{
|
||||
SecmonArgs args = {0};
|
||||
args.X[0] = 0xF0000404; /* smcAmsGetEmunandConfig */
|
||||
args.X[1] = 0; /* NAND */
|
||||
args.X[2] = reinterpret_cast<u64>(&paths); /* path output */
|
||||
R_ASSERT(svcCallSecureMonitor(&args));
|
||||
if (args.X[0] != 0) {
|
||||
std::abort();
|
||||
}
|
||||
std::memcpy(&g_exo_emummc_config, &args.X[1], sizeof(args) - sizeof(args.X[0]));
|
||||
}
|
||||
|
||||
const EmummcType emummc_type = static_cast<EmummcType>(g_exo_emummc_config.base_cfg.type);
|
||||
|
||||
/* Ignore format warnings. */
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat-truncation"
|
||||
switch (emummc_type) {
|
||||
case EmummcType_SdFile:
|
||||
std::snprintf(g_exo_emummc_config.file_cfg.path, sizeof(g_exo_emummc_config.file_cfg.path), "/%s", paths.file_path);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
std::snprintf(g_exo_emummc_config.emu_dir_path, sizeof(g_exo_emummc_config.emu_dir_path), "/%s", paths.nintendo_path);
|
||||
|
||||
g_IsEmummc = g_exo_emummc_config.base_cfg.magic == EmummcStorageMagic && emummc_type != EmummcType_Emmc;
|
||||
|
||||
/* Default Nintendo redirection path. */
|
||||
if (g_IsEmummc) {
|
||||
if (std::strcmp(g_exo_emummc_config.emu_dir_path, "/") == 0) {
|
||||
std::snprintf(g_exo_emummc_config.emu_dir_path, sizeof(g_exo_emummc_config.emu_dir_path), "/emummc/Nintendo_%04x", g_exo_emummc_config.base_cfg.id);
|
||||
}
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
__atomic_store_n(&g_HasCached, true, __ATOMIC_SEQ_CST);
|
||||
|
||||
mutexUnlock(&g_Mutex);
|
||||
}
|
||||
|
||||
|
||||
/* Get whether emummc is active. */
|
||||
bool IsEmummc() {
|
||||
_CacheValues();
|
||||
return g_IsEmummc;
|
||||
}
|
||||
|
||||
/* Get Nintendo redirection path. */
|
||||
const char *GetEmummcNintendoDirPath() {
|
||||
_CacheValues();
|
||||
if (!g_IsEmummc) {
|
||||
return nullptr;
|
||||
}
|
||||
return g_exo_emummc_config.emu_dir_path;
|
||||
}
|
||||
|
||||
/* Get Emummc folderpath, NULL if not file-based. */
|
||||
const char *GetEmummcFilePath() {
|
||||
_CacheValues();
|
||||
if (!g_IsEmummc || g_exo_emummc_config.base_cfg.type != EmummcType_SdFile) {
|
||||
return nullptr;
|
||||
}
|
||||
return g_exo_emummc_config.file_cfg.path;
|
||||
}
|
147
stratosphere/libstratosphere/source/firmware_version.cpp
Normal file
147
stratosphere/libstratosphere/source/firmware_version.cpp
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 <mutex>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
static FirmwareVersion g_firmware_version = FirmwareVersion_Min;
|
||||
static bool g_HasCached = 0;
|
||||
static Mutex g_Mutex;
|
||||
|
||||
static void _CacheValues(void)
|
||||
{
|
||||
if (__atomic_load_n(&g_HasCached, __ATOMIC_SEQ_CST))
|
||||
return;
|
||||
|
||||
mutexLock(&g_Mutex);
|
||||
|
||||
if (g_HasCached) {
|
||||
mutexUnlock(&g_Mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
u32 target_fw = 0;
|
||||
{
|
||||
SecmonArgs args = {0};
|
||||
args.X[0] = 0xC3000002; /* smcGetConfig */
|
||||
args.X[1] = 65000; /* ConfigItem_ExosphereVersion */
|
||||
R_ASSERT(svcCallSecureMonitor(&args));
|
||||
if (args.X[0] != 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
target_fw = (args.X[1] >> 0x08) & 0xFF;
|
||||
}
|
||||
|
||||
switch (static_cast<AtmosphereTargetFirmware>(target_fw)) {
|
||||
case AtmosphereTargetFirmware_100:
|
||||
g_firmware_version = FirmwareVersion_100;
|
||||
break;
|
||||
case AtmosphereTargetFirmware_200:
|
||||
g_firmware_version = FirmwareVersion_200;
|
||||
break;
|
||||
case AtmosphereTargetFirmware_300:
|
||||
g_firmware_version = FirmwareVersion_300;
|
||||
break;
|
||||
case AtmosphereTargetFirmware_400:
|
||||
g_firmware_version = FirmwareVersion_400;
|
||||
break;
|
||||
case AtmosphereTargetFirmware_500:
|
||||
g_firmware_version = FirmwareVersion_500;
|
||||
break;
|
||||
case AtmosphereTargetFirmware_600:
|
||||
case AtmosphereTargetFirmware_620:
|
||||
g_firmware_version = FirmwareVersion_600;
|
||||
break;
|
||||
case AtmosphereTargetFirmware_700:
|
||||
g_firmware_version = FirmwareVersion_700;
|
||||
break;
|
||||
case AtmosphereTargetFirmware_800:
|
||||
g_firmware_version = FirmwareVersion_800;
|
||||
break;
|
||||
case AtmosphereTargetFirmware_810:
|
||||
g_firmware_version = FirmwareVersion_810;
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
break;
|
||||
}
|
||||
|
||||
__atomic_store_n(&g_HasCached, true, __ATOMIC_SEQ_CST);
|
||||
|
||||
mutexUnlock(&g_Mutex);
|
||||
}
|
||||
|
||||
FirmwareVersion GetRuntimeFirmwareVersion() {
|
||||
_CacheValues();
|
||||
return g_firmware_version;
|
||||
}
|
||||
|
||||
void SetFirmwareVersionForLibnx() {
|
||||
u32 major = 0, minor = 0, micro = 0;
|
||||
switch (GetRuntimeFirmwareVersion()) {
|
||||
case FirmwareVersion_100:
|
||||
major = 1;
|
||||
minor = 0;
|
||||
micro = 0;
|
||||
break;
|
||||
case FirmwareVersion_200:
|
||||
major = 2;
|
||||
minor = 0;
|
||||
micro = 0;
|
||||
break;
|
||||
case FirmwareVersion_300:
|
||||
major = 3;
|
||||
minor = 0;
|
||||
micro = 0;
|
||||
break;
|
||||
case FirmwareVersion_400:
|
||||
major = 4;
|
||||
minor = 0;
|
||||
micro = 0;
|
||||
break;
|
||||
case FirmwareVersion_500:
|
||||
major = 5;
|
||||
minor = 0;
|
||||
micro = 0;
|
||||
break;
|
||||
case FirmwareVersion_600:
|
||||
major = 6;
|
||||
minor = 0;
|
||||
micro = 0;
|
||||
break;
|
||||
case FirmwareVersion_700:
|
||||
major = 7;
|
||||
minor = 0;
|
||||
micro = 0;
|
||||
break;
|
||||
case FirmwareVersion_800:
|
||||
major = 8;
|
||||
minor = 0;
|
||||
micro = 0;
|
||||
break;
|
||||
case FirmwareVersion_810:
|
||||
major = 8;
|
||||
minor = 1;
|
||||
micro = 0;
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
break;
|
||||
}
|
||||
hosversionSet(MAKEHOSVERSION(major, minor, micro));
|
||||
}
|
70
stratosphere/libstratosphere/source/hid/hid_api.cpp
Normal file
70
stratosphere/libstratosphere/source/hid/hid_api.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/pm.hpp>
|
||||
#include <stratosphere/hid.hpp>
|
||||
|
||||
namespace sts::hid {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Global lock. */
|
||||
HosMutex g_hid_lock;
|
||||
bool g_initialized_hid = false;
|
||||
|
||||
/* Helper. */
|
||||
void InitializeHid() {
|
||||
R_ASSERT(smInitialize());
|
||||
ON_SCOPE_EXIT { smExit(); };
|
||||
{
|
||||
R_ASSERT(hidInitialize());
|
||||
}
|
||||
}
|
||||
|
||||
Result EnsureHidInitialized() {
|
||||
if (!g_initialized_hid) {
|
||||
if (!serviceIsActive(hidGetServiceSession())) {
|
||||
if (!pm::info::HasLaunchedTitle(ncm::TitleId::Hid)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
|
||||
}
|
||||
InitializeHid();
|
||||
}
|
||||
g_initialized_hid = true;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result GetKeysHeld(u64 *out) {
|
||||
std::scoped_lock<HosMutex> lk(g_hid_lock);
|
||||
|
||||
R_TRY(EnsureHidInitialized());
|
||||
|
||||
hidScanInput();
|
||||
*out = 0;
|
||||
|
||||
for (size_t controller = 0; controller < 10; controller++) {
|
||||
*out |= hidKeysHeld(static_cast<HidControllerID>(controller));
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
186
stratosphere/libstratosphere/source/kvdb/kvdb_archive.cpp
Normal file
186
stratosphere/libstratosphere/source/kvdb/kvdb_archive.cpp
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* 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.hpp>
|
||||
#include <stratosphere/kvdb/kvdb_archive.hpp>
|
||||
|
||||
namespace sts::kvdb {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr u8 ArchiveHeaderMagic[4] = {'I', 'M', 'K', 'V'};
|
||||
constexpr u8 ArchiveEntryMagic[4] = {'I', 'M', 'E', 'N'};
|
||||
|
||||
/* Archive types. */
|
||||
struct ArchiveHeader {
|
||||
u8 magic[sizeof(ArchiveHeaderMagic)];
|
||||
u32 pad;
|
||||
u32 entry_count;
|
||||
|
||||
Result Validate() const {
|
||||
if (std::memcmp(this->magic, ArchiveHeaderMagic, sizeof(ArchiveHeaderMagic)) != 0) {
|
||||
return ResultKvdbInvalidKeyValue;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static ArchiveHeader Make(size_t entry_count) {
|
||||
ArchiveHeader header = {};
|
||||
std::memcpy(header.magic, ArchiveHeaderMagic, sizeof(ArchiveHeaderMagic));
|
||||
header.entry_count = static_cast<u32>(entry_count);
|
||||
return header;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(ArchiveHeader) == 0xC && std::is_pod<ArchiveHeader>::value, "ArchiveHeader definition!");
|
||||
|
||||
struct ArchiveEntryHeader {
|
||||
u8 magic[sizeof(ArchiveEntryMagic)];
|
||||
u32 key_size;
|
||||
u32 value_size;
|
||||
|
||||
Result Validate() const {
|
||||
if (std::memcmp(this->magic, ArchiveEntryMagic, sizeof(ArchiveEntryMagic)) != 0) {
|
||||
return ResultKvdbInvalidKeyValue;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static ArchiveEntryHeader Make(size_t ksz, size_t vsz) {
|
||||
ArchiveEntryHeader header = {};
|
||||
std::memcpy(header.magic, ArchiveEntryMagic, sizeof(ArchiveEntryMagic));
|
||||
header.key_size = ksz;
|
||||
header.value_size = vsz;
|
||||
return header;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(ArchiveEntryHeader) == 0xC && std::is_pod<ArchiveEntryHeader>::value, "ArchiveEntryHeader definition!");
|
||||
|
||||
}
|
||||
|
||||
/* Reader functionality. */
|
||||
Result ArchiveReader::Peek(void *dst, size_t size) {
|
||||
/* Bounds check. */
|
||||
if (this->offset + size > this->buffer.GetSize() || this->offset + size <= this->offset) {
|
||||
return ResultKvdbInvalidKeyValue;
|
||||
}
|
||||
|
||||
std::memcpy(dst, this->buffer.Get() + this->offset, size);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ArchiveReader::Read(void *dst, size_t size) {
|
||||
R_TRY(this->Peek(dst, size));
|
||||
this->offset += size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ArchiveReader::ReadEntryCount(size_t *out) {
|
||||
/* This should only be called at the start of reading stream. */
|
||||
if (this->offset != 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Read and validate header. */
|
||||
ArchiveHeader header;
|
||||
R_TRY(this->Read(&header, sizeof(header)));
|
||||
R_TRY(header.Validate());
|
||||
|
||||
*out = header.entry_count;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ArchiveReader::GetEntrySize(size_t *out_key_size, size_t *out_value_size) {
|
||||
/* This should only be called after ReadEntryCount. */
|
||||
if (this->offset == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Peek the next entry header. */
|
||||
ArchiveEntryHeader header;
|
||||
R_TRY(this->Peek(&header, sizeof(header)));
|
||||
R_TRY(header.Validate());
|
||||
|
||||
*out_key_size = header.key_size;
|
||||
*out_value_size = header.value_size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ArchiveReader::ReadEntry(void *out_key, size_t key_size, void *out_value, size_t value_size) {
|
||||
/* This should only be called after ReadEntryCount. */
|
||||
if (this->offset == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Read the next entry header. */
|
||||
ArchiveEntryHeader header;
|
||||
R_TRY(this->Read(&header, sizeof(header)));
|
||||
R_TRY(header.Validate());
|
||||
|
||||
/* Key size and Value size must be correct. */
|
||||
if (key_size != header.key_size || value_size != header.value_size) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
R_ASSERT(this->Read(out_key, key_size));
|
||||
R_ASSERT(this->Read(out_value, value_size));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Writer functionality. */
|
||||
Result ArchiveWriter::Write(const void *src, size_t size) {
|
||||
/* Bounds check. */
|
||||
if (this->offset + size > this->buffer.GetSize() || this->offset + size <= this->offset) {
|
||||
return ResultKvdbInvalidKeyValue;
|
||||
}
|
||||
|
||||
std::memcpy(this->buffer.Get() + this->offset, src, size);
|
||||
this->offset += size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void ArchiveWriter::WriteHeader(size_t entry_count) {
|
||||
/* This should only be called at start of write. */
|
||||
if (this->offset != 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
ArchiveHeader header = ArchiveHeader::Make(entry_count);
|
||||
R_ASSERT(this->Write(&header, sizeof(header)));
|
||||
}
|
||||
|
||||
void ArchiveWriter::WriteEntry(const void *key, size_t key_size, const void *value, size_t value_size) {
|
||||
/* This should only be called after writing header. */
|
||||
if (this->offset == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
ArchiveEntryHeader header = ArchiveEntryHeader::Make(key_size, value_size);
|
||||
R_ASSERT(this->Write(&header, sizeof(header)));
|
||||
R_ASSERT(this->Write(key, key_size));
|
||||
R_ASSERT(this->Write(value, value_size));
|
||||
}
|
||||
|
||||
/* Size helper functionality. */
|
||||
ArchiveSizeHelper::ArchiveSizeHelper() : size(sizeof(ArchiveHeader)) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
void ArchiveSizeHelper::AddEntry(size_t key_size, size_t value_size) {
|
||||
this->size += sizeof(ArchiveEntryHeader) + key_size + value_size;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
* 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 <sys/stat.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/kvdb/kvdb_file_key_value_store.hpp>
|
||||
|
||||
namespace sts::kvdb {
|
||||
|
||||
/* Cache implementation. */
|
||||
void *FileKeyValueStore::Cache::Allocate(size_t size) {
|
||||
if (this->backing_buffer_size - this->backing_buffer_free_offset < size) {
|
||||
return nullptr;
|
||||
}
|
||||
ON_SCOPE_EXIT { this->backing_buffer_free_offset += size; };
|
||||
return this->backing_buffer + this->backing_buffer_free_offset;
|
||||
}
|
||||
|
||||
Result FileKeyValueStore::Cache::Initialize(void *buffer, size_t buffer_size, size_t capacity) {
|
||||
this->backing_buffer = static_cast<u8 *>(buffer);
|
||||
this->backing_buffer_size = buffer_size;
|
||||
this->backing_buffer_free_offset = 0;
|
||||
this->entries = nullptr;
|
||||
this->count = 0;
|
||||
this->capacity = capacity;
|
||||
|
||||
/* If we have memory to work with, ensure it's at least enough for the cache entries. */
|
||||
if (this->backing_buffer != nullptr) {
|
||||
this->entries = static_cast<decltype(this->entries)>(this->Allocate(sizeof(*this->entries) * this->capacity));
|
||||
if (this->entries == nullptr) {
|
||||
return ResultKvdbBufferInsufficient;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void FileKeyValueStore::Cache::Invalidate() {
|
||||
if (!this->HasEntries()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reset the allocation pool. */
|
||||
this->backing_buffer_free_offset = 0;
|
||||
this->count = 0;
|
||||
this->entries = static_cast<decltype(this->entries)>(this->Allocate(sizeof(*this->entries) * this->capacity));
|
||||
if (this->entries == nullptr) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<size_t> FileKeyValueStore::Cache::TryGet(void *out_value, size_t max_out_size, const void *key, size_t key_size) {
|
||||
if (!this->HasEntries()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/* Try to find the entry. */
|
||||
for (size_t i = 0; i < this->count; i++) {
|
||||
const auto &entry = this->entries[i];
|
||||
if (entry.key_size == key_size && std::memcmp(entry.key, key, key_size) == 0) {
|
||||
/* If we don't have enough space, fail to read from cache. */
|
||||
if (max_out_size < entry.value_size) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::memcpy(out_value, entry.value, entry.value_size);
|
||||
return entry.value_size;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<size_t> FileKeyValueStore::Cache::TryGetSize(const void *key, size_t key_size) {
|
||||
if (!this->HasEntries()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/* Try to find the entry. */
|
||||
for (size_t i = 0; i < this->count; i++) {
|
||||
const auto &entry = this->entries[i];
|
||||
if (entry.key_size == key_size && std::memcmp(entry.key, key, key_size) == 0) {
|
||||
return entry.value_size;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void FileKeyValueStore::Cache::Set(const void *key, size_t key_size, const void *value, size_t value_size) {
|
||||
if (!this->HasEntries()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Ensure key size is small enough. */
|
||||
if (key_size > MaxKeySize) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* If we're at capacity, invalidate the cache. */
|
||||
if (this->count == this->capacity) {
|
||||
this->Invalidate();
|
||||
}
|
||||
|
||||
/* Allocate memory for the value. */
|
||||
void *value_buf = this->Allocate(value_size);
|
||||
if (value_buf == nullptr) {
|
||||
/* We didn't have enough memory for the value. Invalidating might get us enough memory. */
|
||||
this->Invalidate();
|
||||
value_buf = this->Allocate(value_size);
|
||||
if (value_buf == nullptr) {
|
||||
/* If we still don't have enough memory, just fail to put the value in the cache. */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto &entry = this->entries[this->count++];
|
||||
std::memcpy(entry.key, key, key_size);
|
||||
entry.key_size = key_size;
|
||||
entry.value = value_buf;
|
||||
std::memcpy(entry.value, value, value_size);
|
||||
entry.value_size = value_size;
|
||||
}
|
||||
|
||||
bool FileKeyValueStore::Cache::Contains(const void *key, size_t key_size) {
|
||||
return this->TryGetSize(key, key_size).has_value();
|
||||
}
|
||||
|
||||
/* Store functionality. */
|
||||
FileKeyValueStore::Path FileKeyValueStore::GetPath(const void *_key, size_t key_size) {
|
||||
/* Format is "<dir>/<hex formatted key>.val" */
|
||||
FileKeyValueStore::Path key_path(this->dir_path.Get());
|
||||
key_path.Append('/');
|
||||
|
||||
/* Append hex formatted key. */
|
||||
const u8 *key = static_cast<const u8 *>(_key);
|
||||
for (size_t i = 0; i < key_size; i++) {
|
||||
key_path.AppendFormat("%02x", key[i]);
|
||||
}
|
||||
|
||||
/* Append extension. */
|
||||
key_path.Append(FileExtension);
|
||||
|
||||
return key_path;
|
||||
}
|
||||
|
||||
Result FileKeyValueStore::GetKey(size_t *out_size, void *_out_key, size_t max_out_size, const FileKeyValueStore::FileName &file_name) {
|
||||
/* Validate that the filename can be converted to a key. */
|
||||
/* TODO: Nintendo does not validate that the key is valid hex. Should we do this? */
|
||||
const size_t file_name_len = file_name.GetLength();
|
||||
const size_t key_name_len = file_name_len - FileExtensionLength;
|
||||
if (file_name_len < FileExtensionLength + 2 || !file_name.EndsWith(FileExtension) || key_name_len % 2 != 0) {
|
||||
return ResultKvdbInvalidKeyValue;
|
||||
}
|
||||
|
||||
/* Validate that we have space for the converted key. */
|
||||
const size_t key_size = key_name_len / 2;
|
||||
if (key_size > max_out_size) {
|
||||
return ResultKvdbBufferInsufficient;
|
||||
}
|
||||
|
||||
/* Convert the hex key back. */
|
||||
u8 *out_key = static_cast<u8 *>(_out_key);
|
||||
for (size_t i = 0; i < key_size; i++) {
|
||||
char substr[2 * sizeof(u8) + 1];
|
||||
file_name.GetSubstring(substr, sizeof(substr), 2 * i, sizeof(substr) - 1);
|
||||
out_key[i] = static_cast<u8>(std::strtoul(substr, nullptr, 0x10));
|
||||
}
|
||||
|
||||
*out_size = key_size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result FileKeyValueStore::Initialize(const char *dir) {
|
||||
return this->InitializeWithCache(dir, nullptr, 0, 0);
|
||||
}
|
||||
|
||||
Result FileKeyValueStore::InitializeWithCache(const char *dir, void *cache_buffer, size_t cache_buffer_size, size_t cache_capacity) {
|
||||
/* Ensure that the passed path is a directory. */
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(dir, &st) != 0 || !(S_ISDIR(st.st_mode))) {
|
||||
return ResultFsPathNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set path. */
|
||||
this->dir_path.Set(dir);
|
||||
|
||||
/* Initialize our cache. */
|
||||
R_TRY(this->cache.Initialize(cache_buffer, cache_buffer_size, cache_capacity));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result FileKeyValueStore::Get(size_t *out_size, void *out_value, size_t max_out_size, const void *key, size_t key_size) {
|
||||
std::scoped_lock lk(this->lock);
|
||||
|
||||
/* Ensure key size is small enough. */
|
||||
if (key_size > MaxKeySize) {
|
||||
return ResultKvdbKeyCapacityInsufficient;
|
||||
}
|
||||
|
||||
/* Try to get from cache. */
|
||||
{
|
||||
auto size = this->cache.TryGet(out_value, max_out_size, key, key_size);
|
||||
if (size) {
|
||||
*out_size = *size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
/* Open the value file. */
|
||||
FILE *fp = fopen(this->GetPath(key, key_size), "rb");
|
||||
if (fp == nullptr) {
|
||||
R_TRY_CATCH(fsdevGetLastResult()) {
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
return ResultKvdbKeyNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(fp); };
|
||||
|
||||
/* Get the value size. */
|
||||
fseek(fp, 0, SEEK_END);
|
||||
const size_t value_size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
/* Ensure there's enough space for the value. */
|
||||
if (max_out_size < value_size) {
|
||||
return ResultKvdbBufferInsufficient;
|
||||
}
|
||||
|
||||
/* Read the value. */
|
||||
if (fread(out_value, value_size, 1, fp) != 1) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
*out_size = value_size;
|
||||
|
||||
/* Cache the newly read value. */
|
||||
this->cache.Set(key, key_size, out_value, value_size);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result FileKeyValueStore::GetSize(size_t *out_size, const void *key, size_t key_size) {
|
||||
std::scoped_lock lk(this->lock);
|
||||
|
||||
/* Ensure key size is small enough. */
|
||||
if (key_size > MaxKeySize) {
|
||||
return ResultKvdbKeyCapacityInsufficient;
|
||||
}
|
||||
|
||||
/* Try to get from cache. */
|
||||
{
|
||||
auto size = this->cache.TryGetSize(key, key_size);
|
||||
if (size) {
|
||||
*out_size = *size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
/* Open the value file. */
|
||||
FILE *fp = fopen(this->GetPath(key, key_size), "rb");
|
||||
if (fp == nullptr) {
|
||||
R_TRY_CATCH(fsdevGetLastResult()) {
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
return ResultKvdbKeyNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(fp); };
|
||||
|
||||
/* Get the value size. */
|
||||
fseek(fp, 0, SEEK_END);
|
||||
*out_size = ftell(fp);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result FileKeyValueStore::Set(const void *key, size_t key_size, const void *value, size_t value_size) {
|
||||
std::scoped_lock lk(this->lock);
|
||||
|
||||
/* Ensure key size is small enough. */
|
||||
if (key_size > MaxKeySize) {
|
||||
return ResultKvdbKeyCapacityInsufficient;
|
||||
}
|
||||
|
||||
/* When the cache contains the key being set, Nintendo invalidates the cache. */
|
||||
if (this->cache.Contains(key, key_size)) {
|
||||
this->cache.Invalidate();
|
||||
}
|
||||
|
||||
/* Delete the file, if it exists. Don't check result, since it's okay if it's already deleted. */
|
||||
auto key_path = this->GetPath(key, key_size);
|
||||
std::remove(key_path);
|
||||
|
||||
/* Open the value file. */
|
||||
FILE *fp = fopen(key_path, "wb");
|
||||
if (fp == nullptr) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(fp); };
|
||||
|
||||
/* Write the value file. */
|
||||
if (fwrite(value, value_size, 1, fp) != 1) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
/* Flush the value file. */
|
||||
fflush(fp);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result FileKeyValueStore::Remove(const void *key, size_t key_size) {
|
||||
std::scoped_lock lk(this->lock);
|
||||
|
||||
/* Ensure key size is small enough. */
|
||||
if (key_size > MaxKeySize) {
|
||||
return ResultKvdbKeyCapacityInsufficient;
|
||||
}
|
||||
|
||||
/* When the cache contains the key being set, Nintendo invalidates the cache. */
|
||||
if (this->cache.Contains(key, key_size)) {
|
||||
this->cache.Invalidate();
|
||||
}
|
||||
|
||||
/* Remove the file. */
|
||||
if (std::remove(this->GetPath(key, key_size)) != 0) {
|
||||
R_TRY_CATCH(fsdevGetLastResult()) {
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
return ResultKvdbKeyNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
68
stratosphere/libstratosphere/source/ldr/ldr_ams.c
Normal file
68
stratosphere/libstratosphere/source/ldr/ldr_ams.c
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 "ldr_ams.h"
|
||||
|
||||
static Result _ldrAtmosphereHasLaunchedTitle(Service *srv, bool *out, u64 tid) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 title_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65000;
|
||||
raw->title_id = tid;
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u8 has_launched_title;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out = resp->has_launched_title != 0;
|
||||
} else {
|
||||
rc = 0x666;
|
||||
}
|
||||
} else {
|
||||
rc = 0x555;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result ldrDmntAtmosphereHasLaunchedTitle(bool *out, u64 tid) {
|
||||
return _ldrAtmosphereHasLaunchedTitle(ldrDmntGetServiceSession(), out, tid);
|
||||
}
|
||||
|
||||
Result ldrPmAtmosphereHasLaunchedTitle(bool *out, u64 tid) {
|
||||
return _ldrAtmosphereHasLaunchedTitle(ldrPmGetServiceSession(), out, tid);
|
||||
}
|
19
stratosphere/libstratosphere/source/ldr/ldr_ams.h
Normal file
19
stratosphere/libstratosphere/source/ldr/ldr_ams.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* @file ldr_ams.h
|
||||
* @brief Loader (ldr:*) IPC wrapper for Atmosphere extensions.
|
||||
* @author SciresM
|
||||
* @copyright libnx Authors
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Result ldrPmAtmosphereHasLaunchedTitle(bool *out, u64 tid);
|
||||
Result ldrDmntAtmosphereHasLaunchedTitle(bool *out, u64 tid);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
48
stratosphere/libstratosphere/source/ldr/ldr_pm_api.cpp
Normal file
48
stratosphere/libstratosphere/source/ldr/ldr_pm_api.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/ldr.hpp>
|
||||
#include <stratosphere/ldr/ldr_pm_api.hpp>
|
||||
|
||||
#include "ldr_ams.h"
|
||||
|
||||
namespace sts::ldr::pm {
|
||||
|
||||
/* Information API. */
|
||||
Result CreateProcess(Handle *out, PinId pin_id, u32 flags, Handle reslimit) {
|
||||
return ldrPmCreateProcess(flags, pin_id.value, reslimit, out);
|
||||
}
|
||||
|
||||
Result GetProgramInfo(ProgramInfo *out, const ncm::TitleLocation &loc) {
|
||||
return ldrPmGetProgramInfo(static_cast<u64>(loc.title_id), static_cast<FsStorageId>(loc.storage_id), reinterpret_cast<LoaderProgramInfo *>(out));
|
||||
}
|
||||
|
||||
Result PinTitle(PinId *out, const ncm::TitleLocation &loc) {
|
||||
static_assert(sizeof(*out) == sizeof(u64), "PinId definition!");
|
||||
return ldrPmRegisterTitle(static_cast<u64>(loc.title_id), static_cast<FsStorageId>(loc.storage_id), reinterpret_cast<u64 *>(out));
|
||||
}
|
||||
|
||||
Result UnpinTitle(PinId pin_id) {
|
||||
return ldrPmUnregisterTitle(pin_id.value);
|
||||
}
|
||||
|
||||
Result HasLaunchedTitle(bool *out, ncm::TitleId title_id) {
|
||||
return ldrPmAtmosphereHasLaunchedTitle(out, static_cast<u64>(title_id));
|
||||
}
|
||||
|
||||
}
|
238
stratosphere/libstratosphere/source/map/map_api.cpp
Normal file
238
stratosphere/libstratosphere/source/map/map_api.cpp
Normal file
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* 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 <stratosphere/map.hpp>
|
||||
|
||||
namespace sts::map {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience defines. */
|
||||
constexpr size_t GuardRegionSize = 0x4000;
|
||||
constexpr size_t LocateRetryCount = 0x200;
|
||||
|
||||
/* Deprecated/Modern implementations. */
|
||||
Result LocateMappableSpaceDeprecated(uintptr_t *out_address, size_t size) {
|
||||
MemoryInfo mem_info = {};
|
||||
u32 page_info = 0;
|
||||
uintptr_t cur_base = 0;
|
||||
|
||||
AddressSpaceInfo address_space;
|
||||
R_TRY(GetProcessAddressSpaceInfo(&address_space, CUR_PROCESS_HANDLE));
|
||||
cur_base = address_space.aslr_base;
|
||||
|
||||
do {
|
||||
R_TRY(svcQueryMemory(&mem_info, &page_info, cur_base));
|
||||
|
||||
if (mem_info.type == MemType_Unmapped && mem_info.addr - cur_base + mem_info.size >= size) {
|
||||
*out_address = cur_base;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
const uintptr_t mem_end = mem_info.addr + mem_info.size;
|
||||
if (mem_info.type == MemType_Reserved || mem_end < cur_base || (mem_end >> 31)) {
|
||||
return ResultKernelOutOfMemory;
|
||||
}
|
||||
|
||||
cur_base = mem_end;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
Result LocateMappableSpaceModern(uintptr_t *out_address, size_t size) {
|
||||
MemoryInfo mem_info = {};
|
||||
u32 page_info = 0;
|
||||
uintptr_t cur_base = 0, cur_end = 0;
|
||||
|
||||
AddressSpaceInfo address_space;
|
||||
R_TRY(GetProcessAddressSpaceInfo(&address_space, CUR_PROCESS_HANDLE));
|
||||
cur_base = address_space.aslr_base;
|
||||
cur_end = cur_base + 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.alias_size && (address_space.alias_base <= cur_end - 1 && cur_base <= address_space.alias_end - 1)) {
|
||||
/* If we overlap the alias region, go to the end of the alias region. */
|
||||
if (cur_base == address_space.alias_end) {
|
||||
return ResultKernelOutOfMemory;
|
||||
}
|
||||
cur_base = address_space.alias_end;
|
||||
} else {
|
||||
R_ASSERT(svcQueryMemory(&mem_info, &page_info, cur_base));
|
||||
if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= size) {
|
||||
*out_address = 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.aslr_end) {
|
||||
return ResultKernelOutOfMemory;
|
||||
}
|
||||
}
|
||||
cur_end = cur_base + size;
|
||||
if (cur_base + size <= cur_base) {
|
||||
return ResultKernelOutOfMemory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result MapCodeMemoryInProcessDeprecated(MappedCodeMemory &out_mcm, Handle process_handle, uintptr_t base_address, size_t size) {
|
||||
AddressSpaceInfo address_space;
|
||||
R_TRY(GetProcessAddressSpaceInfo(&address_space, process_handle));
|
||||
|
||||
if (size > address_space.aslr_size) {
|
||||
return ResultRoInsufficientAddressSpace;
|
||||
}
|
||||
|
||||
uintptr_t try_address;
|
||||
for (unsigned int i = 0; i < LocateRetryCount; i++) {
|
||||
try_address = address_space.aslr_base + (rnd::GenerateRandomU64(static_cast<u64>(address_space.aslr_size - size) >> 12) << 12);
|
||||
|
||||
MappedCodeMemory tmp_mcm(process_handle, try_address, base_address, size);
|
||||
R_TRY_CATCH(tmp_mcm.GetResult()) {
|
||||
R_CATCH(ResultKernelInvalidMemoryState) {
|
||||
continue;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
if (!CanAddGuardRegionsInProcess(process_handle, try_address, size)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We're done searching. */
|
||||
out_mcm = std::move(tmp_mcm);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultRoInsufficientAddressSpace;
|
||||
}
|
||||
|
||||
Result MapCodeMemoryInProcessModern(MappedCodeMemory &out_mcm, Handle process_handle, uintptr_t base_address, size_t size) {
|
||||
AddressSpaceInfo address_space;
|
||||
R_TRY(GetProcessAddressSpaceInfo(&address_space, process_handle));
|
||||
|
||||
if (size > address_space.aslr_size) {
|
||||
return ResultRoInsufficientAddressSpace;
|
||||
}
|
||||
|
||||
uintptr_t try_address;
|
||||
for (unsigned int i = 0; i < LocateRetryCount; i++) {
|
||||
while (true) {
|
||||
try_address = address_space.aslr_base + (rnd::GenerateRandomU64(static_cast<u64>(address_space.aslr_size - size) >> 12) << 12);
|
||||
if (address_space.heap_size && (address_space.heap_base <= try_address + size - 1 && try_address <= address_space.heap_end - 1)) {
|
||||
continue;
|
||||
}
|
||||
if (address_space.alias_size && (address_space.alias_base <= try_address + size - 1 && try_address <= address_space.alias_end - 1)) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
MappedCodeMemory tmp_mcm(process_handle, try_address, base_address, size);
|
||||
R_TRY_CATCH(tmp_mcm.GetResult()) {
|
||||
R_CATCH(ResultKernelInvalidMemoryState) {
|
||||
continue;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
if (!CanAddGuardRegionsInProcess(process_handle, try_address, size)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We're done searching. */
|
||||
out_mcm = std::move(tmp_mcm);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultRoInsufficientAddressSpace;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Public API. */
|
||||
Result GetProcessAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h) {
|
||||
/* Clear output. */
|
||||
std::memset(out, 0, sizeof(*out));
|
||||
|
||||
/* Retrieve info from kernel. */
|
||||
R_TRY(svcGetInfo(&out->heap_base, InfoType_HeapRegionAddress, process_h, 0));
|
||||
R_TRY(svcGetInfo(&out->heap_size, InfoType_HeapRegionSize, process_h, 0));
|
||||
R_TRY(svcGetInfo(&out->alias_base, InfoType_AliasRegionAddress, process_h, 0));
|
||||
R_TRY(svcGetInfo(&out->alias_size, InfoType_AliasRegionSize, process_h, 0));
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) {
|
||||
R_TRY(svcGetInfo(&out->aslr_base, InfoType_AslrRegionAddress, process_h, 0));
|
||||
R_TRY(svcGetInfo(&out->aslr_size, InfoType_AslrRegionSize, process_h, 0));
|
||||
} else {
|
||||
/* Auto-detect 32-bit vs 64-bit. */
|
||||
if (out->heap_base < AslrBase64BitDeprecated || out->alias_base < AslrBase64BitDeprecated) {
|
||||
out->aslr_base = AslrBase32Bit;
|
||||
out->aslr_size = AslrSize32Bit;
|
||||
} else {
|
||||
out->aslr_base = AslrBase64BitDeprecated;
|
||||
out->aslr_size = AslrSize64BitDeprecated;
|
||||
}
|
||||
}
|
||||
|
||||
out->heap_end = out->heap_base + out->heap_size;
|
||||
out->alias_end = out->alias_base + out->alias_size;
|
||||
out->aslr_end = out->aslr_base + out->aslr_size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result LocateMappableSpace(uintptr_t *out_address, size_t size) {
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) {
|
||||
return LocateMappableSpaceModern(out_address, size);
|
||||
} else {
|
||||
return LocateMappableSpaceDeprecated(out_address, size);
|
||||
}
|
||||
}
|
||||
|
||||
Result MapCodeMemoryInProcess(MappedCodeMemory &out_mcm, Handle process_handle, uintptr_t base_address, size_t size) {
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) {
|
||||
return MapCodeMemoryInProcessModern(out_mcm, process_handle, base_address, size);
|
||||
} else {
|
||||
return MapCodeMemoryInProcessDeprecated(out_mcm, process_handle, base_address, size);
|
||||
}
|
||||
}
|
||||
|
||||
bool CanAddGuardRegionsInProcess(Handle process_handle, uintptr_t address, size_t size) {
|
||||
MemoryInfo mem_info;
|
||||
u32 page_info;
|
||||
|
||||
/* Nintendo doesn't validate SVC return values at all. */
|
||||
/* TODO: Should we allow these to fail? */
|
||||
R_ASSERT(svcQueryProcessMemory(&mem_info, &page_info, process_handle, address - 1));
|
||||
if (mem_info.type == MemType_Unmapped && address - GuardRegionSize >= mem_info.addr) {
|
||||
R_ASSERT(svcQueryProcessMemory(&mem_info, &page_info, process_handle, address + size));
|
||||
return mem_info.type == MemType_Unmapped && address + size + GuardRegionSize <= mem_info.addr + mem_info.size;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
235
stratosphere/libstratosphere/source/message_queue.cpp
Normal file
235
stratosphere/libstratosphere/source/message_queue.cpp
Normal file
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* 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 <mutex>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
|
||||
void HosMessageQueue::Send(uintptr_t data) {
|
||||
/* Acquire mutex, wait sendable. */
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
|
||||
while (this->IsFull()) {
|
||||
this->cv_not_full.Wait(&this->queue_lock);
|
||||
}
|
||||
|
||||
/* Send, signal. */
|
||||
this->SendInternal(data);
|
||||
this->cv_not_empty.WakeAll();
|
||||
}
|
||||
|
||||
bool HosMessageQueue::TrySend(uintptr_t data) {
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
if (this->IsFull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Send, signal. */
|
||||
this->SendInternal(data);
|
||||
this->cv_not_empty.WakeAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HosMessageQueue::TimedSend(uintptr_t data, u64 timeout) {
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
TimeoutHelper timeout_helper(timeout);
|
||||
|
||||
while (this->IsFull()) {
|
||||
if (timeout_helper.TimedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->cv_not_full.TimedWait(timeout, &this->queue_lock);
|
||||
}
|
||||
|
||||
/* Send, signal. */
|
||||
this->SendInternal(data);
|
||||
this->cv_not_empty.WakeAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HosMessageQueue::SendNext(uintptr_t data) {
|
||||
/* Acquire mutex, wait sendable. */
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
|
||||
while (this->IsFull()) {
|
||||
this->cv_not_full.Wait(&this->queue_lock);
|
||||
}
|
||||
|
||||
/* Send, signal. */
|
||||
this->SendNextInternal(data);
|
||||
this->cv_not_empty.WakeAll();
|
||||
}
|
||||
|
||||
bool HosMessageQueue::TrySendNext(uintptr_t data) {
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
if (this->IsFull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Send, signal. */
|
||||
this->SendNextInternal(data);
|
||||
this->cv_not_empty.WakeAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HosMessageQueue::TimedSendNext(uintptr_t data, u64 timeout) {
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
TimeoutHelper timeout_helper(timeout);
|
||||
|
||||
while (this->IsFull()) {
|
||||
if (timeout_helper.TimedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->cv_not_full.TimedWait(timeout, &this->queue_lock);
|
||||
}
|
||||
|
||||
/* Send, signal. */
|
||||
this->SendNextInternal(data);
|
||||
this->cv_not_empty.WakeAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HosMessageQueue::Receive(uintptr_t *out) {
|
||||
/* Acquire mutex, wait receivable. */
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
|
||||
while (this->IsEmpty()) {
|
||||
this->cv_not_empty.Wait(&this->queue_lock);
|
||||
}
|
||||
|
||||
/* Receive, signal. */
|
||||
*out = this->ReceiveInternal();
|
||||
this->cv_not_full.WakeAll();
|
||||
}
|
||||
bool HosMessageQueue::TryReceive(uintptr_t *out) {
|
||||
/* Acquire mutex, wait receivable. */
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
|
||||
if (this->IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Receive, signal. */
|
||||
*out = this->ReceiveInternal();
|
||||
this->cv_not_full.WakeAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HosMessageQueue::TimedReceive(uintptr_t *out, u64 timeout) {
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
TimeoutHelper timeout_helper(timeout);
|
||||
|
||||
while (this->IsEmpty()) {
|
||||
if (timeout_helper.TimedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->cv_not_empty.TimedWait(timeout, &this->queue_lock);
|
||||
}
|
||||
|
||||
/* Receive, signal. */
|
||||
*out = this->ReceiveInternal();
|
||||
this->cv_not_full.WakeAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HosMessageQueue::Peek(uintptr_t *out) {
|
||||
/* Acquire mutex, wait receivable. */
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
|
||||
while (this->IsEmpty()) {
|
||||
this->cv_not_empty.Wait(&this->queue_lock);
|
||||
}
|
||||
|
||||
/* Peek. */
|
||||
*out = this->PeekInternal();
|
||||
}
|
||||
|
||||
bool HosMessageQueue::TryPeek(uintptr_t *out) {
|
||||
/* Acquire mutex, wait receivable. */
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
|
||||
if (this->IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Peek. */
|
||||
*out = this->PeekInternal();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HosMessageQueue::TimedPeek(uintptr_t *out, u64 timeout) {
|
||||
std::scoped_lock<HosMutex> lock(this->queue_lock);
|
||||
TimeoutHelper timeout_helper(timeout);
|
||||
|
||||
while (this->IsEmpty()) {
|
||||
if (timeout_helper.TimedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->cv_not_empty.TimedWait(timeout, &this->queue_lock);
|
||||
}
|
||||
|
||||
/* Peek. */
|
||||
*out = this->PeekInternal();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HosMessageQueue::SendInternal(uintptr_t data) {
|
||||
/* Ensure we don't corrupt the queue, but this should never happen. */
|
||||
if (this->count >= this->capacity) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Write data to tail of queue. */
|
||||
this->buffer[(this->count++ + this->offset) % this->capacity] = data;
|
||||
}
|
||||
|
||||
void HosMessageQueue::SendNextInternal(uintptr_t data) {
|
||||
/* Ensure we don't corrupt the queue, but this should never happen. */
|
||||
if (this->count >= this->capacity) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Write data to head of queue. */
|
||||
this->offset = (this->offset + this->capacity - 1) % this->capacity;
|
||||
this->buffer[this->offset] = data;
|
||||
this->count++;
|
||||
}
|
||||
|
||||
uintptr_t HosMessageQueue::ReceiveInternal() {
|
||||
/* Ensure we don't corrupt the queue, but this should never happen. */
|
||||
if (this->count == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
uintptr_t data = this->buffer[this->offset];
|
||||
this->offset = (this->offset + 1) % this->capacity;
|
||||
this->count--;
|
||||
return data;
|
||||
}
|
||||
|
||||
uintptr_t HosMessageQueue::PeekInternal() {
|
||||
/* Ensure we don't corrupt the queue, but this should never happen. */
|
||||
if (this->count == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return this->buffer[this->offset];
|
||||
}
|
47
stratosphere/libstratosphere/source/mitm_server.cpp
Normal file
47
stratosphere/libstratosphere/source/mitm_server.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 <mutex>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
static HosMutex g_server_query_mutex;
|
||||
static HosThread g_server_query_manager_thread;
|
||||
static SessionManagerBase *g_server_query_manager = nullptr;
|
||||
|
||||
static void ServerQueryManagerThreadFunc(void *arg) {
|
||||
g_server_query_manager->Process();
|
||||
}
|
||||
|
||||
void RegisterMitmServerQueryHandle(Handle query_h, ServiceObjectHolder &&service) {
|
||||
std::scoped_lock<HosMutex> lock(g_server_query_mutex);
|
||||
|
||||
const bool exists = g_server_query_manager != nullptr;
|
||||
if (!exists) {
|
||||
/* Create a new waitable manager if it doesn't exist already. */
|
||||
static auto s_server_query_manager = WaitableManager(1);
|
||||
g_server_query_manager = &s_server_query_manager;
|
||||
}
|
||||
|
||||
/* Add session to the manager. */
|
||||
g_server_query_manager->AddSession(query_h, std::move(service));
|
||||
|
||||
/* If this is our first time, launch thread. */
|
||||
if (!exists) {
|
||||
R_ASSERT(g_server_query_manager_thread.Initialize(&ServerQueryManagerThreadFunc, nullptr, 0x4000, 27));
|
||||
R_ASSERT(g_server_query_manager_thread.Start());
|
||||
}
|
||||
}
|
142
stratosphere/libstratosphere/source/on_crash.cpp
Normal file
142
stratosphere/libstratosphere/source/on_crash.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* 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 <mutex>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
WEAK sts::ncm::TitleId __stratosphere_title_id = sts::ncm::TitleId::Invalid;
|
||||
|
||||
extern "C" {
|
||||
void WEAK __libstratosphere_exception_handler(AtmosphereFatalErrorContext *ctx);
|
||||
|
||||
/* Redefine abort, so that it triggers these handlers. */
|
||||
void abort();
|
||||
};
|
||||
|
||||
static inline u64 GetPc() {
|
||||
u64 pc;
|
||||
__asm__ __volatile__ ("adr %[pc], ." : [pc]"=&r"(pc) :: );
|
||||
return pc;
|
||||
}
|
||||
|
||||
struct StackFrame {
|
||||
u64 fp;
|
||||
u64 lr;
|
||||
};
|
||||
|
||||
void StratosphereCrashHandler(ThreadExceptionDump *ctx) {
|
||||
AtmosphereFatalErrorContext ams_ctx;
|
||||
/* Convert thread dump to atmosphere dump. */
|
||||
{
|
||||
ams_ctx.magic = AtmosphereFatalErrorMagic;
|
||||
ams_ctx.error_desc = ctx->error_desc;
|
||||
ams_ctx.title_id = static_cast<u64>(__stratosphere_title_id);
|
||||
for (size_t i = 0; i < AtmosphereFatalErrorNumGprs; i++) {
|
||||
ams_ctx.gprs[i] = ctx->cpu_gprs[i].x;
|
||||
}
|
||||
if (ams_ctx.error_desc == DATA_ABORT_ERROR_DESC &&
|
||||
ams_ctx.gprs[2] == STD_ABORT_ADDR_MAGIC &&
|
||||
ams_ctx.gprs[3] == STD_ABORT_VALUE_MAGIC) {
|
||||
/* Detect std::abort(). */
|
||||
ams_ctx.error_desc = STD_ABORT_ERROR_DESC;
|
||||
}
|
||||
|
||||
ams_ctx.fp = ctx->fp.x;
|
||||
ams_ctx.lr = ctx->lr.x;
|
||||
ams_ctx.sp = ctx->sp.x;
|
||||
ams_ctx.pc = ctx->pc.x;
|
||||
ams_ctx.pstate = ctx->pstate;
|
||||
ams_ctx.afsr0 = ctx->afsr0;
|
||||
ams_ctx.afsr1 = ctx->afsr1;
|
||||
ams_ctx.far = ctx->far.x;
|
||||
ams_ctx.report_identifier = armGetSystemTick();
|
||||
/* Grab module base. */
|
||||
{
|
||||
MemoryInfo mem_info;
|
||||
u32 page_info;
|
||||
if (R_SUCCEEDED(svcQueryMemory(&mem_info, &page_info, GetPc()))) {
|
||||
ams_ctx.module_base = mem_info.addr;
|
||||
} else {
|
||||
ams_ctx.module_base = 0;
|
||||
}
|
||||
}
|
||||
ams_ctx.stack_trace_size = 0;
|
||||
u64 cur_fp = ams_ctx.fp;
|
||||
for (size_t i = 0; i < AMS_FATAL_ERROR_MAX_STACKTRACE; i++) {
|
||||
/* Validate current frame. */
|
||||
if (cur_fp == 0 || (cur_fp & 0xF)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Read a new frame. */
|
||||
StackFrame cur_frame;
|
||||
MemoryInfo mem_info;
|
||||
u32 page_info;
|
||||
if (R_SUCCEEDED(svcQueryMemory(&mem_info, &page_info, cur_fp)) && (mem_info.perm & Perm_R) == Perm_R) {
|
||||
std::memcpy(&cur_frame, reinterpret_cast<void *>(cur_fp), sizeof(cur_frame));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Advance to the next frame. */
|
||||
ams_ctx.stack_trace[ams_ctx.stack_trace_size++] = cur_frame.lr;
|
||||
cur_fp = cur_frame.fp;
|
||||
}
|
||||
/* Clear unused parts of stack trace. */
|
||||
for (size_t i = ams_ctx.stack_trace_size; i < AMS_FATAL_ERROR_MAX_STACKTRACE; i++) {
|
||||
ams_ctx.stack_trace[i] = 0;
|
||||
}
|
||||
|
||||
/* Grab up to 0x100 of stack. */
|
||||
{
|
||||
MemoryInfo mem_info;
|
||||
u32 page_info;
|
||||
if (R_SUCCEEDED(svcQueryMemory(&mem_info, &page_info, ams_ctx.sp)) && (mem_info.perm & Perm_R) == Perm_R) {
|
||||
size_t copy_size = std::min(static_cast<size_t>(AMS_FATAL_ERROR_MAX_STACKDUMP), static_cast<size_t>(mem_info.addr + mem_info.size - ams_ctx.sp));
|
||||
ams_ctx.stack_dump_size = copy_size;
|
||||
std::memcpy(ams_ctx.stack_dump, reinterpret_cast<void *>(ams_ctx.sp), copy_size);
|
||||
} else {
|
||||
ams_ctx.stack_dump_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Just call the user exception handler. */
|
||||
__libstratosphere_exception_handler(&ams_ctx);
|
||||
}
|
||||
|
||||
/* Default exception handler behavior. */
|
||||
void WEAK __libstratosphere_exception_handler(AtmosphereFatalErrorContext *ctx) {
|
||||
R_ASSERT(bpcAmsInitialize());
|
||||
R_ASSERT(bpcAmsRebootToFatalError(ctx));
|
||||
bpcAmsExit();
|
||||
while (1) { }
|
||||
}
|
||||
|
||||
/* Custom abort handler, so that std::abort will trigger these. */
|
||||
void abort() {
|
||||
/* Just perform a data abort. */
|
||||
register u64 addr __asm__("x2") = STD_ABORT_ADDR_MAGIC;
|
||||
register u64 val __asm__("x3") = STD_ABORT_VALUE_MAGIC;
|
||||
while (true) {
|
||||
__asm__ __volatile__ (
|
||||
"str %[val], [%[addr]]"
|
||||
:
|
||||
: [val]"r"(val), [addr]"r"(addr)
|
||||
);
|
||||
}
|
||||
}
|
256
stratosphere/libstratosphere/source/patcher/patcher_api.cpp
Normal file
256
stratosphere/libstratosphere/source/patcher/patcher_api.cpp
Normal file
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* 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 <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <dirent.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/patcher.hpp>
|
||||
|
||||
/* IPS Patching adapted from Luma3DS (https://github.com/AuroraWright/Luma3DS/blob/master/sysmodules/loader/source/patcher.c) */
|
||||
|
||||
namespace sts::patcher {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr const char IpsHeadMagic[5] = {'P', 'A', 'T', 'C', 'H'};
|
||||
constexpr const char IpsTailMagic[3] = {'E', 'O', 'F'};
|
||||
constexpr const char Ips32HeadMagic[5] = {'I', 'P', 'S', '3', '2'};
|
||||
constexpr const char Ips32TailMagic[4] = {'E', 'E', 'O', 'F'};
|
||||
constexpr const char *IpsFileExtension = ".ips";
|
||||
constexpr size_t IpsFileExtensionLength = std::strlen(IpsFileExtension);
|
||||
constexpr size_t ModuleIpsPatchLength = 2 * sizeof(ro::ModuleId) + IpsFileExtensionLength;
|
||||
|
||||
/* Helpers. */
|
||||
inline u8 ConvertHexNybble(const char nybble) {
|
||||
if ('0' <= nybble && nybble <= '9') {
|
||||
return nybble - '0';
|
||||
} else if ('a' <= nybble && nybble <= 'f') {
|
||||
return nybble - 'a' + 0xa;
|
||||
} else {
|
||||
return nybble - 'A' + 0xA;
|
||||
}
|
||||
}
|
||||
|
||||
bool ParseModuleIdFromPath(ro::ModuleId *out_module_id, const char *name, size_t name_len, size_t extension_len) {
|
||||
/* Validate name is hex module id. */
|
||||
for (unsigned int i = 0; i < name_len - extension_len; i++) {
|
||||
if (std::isxdigit(name[i]) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read module id from name. */
|
||||
std::memset(out_module_id, 0, sizeof(*out_module_id));
|
||||
for (unsigned int name_ofs = 0, id_ofs = 0; name_ofs < name_len - extension_len && id_ofs < sizeof(*out_module_id); id_ofs++) {
|
||||
out_module_id->build_id[id_ofs] |= ConvertHexNybble(name[name_ofs++]) << 4;
|
||||
out_module_id->build_id[id_ofs] |= ConvertHexNybble(name[name_ofs++]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MatchesModuleId(const char *name, size_t name_len, size_t extension_len, const ro::ModuleId *module_id) {
|
||||
/* Get module id. */
|
||||
ro::ModuleId module_id_from_name;
|
||||
if (!ParseModuleIdFromPath(&module_id_from_name, name, name_len, extension_len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::memcmp(&module_id_from_name, module_id, sizeof(*module_id)) == 0;
|
||||
}
|
||||
|
||||
inline bool IsIpsTail(bool is_ips32, u8 *buffer) {
|
||||
if (is_ips32) {
|
||||
return std::memcmp(buffer, Ips32TailMagic, sizeof(Ips32TailMagic)) == 0;
|
||||
} else {
|
||||
return std::memcmp(buffer, IpsTailMagic, sizeof(IpsTailMagic)) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline u32 GetIpsPatchOffset(bool is_ips32, u8 *buffer) {
|
||||
if (is_ips32) {
|
||||
return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | (buffer[3]);
|
||||
} else {
|
||||
return (buffer[0] << 16) | (buffer[1] << 8) | (buffer[2]);
|
||||
}
|
||||
}
|
||||
|
||||
inline u32 GetIpsPatchSize(bool is_ips32, u8 *buffer) {
|
||||
return (buffer[0] << 8) | (buffer[1]);
|
||||
}
|
||||
|
||||
void ApplyIpsPatch(u8 *mapped_module, size_t mapped_size, size_t protected_size, size_t offset, bool is_ips32, FILE *f_ips) {
|
||||
/* Validate offset/protected size. */
|
||||
if (offset > protected_size) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
u8 buffer[sizeof(Ips32TailMagic)];
|
||||
while (true) {
|
||||
if (fread(buffer, is_ips32 ? sizeof(Ips32TailMagic) : sizeof(IpsTailMagic), 1, f_ips) != 1) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (IsIpsTail(is_ips32, buffer)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Offset of patch. */
|
||||
u32 patch_offset = GetIpsPatchOffset(is_ips32, buffer);
|
||||
|
||||
/* Size of patch. */
|
||||
if (fread(buffer, 2, 1, f_ips) != 1) {
|
||||
std::abort();
|
||||
}
|
||||
u32 patch_size = GetIpsPatchSize(is_ips32, buffer);
|
||||
|
||||
/* Check for RLE encoding. */
|
||||
if (patch_size == 0) {
|
||||
/* Size of RLE. */
|
||||
if (fread(buffer, 2, 1, f_ips) != 1) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
u32 rle_size = (buffer[0] << 8) | (buffer[1]);
|
||||
|
||||
/* Value for RLE. */
|
||||
if (fread(buffer, 1, 1, f_ips) != 1) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Ensure we don't write to protected region. */
|
||||
if (patch_offset < protected_size) {
|
||||
if (patch_offset + rle_size > protected_size) {
|
||||
const u32 diff = protected_size - patch_offset;
|
||||
patch_offset += diff;
|
||||
rle_size -= diff;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust offset, if relevant. */
|
||||
patch_offset -= offset;
|
||||
|
||||
/* Apply patch. */
|
||||
if (patch_offset + rle_size > mapped_size) {
|
||||
rle_size = mapped_size - patch_offset;
|
||||
}
|
||||
std::memset(mapped_module + patch_offset, buffer[0], rle_size);
|
||||
} else {
|
||||
/* Ensure we don't write to protected region. */
|
||||
if (patch_offset < protected_size) {
|
||||
if (patch_offset + patch_size > protected_size) {
|
||||
const u32 diff = protected_size - patch_offset;
|
||||
patch_offset += diff;
|
||||
patch_size -= diff;
|
||||
fseek(f_ips, diff, SEEK_CUR);
|
||||
} else {
|
||||
fseek(f_ips, patch_size, SEEK_CUR);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust offset, if relevant. */
|
||||
patch_offset -= offset;
|
||||
|
||||
/* Apply patch. */
|
||||
u32 read_size = patch_size;
|
||||
if (patch_offset + read_size > mapped_size) {
|
||||
read_size = mapped_size - patch_offset;
|
||||
}
|
||||
if (fread(mapped_module + patch_offset, read_size, 1, f_ips) != 1) {
|
||||
std::abort();
|
||||
}
|
||||
if (patch_size > read_size) {
|
||||
fseek(f_ips, patch_size - read_size, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LocateAndApplyIpsPatchesToModule(const char *patch_dir_name, size_t protected_size, size_t offset, const ro::ModuleId *module_id, u8 *mapped_module, size_t mapped_size) {
|
||||
/* Inspect all patches from /atmosphere/<patch_dir>/<*>/<*>.ips */
|
||||
char path[FS_MAX_PATH+1] = {0};
|
||||
std::snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/%s", patch_dir_name);
|
||||
|
||||
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 (std::strcmp(pdir_ent->d_name, ".") == 0 || std::strcmp(pdir_ent->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/%s/%s", patch_dir_name, 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 (std::strcmp(ent->d_name, ".") == 0 || std::strcmp(ent->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t name_len = strlen(ent->d_name);
|
||||
if (!(IpsFileExtensionLength < name_len && name_len <= ModuleIpsPatchLength)) {
|
||||
continue;
|
||||
}
|
||||
if ((name_len & 1) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (std::strcmp(ent->d_name + name_len - IpsFileExtensionLength, IpsFileExtension) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (!MatchesModuleId(ent->d_name, name_len, IpsFileExtensionLength, module_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/%s/%s/%s", patch_dir_name, pdir_ent->d_name, ent->d_name);
|
||||
FILE *f_ips = fopen(path, "rb");
|
||||
if (f_ips == NULL) {
|
||||
continue;
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(f_ips); };
|
||||
|
||||
u8 header[5];
|
||||
if (fread(header, 5, 1, f_ips) == 1) {
|
||||
if (std::memcmp(header, IpsHeadMagic, 5) == 0) {
|
||||
ApplyIpsPatch(mapped_module, mapped_size, protected_size, offset, false, f_ips);
|
||||
} else if (std::memcmp(header, Ips32HeadMagic, 5) == 0) {
|
||||
ApplyIpsPatch(mapped_module, mapped_size, protected_size, offset, true, f_ips);
|
||||
}
|
||||
}
|
||||
fclose(f_ips);
|
||||
}
|
||||
closedir(patch_dir);
|
||||
}
|
||||
}
|
||||
closedir(patches_dir);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
96
stratosphere/libstratosphere/source/pm/pm_ams.c
Normal file
96
stratosphere/libstratosphere/source/pm/pm_ams.c
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 "pm_ams.h"
|
||||
|
||||
Result pminfoAtmosphereGetProcessId(u64 *out_pid, u64 tid) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = pminfoGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 title_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65000;
|
||||
raw->title_id = tid;
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u64 pid;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out_pid = resp->pid;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result pminfoAtmosphereHasLaunchedTitle(bool *out, u64 tid) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = pminfoGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 title_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65001;
|
||||
raw->title_id = tid;
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u8 has_launched_title;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out = resp->has_launched_title != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
19
stratosphere/libstratosphere/source/pm/pm_ams.h
Normal file
19
stratosphere/libstratosphere/source/pm/pm_ams.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* @file pm_ams.h
|
||||
* @brief Process Manager (pm:*) IPC wrapper for Atmosphere extensions.
|
||||
* @author SciresM
|
||||
* @copyright libnx Authors
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Result pminfoAtmosphereGetProcessId(u64 *out_pid, u64 tid);
|
||||
Result pminfoAtmosphereHasLaunchedTitle(bool *out, u64 tid);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
34
stratosphere/libstratosphere/source/pm/pm_boot_mode_api.cpp
Normal file
34
stratosphere/libstratosphere/source/pm/pm_boot_mode_api.cpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 <stratosphere/pm.hpp>
|
||||
|
||||
namespace sts::pm::bm {
|
||||
|
||||
/* Boot Mode API. */
|
||||
/* Both functions should be weakly linked, so that they can be overridden by sts::boot2 as needed. */
|
||||
BootMode WEAK GetBootMode() {
|
||||
PmBootMode boot_mode = PmBootMode_Normal;
|
||||
R_ASSERT(pmbmGetBootMode(&boot_mode));
|
||||
return static_cast<BootMode>(boot_mode);
|
||||
}
|
||||
|
||||
void WEAK SetMaintenanceBoot() {
|
||||
R_ASSERT(pmbmSetMaintenanceBoot());
|
||||
}
|
||||
|
||||
}
|
70
stratosphere/libstratosphere/source/pm/pm_info_api.cpp
Normal file
70
stratosphere/libstratosphere/source/pm/pm_info_api.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/pm.hpp>
|
||||
|
||||
#include "pm_ams.h"
|
||||
|
||||
namespace sts::pm::info {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Global lock. */
|
||||
HosMutex g_info_lock;
|
||||
std::set<u64> g_cached_launched_titles;
|
||||
|
||||
}
|
||||
|
||||
/* Information API. */
|
||||
Result GetTitleId(ncm::TitleId *out_title_id, u64 process_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_info_lock);
|
||||
|
||||
return pminfoGetTitleId(reinterpret_cast<u64 *>(out_title_id), process_id);
|
||||
}
|
||||
|
||||
Result GetProcessId(u64 *out_process_id, ncm::TitleId title_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_info_lock);
|
||||
|
||||
return pminfoAtmosphereGetProcessId(out_process_id, static_cast<u64>(title_id));
|
||||
}
|
||||
|
||||
Result WEAK HasLaunchedTitle(bool *out, ncm::TitleId title_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_info_lock);
|
||||
|
||||
if (g_cached_launched_titles.find(static_cast<u64>(title_id)) != g_cached_launched_titles.end()) {
|
||||
*out = true;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
bool has_launched = false;
|
||||
R_TRY(pminfoAtmosphereHasLaunchedTitle(&has_launched, static_cast<u64>(title_id)));
|
||||
if (has_launched) {
|
||||
g_cached_launched_titles.insert(static_cast<u64>(title_id));
|
||||
}
|
||||
|
||||
*out = has_launched;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
bool HasLaunchedTitle(ncm::TitleId title_id) {
|
||||
bool has_launched = false;
|
||||
R_ASSERT(HasLaunchedTitle(&has_launched, title_id));
|
||||
return has_launched;
|
||||
}
|
||||
|
||||
}
|
28
stratosphere/libstratosphere/source/pm/pm_shell_api.cpp
Normal file
28
stratosphere/libstratosphere/source/pm/pm_shell_api.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 <stratosphere/pm.hpp>
|
||||
|
||||
namespace sts::pm::shell {
|
||||
|
||||
/* Shell API. */
|
||||
Result WEAK LaunchTitle(u64 *out_process_id, const ncm::TitleLocation &loc, u32 launch_flags) {
|
||||
return pmshellLaunchProcess(launch_flags, static_cast<u64>(loc.title_id), loc.storage_id, out_process_id);
|
||||
}
|
||||
|
||||
}
|
132
stratosphere/libstratosphere/source/rnd/rnd_api.cpp
Normal file
132
stratosphere/libstratosphere/source/rnd/rnd_api.cpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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 <random>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/rnd.hpp>
|
||||
|
||||
namespace sts::rnd {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Generator type. */
|
||||
/* Official HOS uses TinyMT. This is high effort. Let's just use XorShift. */
|
||||
/* https://en.wikipedia.org/wiki/Xorshift */
|
||||
class XorShiftGenerator {
|
||||
public:
|
||||
using ResultType = uint32_t;
|
||||
using result_type = ResultType;
|
||||
static constexpr ResultType (min)() { return std::numeric_limits<ResultType>::min(); }
|
||||
static constexpr ResultType (max)() { return std::numeric_limits<ResultType>::max(); }
|
||||
static constexpr size_t SeedSize = 4;
|
||||
private:
|
||||
ResultType random_state[SeedSize];
|
||||
public:
|
||||
|
||||
explicit XorShiftGenerator() {
|
||||
/* Seed using process entropy. */
|
||||
u64 val = 0;
|
||||
for (size_t i = 0; i < SeedSize; i++) {
|
||||
R_ASSERT(svcGetInfo(&val, InfoType_RandomEntropy, INVALID_HANDLE, i));
|
||||
this->random_state[i] = ResultType(val);
|
||||
}
|
||||
}
|
||||
|
||||
explicit XorShiftGenerator(std::random_device &rd) {
|
||||
for (size_t i = 0; i < SeedSize; i++) {
|
||||
this->random_state[i] = ResultType(rd());
|
||||
}
|
||||
}
|
||||
|
||||
ResultType operator()() {
|
||||
ResultType s, t = this->random_state[3];
|
||||
t ^= t << 11;
|
||||
t ^= t >> 8;
|
||||
this->random_state[3] = this->random_state[2]; this->random_state[2] = this->random_state[1]; this->random_state[1] = (s = this->random_state[0]);
|
||||
t ^= s;
|
||||
t ^= s >> 19;
|
||||
this->random_state[0] = t;
|
||||
return t;
|
||||
}
|
||||
|
||||
void discard(size_t n) {
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
operator()();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Generator global. */
|
||||
XorShiftGenerator g_rnd_generator;
|
||||
|
||||
/* Templated helpers. */
|
||||
template<typename T>
|
||||
T GenerateRandom(T max = std::numeric_limits<T>::max()) {
|
||||
std::uniform_int_distribution<T> rnd(std::numeric_limits<T>::min(), max);
|
||||
return rnd(g_rnd_generator);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GenerateRandomBytes(void* _out, size_t size) {
|
||||
uintptr_t out = reinterpret_cast<uintptr_t>(_out);
|
||||
uintptr_t end = out + size;
|
||||
|
||||
/* Force alignment. */
|
||||
if (out % sizeof(u16) && out < end) {
|
||||
*reinterpret_cast<u8 *>(out) = GenerateRandom<u8>();
|
||||
out += sizeof(u8);
|
||||
}
|
||||
if (out % sizeof(u32) && out < end) {
|
||||
*reinterpret_cast<u16 *>(out) = GenerateRandom<u16>();
|
||||
out += sizeof(u16);
|
||||
}
|
||||
if (out % sizeof(u64) && out < end) {
|
||||
*reinterpret_cast<u32 *>(out) = GenerateRandom<u32>();
|
||||
out += sizeof(u32);
|
||||
}
|
||||
|
||||
/* Perform as many aligned writes as possible. */
|
||||
while (out + sizeof(u64) <= end) {
|
||||
*reinterpret_cast<u64 *>(out) = GenerateRandom<u64>();
|
||||
out += sizeof(u64);
|
||||
}
|
||||
|
||||
/* Do remainder writes. */
|
||||
if (out + sizeof(u32) <= end) {
|
||||
*reinterpret_cast<u32 *>(out) = GenerateRandom<u32>();
|
||||
out += sizeof(u32);
|
||||
}
|
||||
if (out + sizeof(u16) <= end) {
|
||||
*reinterpret_cast<u16 *>(out) = GenerateRandom<u16>();
|
||||
out += sizeof(u16);
|
||||
}
|
||||
if (out + sizeof(u8) <= end) {
|
||||
*reinterpret_cast<u8 *>(out) = GenerateRandom<u8>();
|
||||
out += sizeof(u8);
|
||||
}
|
||||
}
|
||||
|
||||
u32 GenerateRandomU32(u32 max) {
|
||||
return GenerateRandom<u32>(max);
|
||||
}
|
||||
|
||||
u64 GenerateRandomU64(u64 max) {
|
||||
return GenerateRandom<u64>(max);
|
||||
}
|
||||
|
||||
}
|
376
stratosphere/libstratosphere/source/sm/sm_ams.c
Normal file
376
stratosphere/libstratosphere/source/sm/sm_ams.c
Normal file
|
@ -0,0 +1,376 @@
|
|||
/*
|
||||
* 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 <switch/arm/atomics.h>
|
||||
#include "sm_ams.h"
|
||||
|
||||
static Service g_smMitmSrv;
|
||||
static u64 g_mitmRefCnt;
|
||||
|
||||
Result smAtmosphereHasService(bool *out, const char *name) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = smGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 service_name;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65100;
|
||||
raw->service_name = smEncodeName(name);
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u8 has_service;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out = resp->has_service != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result smAtmosphereWaitService(const char *name) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = smGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 service_name;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65101;
|
||||
raw->service_name = smEncodeName(name);
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result smAtmosphereHasMitm(bool *out, const char *name) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = smGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 service_name;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65004;
|
||||
raw->service_name = smEncodeName(name);
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u8 has_mitm;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out = resp->has_mitm != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result smAtmosphereWaitMitm(const char *name) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = smGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 service_name;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65005;
|
||||
raw->service_name = smEncodeName(name);
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result smAtmosphereMitmInitialize(void) {
|
||||
atomicIncrement64(&g_mitmRefCnt);
|
||||
|
||||
if (serviceIsActive(&g_smMitmSrv))
|
||||
return 0;
|
||||
|
||||
Handle sm_handle;
|
||||
Result rc = svcConnectToNamedPort(&sm_handle, "sm:");
|
||||
while (R_VALUE(rc) == KERNELRESULT(NotFound)) {
|
||||
svcSleepThread(50000000ul);
|
||||
rc = svcConnectToNamedPort(&sm_handle, "sm:");
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
serviceCreate(&g_smMitmSrv, sm_handle);
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcSendPid(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 zero;
|
||||
u64 reserved[2];
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_smMitmSrv, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 0;
|
||||
raw->zero = 0;
|
||||
|
||||
rc = serviceIpcDispatch(&g_smMitmSrv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
serviceIpcParse(&g_smMitmSrv, &r, sizeof(*resp));
|
||||
|
||||
resp = r.Raw;
|
||||
rc = resp->result;
|
||||
}
|
||||
}
|
||||
|
||||
if (R_FAILED(rc))
|
||||
smAtmosphereMitmExit();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void smAtmosphereMitmExit(void) {
|
||||
if (atomicDecrement64(&g_mitmRefCnt) == 0) {
|
||||
serviceClose(&g_smMitmSrv);
|
||||
}
|
||||
}
|
||||
|
||||
Result smAtmosphereMitmInstall(Handle *handle_out, Handle *query_out, const char *name) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = &g_smMitmSrv;
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 service_name;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65000;
|
||||
raw->service_name = smEncodeName(name);
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*handle_out = r.Handles[0];
|
||||
*query_out = r.Handles[1];
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result smAtmosphereMitmUninstall(const char *name) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = &g_smMitmSrv;
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 service_name;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65001;
|
||||
raw->service_name = smEncodeName(name);
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result smAtmosphereMitmDeclareFuture(const char *name) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = &g_smMitmSrv;
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 service_name;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65006;
|
||||
raw->service_name = smEncodeName(name);
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result smAtmosphereMitmAcknowledgeSession(Service *srv_out, u64 *pid_out, u64 *tid_out, const char *name) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = &g_smMitmSrv;
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 service_name;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65003;
|
||||
raw->service_name = smEncodeName(name);
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u64 pid;
|
||||
u64 tid;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*pid_out = resp->pid;
|
||||
*tid_out = resp->tid;
|
||||
serviceCreate(srv_out, r.Handles[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
29
stratosphere/libstratosphere/source/sm/sm_ams.h
Normal file
29
stratosphere/libstratosphere/source/sm/sm_ams.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* @file sm_ams.h
|
||||
* @brief Service manager (sm) IPC wrapper for Atmosphere extensions.
|
||||
* @author SciresM
|
||||
* @copyright libnx Authors
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Result smAtmosphereHasService(bool *out, const char *name);
|
||||
Result smAtmosphereWaitService(const char *name);
|
||||
Result smAtmosphereHasMitm(bool *out, const char *name);
|
||||
Result smAtmosphereWaitMitm(const char *name);
|
||||
|
||||
Result smAtmosphereMitmInitialize(void);
|
||||
void smAtmosphereMitmExit(void);
|
||||
|
||||
Result smAtmosphereMitmInstall(Handle *handle_out, Handle *query_out, const char *name);
|
||||
Result smAtmosphereMitmUninstall(const char *name);
|
||||
Result smAtmosphereMitmDeclareFuture(const char *name);
|
||||
Result smAtmosphereMitmAcknowledgeSession(Service *srv_out, u64 *pid_out, u64 *tid_out, const char *name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
58
stratosphere/libstratosphere/source/sm/sm_api.cpp
Normal file
58
stratosphere/libstratosphere/source/sm/sm_api.cpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 <stratosphere/sm.hpp>
|
||||
|
||||
#include "sm_ams.h"
|
||||
#include "sm_utils.hpp"
|
||||
|
||||
namespace sts::sm {
|
||||
|
||||
/* Ordinary SM API. */
|
||||
Result GetService(Service *out, ServiceName name) {
|
||||
return impl::DoWithUserSession([&]() {
|
||||
return smGetService(out, name.name);
|
||||
});
|
||||
}
|
||||
|
||||
Result RegisterService(Handle *out, ServiceName name, size_t max_sessions, bool is_light) {
|
||||
return impl::DoWithUserSession([&]() {
|
||||
return smRegisterService(out, name.name, is_light, static_cast<int>(max_sessions));
|
||||
});
|
||||
}
|
||||
|
||||
Result UnregisterService(ServiceName name) {
|
||||
return impl::DoWithUserSession([&]() {
|
||||
return smUnregisterService(name.name);
|
||||
});
|
||||
}
|
||||
|
||||
/* Atmosphere extensions. */
|
||||
Result HasService(bool *out, ServiceName name) {
|
||||
return impl::DoWithUserSession([&]() {
|
||||
return smAtmosphereHasService(out, name.name);
|
||||
});
|
||||
}
|
||||
|
||||
Result WaitService(ServiceName name) {
|
||||
return impl::DoWithUserSession([&]() {
|
||||
return smAtmosphereWaitService(name.name);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
44
stratosphere/libstratosphere/source/sm/sm_manager_api.cpp
Normal file
44
stratosphere/libstratosphere/source/sm/sm_manager_api.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 <stratosphere/sm.hpp>
|
||||
#include <stratosphere/sm/sm_manager_api.hpp>
|
||||
|
||||
#include "smm_ams.h"
|
||||
|
||||
namespace sts::sm::manager {
|
||||
|
||||
/* Manager API. */
|
||||
Result RegisterProcess(u64 process_id, ncm::TitleId title_id, const void *acid, size_t acid_size, const void *aci, size_t aci_size) {
|
||||
return smManagerAtmosphereRegisterProcess(process_id, static_cast<u64>(title_id), acid, acid_size, aci, aci_size);
|
||||
}
|
||||
|
||||
Result UnregisterProcess(u64 process_id) {
|
||||
return smManagerUnregisterProcess(process_id);
|
||||
}
|
||||
|
||||
/* Atmosphere extensions. */
|
||||
Result EndInitialDefers() {
|
||||
return smManagerAtmosphereEndInitialDefers();
|
||||
}
|
||||
|
||||
Result HasMitm(bool *out, ServiceName name) {
|
||||
return smManagerAtmosphereHasMitm(out, name.name);
|
||||
}
|
||||
|
||||
}
|
63
stratosphere/libstratosphere/source/sm/sm_mitm_api.cpp
Normal file
63
stratosphere/libstratosphere/source/sm/sm_mitm_api.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 <stratosphere/sm.hpp>
|
||||
|
||||
#include "sm_ams.h"
|
||||
#include "sm_utils.hpp"
|
||||
|
||||
namespace sts::sm::mitm {
|
||||
|
||||
/* Mitm API. */
|
||||
Result InstallMitm(Handle *out_port, Handle *out_query, ServiceName name) {
|
||||
return impl::DoWithMitmSession([&]() {
|
||||
return smAtmosphereMitmInstall(out_port, out_query, name.name);
|
||||
});
|
||||
}
|
||||
|
||||
Result UninstallMitm(ServiceName name) {
|
||||
return impl::DoWithMitmSession([&]() {
|
||||
return smAtmosphereMitmUninstall(name.name);
|
||||
});
|
||||
}
|
||||
|
||||
Result DeclareFutureMitm(ServiceName name) {
|
||||
return impl::DoWithMitmSession([&]() {
|
||||
return smAtmosphereMitmDeclareFuture(name.name);
|
||||
});
|
||||
}
|
||||
|
||||
Result AcknowledgeSession(Service *out_service, u64 *out_pid, ncm::TitleId *out_tid, ServiceName name) {
|
||||
return impl::DoWithMitmSession([&]() {
|
||||
return smAtmosphereMitmAcknowledgeSession(out_service, out_pid, &out_tid->value, name.name);
|
||||
});
|
||||
}
|
||||
|
||||
Result HasMitm(bool *out, ServiceName name) {
|
||||
return impl::DoWithUserSession([&]() {
|
||||
return smAtmosphereHasMitm(out, name.name);
|
||||
});
|
||||
}
|
||||
|
||||
Result WaitMitm(ServiceName name) {
|
||||
return impl::DoWithUserSession([&]() {
|
||||
return smAtmosphereWaitMitm(name.name);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
38
stratosphere/libstratosphere/source/sm/sm_utils.cpp
Normal file
38
stratosphere/libstratosphere/source/sm/sm_utils.cpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 "sm_utils.hpp"
|
||||
|
||||
namespace sts::sm::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Globals. */
|
||||
HosRecursiveMutex g_user_session_mutex;
|
||||
HosRecursiveMutex g_mitm_session_mutex;
|
||||
|
||||
}
|
||||
|
||||
/* Utilities. */
|
||||
HosRecursiveMutex &GetUserSessionMutex() {
|
||||
return g_user_session_mutex;
|
||||
}
|
||||
|
||||
HosRecursiveMutex &GetMitmSessionMutex() {
|
||||
return g_mitm_session_mutex;
|
||||
}
|
||||
|
||||
}
|
53
stratosphere/libstratosphere/source/sm/sm_utils.hpp
Normal file
53
stratosphere/libstratosphere/source/sm/sm_utils.hpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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/sm.hpp>
|
||||
|
||||
#include "sm_ams.h"
|
||||
|
||||
namespace sts::sm::impl {
|
||||
|
||||
/* Utilities. */
|
||||
HosRecursiveMutex &GetUserSessionMutex();
|
||||
HosRecursiveMutex &GetMitmSessionMutex();
|
||||
|
||||
template<typename F>
|
||||
Result DoWithUserSession(F f) {
|
||||
std::scoped_lock<HosRecursiveMutex &> lk(GetUserSessionMutex());
|
||||
{
|
||||
R_ASSERT(smInitialize());
|
||||
ON_SCOPE_EXIT { smExit(); };
|
||||
|
||||
return f();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
Result DoWithMitmSession(F f) {
|
||||
std::scoped_lock<HosRecursiveMutex &> lk(GetMitmSessionMutex());
|
||||
{
|
||||
R_ASSERT(smAtmosphereMitmInitialize());
|
||||
ON_SCOPE_EXIT { smAtmosphereMitmExit(); };
|
||||
|
||||
return f();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
129
stratosphere/libstratosphere/source/sm/smm_ams.c
Normal file
129
stratosphere/libstratosphere/source/sm/smm_ams.c
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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 "smm_ams.h"
|
||||
|
||||
Result smManagerAtmosphereEndInitialDefers(void) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = smManagerGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65000;
|
||||
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
Result smManagerAtmosphereRegisterProcess(u64 pid, u64 tid, const void *acid_sac, size_t acid_sac_size, const void *aci_sac, size_t aci_sac_size) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendBuffer(&c, acid_sac, acid_sac_size, BufferType_Normal);
|
||||
ipcAddSendBuffer(&c, aci_sac, aci_sac_size, BufferType_Normal);
|
||||
Service *srv = smManagerGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 pid;
|
||||
u64 tid;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65002;
|
||||
raw->pid = pid;
|
||||
raw->tid = tid;
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result smManagerAtmosphereHasMitm(bool *out, const char* name) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = smManagerGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 service_name;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65001;
|
||||
raw->service_name = smEncodeName(name);
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u8 has_mitm;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out = resp->has_mitm != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
20
stratosphere/libstratosphere/source/sm/smm_ams.h
Normal file
20
stratosphere/libstratosphere/source/sm/smm_ams.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* @file smm_ams.h
|
||||
* @brief Service manager manager (sm:m) IPC wrapper for Atmosphere extensions.
|
||||
* @author SciresM
|
||||
* @copyright libnx Authors
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Result smManagerAtmosphereEndInitialDefers(void);
|
||||
Result smManagerAtmosphereRegisterProcess(u64 pid, u64 tid, const void *acid_sac, size_t acid_sac_size, const void *aci_sac, size_t aci_sac_size);
|
||||
Result smManagerAtmosphereHasMitm(bool *out, const char* name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
312
stratosphere/libstratosphere/source/spl/smc/spl_smc.cpp
Normal file
312
stratosphere/libstratosphere/source/spl/smc/spl_smc.cpp
Normal file
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* 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 <stratosphere/spl/smc/spl_smc.hpp>
|
||||
|
||||
namespace sts::spl::smc {
|
||||
|
||||
Result SetConfig(SplConfigItem which, const u64 *value, size_t num_qwords) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::SetConfig);
|
||||
args.X[1] = which;
|
||||
args.X[2] = 0;
|
||||
for (size_t i = 0; i < std::min(size_t(4), num_qwords); i++) {
|
||||
args.X[3 + i] = value[i];
|
||||
}
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result GetConfig(u64 *out, size_t num_qwords, SplConfigItem which) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::GetConfig);
|
||||
args.X[1] = which;
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
for (size_t i = 0; i < std::min(size_t(4), num_qwords); i++) {
|
||||
out[i] = args.X[1 + i];
|
||||
}
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result CheckStatus(Result *out, AsyncOperationKey op) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::CheckStatus);
|
||||
args.X[1] = op.value;
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
*out = static_cast<Result>(args.X[1]);
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result GetResult(Result *out, void *out_buf, size_t out_buf_size, AsyncOperationKey op) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::GetResult);
|
||||
args.X[1] = op.value;
|
||||
args.X[2] = reinterpret_cast<u64>(out_buf);
|
||||
args.X[3] = out_buf_size;
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
*out = static_cast<Result>(args.X[1]);
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result ExpMod(AsyncOperationKey *out_op, const void *base, const void *exp, size_t exp_size, const void *mod) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::ExpMod);
|
||||
args.X[1] = reinterpret_cast<u64>(base);
|
||||
args.X[2] = reinterpret_cast<u64>(exp);
|
||||
args.X[3] = reinterpret_cast<u64>(mod);
|
||||
args.X[4] = exp_size;
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
out_op->value = args.X[1];
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result GenerateRandomBytes(void *out, size_t size) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::GenerateRandomBytes);
|
||||
args.X[1] = size;
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
if (args.X[0] == static_cast<u64>(Result::Success) && (size <= sizeof(args) - sizeof(args.X[0]))) {
|
||||
std::memcpy(out, &args.X[1], size);
|
||||
}
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result GenerateAesKek(AccessKey *out, const KeySource &source, u32 generation, u32 option) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::GenerateAesKek);
|
||||
args.X[1] = source.data64[0];
|
||||
args.X[2] = source.data64[1];
|
||||
args.X[3] = generation;
|
||||
args.X[4] = option;
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
out->data64[0] = args.X[1];
|
||||
out->data64[1] = args.X[2];
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result LoadAesKey(u32 keyslot, const AccessKey &access_key, const KeySource &source) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::LoadAesKey);
|
||||
args.X[1] = keyslot;
|
||||
args.X[2] = access_key.data64[0];
|
||||
args.X[3] = access_key.data64[1];
|
||||
args.X[4] = source.data64[0];
|
||||
args.X[5] = source.data64[1];
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result CryptAes(AsyncOperationKey *out_op, u32 mode, const IvCtr &iv_ctr, u32 dst_addr, u32 src_addr, size_t size) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::CryptAes);
|
||||
args.X[1] = mode;
|
||||
args.X[2] = iv_ctr.data64[0];
|
||||
args.X[3] = iv_ctr.data64[1];
|
||||
args.X[4] = src_addr;
|
||||
args.X[5] = dst_addr;
|
||||
args.X[6] = size;
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
out_op->value = args.X[1];
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &source, u32 generation, u32 which) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::GenerateSpecificAesKey);
|
||||
args.X[1] = source.data64[0];
|
||||
args.X[2] = source.data64[1];
|
||||
args.X[3] = generation;
|
||||
args.X[4] = which;
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
out_key->data64[0] = args.X[1];
|
||||
out_key->data64[1] = args.X[2];
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result ComputeCmac(Cmac *out_mac, u32 keyslot, const void *data, size_t size) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::ComputeCmac);
|
||||
args.X[1] = keyslot;
|
||||
args.X[2] = reinterpret_cast<u64>(data);
|
||||
args.X[3] = size;
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
out_mac->data64[0] = args.X[1];
|
||||
out_mac->data64[1] = args.X[2];
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result ReEncryptRsaPrivateKey(void *data, size_t size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::ReEncryptRsaPrivateKey);
|
||||
args.X[1] = reinterpret_cast<u64>(&access_key_dec);
|
||||
args.X[2] = reinterpret_cast<u64>(&access_key_enc);
|
||||
args.X[3] = option;
|
||||
args.X[4] = reinterpret_cast<u64>(data);
|
||||
args.X[5] = size;
|
||||
args.X[6] = reinterpret_cast<u64>(&source_dec);
|
||||
args.X[7] = reinterpret_cast<u64>(&source_enc);
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result DecryptOrImportRsaPrivateKey(void *data, size_t size, const AccessKey &access_key, const KeySource &source, DecryptOrImportMode mode) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::DecryptOrImportRsaPrivateKey);
|
||||
args.X[1] = access_key.data64[0];
|
||||
args.X[2] = access_key.data64[1];
|
||||
args.X[3] = static_cast<u32>(mode);
|
||||
args.X[4] = reinterpret_cast<u64>(data);
|
||||
args.X[5] = size;
|
||||
args.X[6] = source.data64[0];
|
||||
args.X[7] = source.data64[1];
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result SecureExpMod(AsyncOperationKey *out_op, const void *base, const void *mod, SecureExpModMode mode) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::SecureExpMod);
|
||||
args.X[1] = reinterpret_cast<u64>(base);
|
||||
args.X[2] = reinterpret_cast<u64>(mod);
|
||||
args.X[3] = static_cast<u32>(mode);
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
out_op->value = args.X[1];
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result UnwrapTitleKey(AsyncOperationKey *out_op, const void *base, const void *mod, const void *label_digest, size_t label_digest_size, u32 option) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::UnwrapTitleKey);
|
||||
args.X[1] = reinterpret_cast<u64>(base);
|
||||
args.X[2] = reinterpret_cast<u64>(mod);
|
||||
std::memset(&args.X[3], 0, 4 * sizeof(args.X[3]));
|
||||
std::memcpy(&args.X[3], label_digest, std::min(size_t(4 * sizeof(args.X[3])), label_digest_size));
|
||||
args.X[7] = option;
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
out_op->value = args.X[1];
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result LoadTitleKey(u32 keyslot, const AccessKey &access_key) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::LoadTitleKey);
|
||||
args.X[1] = keyslot;
|
||||
args.X[2] = access_key.data64[0];
|
||||
args.X[3] = access_key.data64[1];
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result UnwrapCommonTitleKey(AccessKey *out, const KeySource &source, u32 generation) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::UnwrapCommonTitleKey);
|
||||
args.X[1] = source.data64[0];
|
||||
args.X[2] = source.data64[1];
|
||||
args.X[3] = generation;
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
out->data64[0] = args.X[1];
|
||||
out->data64[1] = args.X[2];
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
|
||||
/* Deprecated functions. */
|
||||
Result ImportEsKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::ImportEsKey);
|
||||
args.X[1] = access_key.data64[0];
|
||||
args.X[2] = access_key.data64[1];
|
||||
args.X[3] = option;
|
||||
args.X[4] = reinterpret_cast<u64>(data);
|
||||
args.X[5] = size;
|
||||
args.X[6] = source.data64[0];
|
||||
args.X[7] = source.data64[1];
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result DecryptRsaPrivateKey(size_t *out_size, void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::DecryptRsaPrivateKey);
|
||||
args.X[1] = access_key.data64[0];
|
||||
args.X[2] = access_key.data64[1];
|
||||
args.X[3] = option;
|
||||
args.X[4] = reinterpret_cast<u64>(data);
|
||||
args.X[5] = size;
|
||||
args.X[6] = source.data64[0];
|
||||
args.X[7] = source.data64[1];
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
*out_size = static_cast<size_t>(args.X[1]);
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
Result ImportSecureExpModKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = static_cast<u64>(FunctionId::ImportSecureExpModKey);
|
||||
args.X[1] = access_key.data64[0];
|
||||
args.X[2] = access_key.data64[1];
|
||||
args.X[3] = option;
|
||||
args.X[4] = reinterpret_cast<u64>(data);
|
||||
args.X[5] = size;
|
||||
args.X[6] = source.data64[0];
|
||||
args.X[7] = source.data64[1];
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
return static_cast<Result>(args.X[0]);
|
||||
}
|
||||
|
||||
}
|
78
stratosphere/libstratosphere/source/spl/spl_api.cpp
Normal file
78
stratosphere/libstratosphere/source/spl/spl_api.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.hpp>
|
||||
#include <stratosphere/spl.hpp>
|
||||
|
||||
namespace sts::spl {
|
||||
|
||||
HardwareType GetHardwareType() {
|
||||
u64 out_val = 0;
|
||||
R_ASSERT(splGetConfig(SplConfigItem_HardwareType, &out_val));
|
||||
return static_cast<HardwareType>(out_val);
|
||||
}
|
||||
|
||||
MemoryArrangement GetMemoryArrangement() {
|
||||
u64 arrange = 0;
|
||||
R_ASSERT(splGetConfig(SplConfigItem_MemoryArrange, &arrange));
|
||||
arrange &= 0x3F;
|
||||
switch (arrange) {
|
||||
case 2:
|
||||
return MemoryArrangement_StandardForAppletDev;
|
||||
case 3:
|
||||
return MemoryArrangement_StandardForSystemDev;
|
||||
case 17:
|
||||
return MemoryArrangement_Expanded;
|
||||
case 18:
|
||||
return MemoryArrangement_ExpandedForAppletDev;
|
||||
default:
|
||||
return MemoryArrangement_Standard;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDevelopmentHardware() {
|
||||
bool is_dev_hardware;
|
||||
R_ASSERT(splIsDevelopment(&is_dev_hardware));
|
||||
return is_dev_hardware;
|
||||
}
|
||||
|
||||
bool IsDevelopmentFunctionEnabled() {
|
||||
u64 val = 0;
|
||||
R_ASSERT(splGetConfig(SplConfigItem_IsDebugMode, &val));
|
||||
return val != 0;
|
||||
}
|
||||
|
||||
bool IsRecoveryBoot() {
|
||||
u64 val = 0;
|
||||
R_ASSERT(splGetConfig(SplConfigItem_IsRecoveryBoot, &val));
|
||||
return val != 0;
|
||||
}
|
||||
|
||||
bool IsMariko() {
|
||||
const auto hw_type = GetHardwareType();
|
||||
switch (hw_type) {
|
||||
case HardwareType::Icosa:
|
||||
case HardwareType::Copper:
|
||||
return false;
|
||||
case HardwareType::Hoag:
|
||||
case HardwareType::Iowa:
|
||||
return true;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
562
stratosphere/libstratosphere/source/updater/updater_api.cpp
Normal file
562
stratosphere/libstratosphere/source/updater/updater_api.cpp
Normal file
|
@ -0,0 +1,562 @@
|
|||
/*
|
||||
* 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 <stratosphere/updater.hpp>
|
||||
|
||||
#include "updater_bis_save.hpp"
|
||||
#include "updater_files.hpp"
|
||||
#include "updater_paths.hpp"
|
||||
|
||||
namespace sts::updater {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Validation Prototypes. */
|
||||
Result ValidateWorkBuffer(const void *work_buffer, size_t work_buffer_size);
|
||||
|
||||
/* Configuration Prototypes. */
|
||||
bool HasEks(BootImageUpdateType boot_image_update_type);
|
||||
bool HasAutoRcmPreserve(BootImageUpdateType boot_image_update_type);
|
||||
u32 GetNcmTitleType(BootModeType mode);
|
||||
Result GetBootImagePackageDataId(u64 *out_data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size);
|
||||
|
||||
/* Verification Prototypes. */
|
||||
Result GetVerificationState(VerificationState *out, void *work_buffer, size_t work_buffer_size);
|
||||
Result VerifyBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result VerifyBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result VerifyBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
|
||||
/* Update Prototypes. */
|
||||
Result SetVerificationNeeded(BootModeType mode, bool needed, void *work_buffer, size_t work_buffer_size);
|
||||
Result UpdateBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result UpdateBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result UpdateBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
|
||||
/* Package helpers. */
|
||||
Result ValidateBctFileHash(Boot0Accessor &accessor, Boot0Partition which, const void *stored_hash, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result GetPackage2Hash(void *dst_hash, size_t package2_size, void *work_buffer, size_t work_buffer_size, Package2Type which);
|
||||
Result WritePackage2(void *work_buffer, size_t work_buffer_size, Package2Type which, BootImageUpdateType boot_image_update_type);
|
||||
|
||||
/* Implementations. */
|
||||
Result ValidateWorkBuffer(const void *work_buffer, size_t work_buffer_size) {
|
||||
if (work_buffer_size < BctSize + EksSize) {
|
||||
return ResultUpdaterTooSmallWorkBuffer;
|
||||
}
|
||||
if (reinterpret_cast<uintptr_t>(work_buffer) & 0xFFF) {
|
||||
return ResultUpdaterMisalignedWorkBuffer;
|
||||
}
|
||||
if (reinterpret_cast<uintptr_t>(work_buffer_size) & 0x1FF) {
|
||||
return ResultUpdaterMisalignedWorkBuffer;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
bool HasEks(BootImageUpdateType boot_image_update_type) {
|
||||
switch (boot_image_update_type) {
|
||||
case BootImageUpdateType::Erista:
|
||||
return true;
|
||||
case BootImageUpdateType::Mariko:
|
||||
return false;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
bool HasAutoRcmPreserve(BootImageUpdateType boot_image_update_type) {
|
||||
switch (boot_image_update_type) {
|
||||
case BootImageUpdateType::Erista:
|
||||
return true;
|
||||
case BootImageUpdateType::Mariko:
|
||||
return false;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
u32 GetNcmTitleType(BootModeType mode) {
|
||||
switch (mode) {
|
||||
case BootModeType::Normal:
|
||||
return NcmContentMetaType_BootImagePackage;
|
||||
case BootModeType::Safe:
|
||||
return NcmContentMetaType_BootImagePackageSafe;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
Result GetVerificationState(VerificationState *out, void *work_buffer, size_t work_buffer_size) {
|
||||
/* Always set output to true before doing anything else. */
|
||||
out->needs_verify_normal = true;
|
||||
out->needs_verify_safe = true;
|
||||
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
/* Initialize boot0 save accessor. */
|
||||
BisSave save;
|
||||
R_TRY(save.Initialize(work_buffer, work_buffer_size));
|
||||
ON_SCOPE_EXIT { save.Finalize(); };
|
||||
|
||||
/* Load save from NAND. */
|
||||
R_TRY(save.Load());
|
||||
|
||||
/* Read data from save. */
|
||||
out->needs_verify_normal = save.GetNeedsVerification(BootModeType::Normal);
|
||||
out->needs_verify_safe = save.GetNeedsVerification(BootModeType::Safe);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result VerifyBootImagesAndRepairIfNeeded(bool *out_repaired, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Get system data id for boot images (819/81A/81B/81C). */
|
||||
u64 bip_data_id = 0;
|
||||
R_TRY(GetBootImagePackageDataId(&bip_data_id, mode, work_buffer, work_buffer_size));
|
||||
|
||||
/* Verify the boot images in NAND. */
|
||||
R_TRY_CATCH(VerifyBootImages(bip_data_id, mode, work_buffer, work_buffer_size, boot_image_update_type)) {
|
||||
R_CATCH(ResultUpdaterNeedsRepairBootImages) {
|
||||
/* Perform repair. */
|
||||
*out_repaired = true;
|
||||
R_TRY(UpdateBootImages(bip_data_id, mode, work_buffer, work_buffer_size, boot_image_update_type));
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* We've either just verified or just repaired. Either way, we don't need to verify any more. */
|
||||
return SetVerificationNeeded(mode, false, work_buffer, work_buffer_size);
|
||||
}
|
||||
|
||||
Result GetBootImagePackageDataId(u64 *out_data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size) {
|
||||
/* Ensure we can read content metas. */
|
||||
constexpr size_t MaxContentMetas = 0x40;
|
||||
if (work_buffer_size < sizeof(NcmMetaRecord) * MaxContentMetas) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Open NAND System meta database, list contents. */
|
||||
NcmContentMetaDatabase meta_db;
|
||||
R_TRY(ncmOpenContentMetaDatabase(FsStorageId_NandSystem, &meta_db));
|
||||
ON_SCOPE_EXIT { serviceClose(&meta_db.s); };
|
||||
|
||||
NcmMetaRecord *records = reinterpret_cast<NcmMetaRecord *>(work_buffer);
|
||||
|
||||
const u32 title_type = GetNcmTitleType(mode);
|
||||
u32 written_entries;
|
||||
u32 total_entries;
|
||||
R_TRY(ncmContentMetaDatabaseList(&meta_db, title_type, 0, 0, UINT64_MAX, records, MaxContentMetas * sizeof(*records), &written_entries, &total_entries));
|
||||
if (total_entries == 0) {
|
||||
return ResultUpdaterBootImagePackageNotFound;
|
||||
}
|
||||
|
||||
if (total_entries != written_entries) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Output is sorted, return the lowest valid exfat entry. */
|
||||
if (total_entries > 1) {
|
||||
for (size_t i = 0; i < total_entries; i++) {
|
||||
u8 attr;
|
||||
R_TRY(ncmContentMetaDatabaseGetAttributes(&meta_db, &records[i], &attr));
|
||||
|
||||
if (attr & NcmContentMetaAttribute_Exfat) {
|
||||
*out_data_id = records[i].titleId;
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If there's only one entry or no exfat entries, return that entry. */
|
||||
*out_data_id = records[0].titleId;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result VerifyBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
switch (mode) {
|
||||
case BootModeType::Normal:
|
||||
return VerifyBootImagesNormal(data_id, work_buffer, work_buffer_size, boot_image_update_type);
|
||||
case BootModeType::Safe:
|
||||
return VerifyBootImagesSafe(data_id, work_buffer, work_buffer_size, boot_image_update_type);
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
Result VerifyBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY_CATCH(romfsMountFromDataArchive(data_id, FsStorageId_NandSystem, GetBootImagePackageMountPath())) {
|
||||
R_CATCH(ResultFsTargetNotFound) {
|
||||
return ResultUpdaterBootImagePackageNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { if (R_FAILED(romfsUnmount(GetBootImagePackageMountPath()))) { std::abort(); } };
|
||||
|
||||
/* Read and validate hashes of boot images. */
|
||||
{
|
||||
size_t size;
|
||||
u8 nand_hash[SHA256_HASH_SIZE];
|
||||
u8 file_hash[SHA256_HASH_SIZE];
|
||||
|
||||
Boot0Accessor boot0_accessor;
|
||||
R_TRY(boot0_accessor.Initialize());
|
||||
ON_SCOPE_EXIT { boot0_accessor.Finalize(); };
|
||||
|
||||
/* Compare BCT hashes. */
|
||||
R_TRY(boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctNormalMain));
|
||||
R_TRY(ValidateBctFileHash(boot0_accessor, Boot0Partition::BctNormalMain, nand_hash, work_buffer, work_buffer_size, boot_image_update_type));
|
||||
|
||||
/* Compare BCT Sub hashes. */
|
||||
R_TRY(boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctNormalSub));
|
||||
R_TRY(ValidateBctFileHash(boot0_accessor, Boot0Partition::BctNormalSub, nand_hash, work_buffer, work_buffer_size, boot_image_update_type));
|
||||
|
||||
/* Compare Package1 Normal/Sub hashes. */
|
||||
R_TRY(GetFileHash(&size, file_hash, GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size));
|
||||
R_TRY(boot0_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot0Partition::Package1NormalMain));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultUpdaterNeedsRepairBootImages;
|
||||
}
|
||||
R_TRY(boot0_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot0Partition::Package1NormalSub));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultUpdaterNeedsRepairBootImages;
|
||||
}
|
||||
|
||||
/* Compare Package2 Normal/Sub hashes. */
|
||||
R_TRY(GetFileHash(&size, file_hash, GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size));
|
||||
R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::NormalMain));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultUpdaterNeedsRepairBootImages;
|
||||
}
|
||||
R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::NormalSub));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultUpdaterNeedsRepairBootImages;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result VerifyBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY_CATCH(romfsMountFromDataArchive(data_id, FsStorageId_NandSystem, GetBootImagePackageMountPath())) {
|
||||
R_CATCH(ResultFsTargetNotFound) {
|
||||
return ResultUpdaterBootImagePackageNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { if (R_FAILED(romfsUnmount(GetBootImagePackageMountPath()))) { std::abort(); } };
|
||||
|
||||
/* Read and validate hashes of boot images. */
|
||||
{
|
||||
size_t size;
|
||||
u8 nand_hash[SHA256_HASH_SIZE];
|
||||
u8 file_hash[SHA256_HASH_SIZE];
|
||||
|
||||
Boot0Accessor boot0_accessor;
|
||||
R_TRY(boot0_accessor.Initialize());
|
||||
ON_SCOPE_EXIT { boot0_accessor.Finalize(); };
|
||||
|
||||
Boot1Accessor boot1_accessor;
|
||||
R_TRY(boot1_accessor.Initialize());
|
||||
ON_SCOPE_EXIT { boot1_accessor.Finalize(); };
|
||||
|
||||
|
||||
/* Compare BCT hashes. */
|
||||
R_TRY(boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctSafeMain));
|
||||
R_TRY(ValidateBctFileHash(boot0_accessor, Boot0Partition::BctSafeMain, nand_hash, work_buffer, work_buffer_size, boot_image_update_type));
|
||||
|
||||
/* Compare BCT Sub hashes. */
|
||||
R_TRY(boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctSafeSub));
|
||||
R_TRY(ValidateBctFileHash(boot0_accessor, Boot0Partition::BctSafeSub, nand_hash, work_buffer, work_buffer_size, boot_image_update_type));
|
||||
|
||||
/* Compare Package1 Normal/Sub hashes. */
|
||||
R_TRY(GetFileHash(&size, file_hash, GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size));
|
||||
R_TRY(boot1_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot1Partition::Package1SafeMain));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultUpdaterNeedsRepairBootImages;
|
||||
}
|
||||
R_TRY(boot1_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot1Partition::Package1SafeSub));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultUpdaterNeedsRepairBootImages;
|
||||
}
|
||||
|
||||
/* Compare Package2 Normal/Sub hashes. */
|
||||
R_TRY(GetFileHash(&size, file_hash, GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size));
|
||||
R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::SafeMain));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultUpdaterNeedsRepairBootImages;
|
||||
}
|
||||
R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::SafeSub));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultUpdaterNeedsRepairBootImages;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result UpdateBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
switch (mode) {
|
||||
case BootModeType::Normal:
|
||||
return UpdateBootImagesNormal(data_id, work_buffer, work_buffer_size, boot_image_update_type);
|
||||
case BootModeType::Safe:
|
||||
return UpdateBootImagesSafe(data_id, work_buffer, work_buffer_size, boot_image_update_type);
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
Result UpdateBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY_CATCH(romfsMountFromDataArchive(data_id, FsStorageId_NandSystem, GetBootImagePackageMountPath())) {
|
||||
R_CATCH(ResultFsTargetNotFound) {
|
||||
return ResultUpdaterBootImagePackageNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { if (R_FAILED(romfsUnmount(GetBootImagePackageMountPath()))) { std::abort(); } };
|
||||
|
||||
{
|
||||
Boot0Accessor boot0_accessor;
|
||||
R_TRY(boot0_accessor.Initialize());
|
||||
ON_SCOPE_EXIT { boot0_accessor.Finalize(); };
|
||||
|
||||
/* Write Package1 sub. */
|
||||
R_TRY(boot0_accessor.Clear(work_buffer, work_buffer_size, Boot0Partition::Package1NormalSub));
|
||||
R_TRY(boot0_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot0Partition::Package1NormalSub));
|
||||
|
||||
/* Write Package2 sub. */
|
||||
R_TRY(WritePackage2(work_buffer, work_buffer_size, Package2Type::NormalSub, boot_image_update_type));
|
||||
|
||||
/* Write BCT sub + BCT main, in that order. */
|
||||
{
|
||||
void *bct = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + 0);
|
||||
void *work = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + BctSize);
|
||||
|
||||
size_t size;
|
||||
R_TRY(ReadFile(&size, bct, BctSize, GetBctPath(boot_image_update_type)));
|
||||
if (HasEks(boot_image_update_type)) {
|
||||
R_TRY(boot0_accessor.UpdateEks(bct, work));
|
||||
}
|
||||
|
||||
/* Only preserve autorcm if on a unit with unpatched rcm bug. */
|
||||
if (HasAutoRcmPreserve(boot_image_update_type) && !IsRcmBugPatched()) {
|
||||
R_TRY(boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctNormalSub));
|
||||
R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalSub));
|
||||
R_TRY(boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctNormalMain));
|
||||
R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalMain));
|
||||
} else {
|
||||
R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalSub));
|
||||
R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalMain));
|
||||
}
|
||||
}
|
||||
|
||||
/* Write Package2 main. */
|
||||
R_TRY(WritePackage2(work_buffer, work_buffer_size, Package2Type::NormalMain, boot_image_update_type));
|
||||
|
||||
/* Write Package1 main. */
|
||||
R_TRY(boot0_accessor.Clear(work_buffer, work_buffer_size, Boot0Partition::Package1NormalMain));
|
||||
R_TRY(boot0_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot0Partition::Package1NormalMain));
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result UpdateBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY_CATCH(romfsMountFromDataArchive(data_id, FsStorageId_NandSystem, GetBootImagePackageMountPath())) {
|
||||
R_CATCH(ResultFsTargetNotFound) {
|
||||
return ResultUpdaterBootImagePackageNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { if (R_FAILED(romfsUnmount(GetBootImagePackageMountPath()))) { std::abort(); } };
|
||||
|
||||
{
|
||||
Boot0Accessor boot0_accessor;
|
||||
R_TRY(boot0_accessor.Initialize());
|
||||
ON_SCOPE_EXIT { boot0_accessor.Finalize(); };
|
||||
|
||||
Boot1Accessor boot1_accessor;
|
||||
R_TRY(boot1_accessor.Initialize());
|
||||
ON_SCOPE_EXIT { boot1_accessor.Finalize(); };
|
||||
|
||||
|
||||
/* Write Package1 sub. */
|
||||
R_TRY(boot1_accessor.Clear(work_buffer, work_buffer_size, Boot1Partition::Package1SafeSub));
|
||||
R_TRY(boot1_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot1Partition::Package1SafeSub));
|
||||
|
||||
/* Write Package2 sub. */
|
||||
R_TRY(WritePackage2(work_buffer, work_buffer_size, Package2Type::SafeSub, boot_image_update_type));
|
||||
|
||||
/* Write BCT sub + BCT main, in that order. */
|
||||
{
|
||||
void *bct = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + 0);
|
||||
void *work = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + BctSize);
|
||||
|
||||
size_t size;
|
||||
R_TRY(ReadFile(&size, bct, BctSize, GetBctPath(boot_image_update_type)));
|
||||
if (HasEks(boot_image_update_type)) {
|
||||
R_TRY(boot0_accessor.UpdateEks(bct, work));
|
||||
}
|
||||
/* Only preserve autorcm if on a unit with unpatched rcm bug. */
|
||||
if (HasAutoRcmPreserve(boot_image_update_type) && !IsRcmBugPatched()) {
|
||||
R_TRY(boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctSafeSub));
|
||||
R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeSub));
|
||||
R_TRY(boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctSafeMain));
|
||||
R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeMain));
|
||||
} else {
|
||||
R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeSub));
|
||||
R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeMain));
|
||||
}
|
||||
}
|
||||
|
||||
/* Write Package2 main. */
|
||||
R_TRY(WritePackage2(work_buffer, work_buffer_size, Package2Type::SafeMain, boot_image_update_type));
|
||||
|
||||
/* Write Package1 main. */
|
||||
R_TRY(boot1_accessor.Clear(work_buffer, work_buffer_size, Boot1Partition::Package1SafeMain));
|
||||
R_TRY(boot1_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot1Partition::Package1SafeMain));
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result SetVerificationNeeded(BootModeType mode, bool needed, void *work_buffer, size_t work_buffer_size) {
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
/* Initialize boot0 save accessor. */
|
||||
BisSave save;
|
||||
R_TRY(save.Initialize(work_buffer, work_buffer_size));
|
||||
ON_SCOPE_EXIT { save.Finalize(); };
|
||||
|
||||
/* Load save from NAND. */
|
||||
R_TRY(save.Load());
|
||||
|
||||
/* Set whether we need to verify, then save to nand. */
|
||||
save.SetNeedsVerification(mode, needed);
|
||||
R_TRY(save.Save());
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ValidateBctFileHash(Boot0Accessor &accessor, Boot0Partition which, const void *stored_hash, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
void *bct = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + 0);
|
||||
void *work = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + BctSize);
|
||||
|
||||
size_t size;
|
||||
R_TRY(ReadFile(&size, bct, BctSize, GetBctPath(boot_image_update_type)));
|
||||
if (HasEks(boot_image_update_type)) {
|
||||
R_TRY(accessor.UpdateEks(bct, work));
|
||||
}
|
||||
if (HasAutoRcmPreserve(boot_image_update_type)) {
|
||||
R_TRY(accessor.PreserveAutoRcm(bct, work, which));
|
||||
}
|
||||
|
||||
u8 file_hash[SHA256_HASH_SIZE];
|
||||
sha256CalculateHash(file_hash, bct, BctSize);
|
||||
|
||||
if (std::memcmp(file_hash, stored_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultUpdaterNeedsRepairBootImages;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result GetPackage2Hash(void *dst_hash, size_t package2_size, void *work_buffer, size_t work_buffer_size, Package2Type which) {
|
||||
Package2Accessor accessor(which);
|
||||
R_TRY(accessor.Initialize());
|
||||
ON_SCOPE_EXIT { accessor.Finalize(); };
|
||||
|
||||
return accessor.GetHash(dst_hash, package2_size, work_buffer, work_buffer_size, Package2Partition::Package2);
|
||||
}
|
||||
|
||||
Result WritePackage2(void *work_buffer, size_t work_buffer_size, Package2Type which, BootImageUpdateType boot_image_update_type) {
|
||||
Package2Accessor accessor(which);
|
||||
R_TRY(accessor.Initialize());
|
||||
ON_SCOPE_EXIT { accessor.Finalize(); };
|
||||
|
||||
return accessor.Write(GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size, Package2Partition::Package2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BootImageUpdateType GetBootImageUpdateType(spl::HardwareType hw_type) {
|
||||
switch (hw_type) {
|
||||
case spl::HardwareType::Icosa:
|
||||
case spl::HardwareType::Copper:
|
||||
return BootImageUpdateType::Erista;
|
||||
case spl::HardwareType::Hoag:
|
||||
case spl::HardwareType::Iowa:
|
||||
return BootImageUpdateType::Mariko;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
Result VerifyBootImagesAndRepairIfNeeded(bool *out_repaired_normal, bool *out_repaired_safe, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Always set output to false before doing anything else. */
|
||||
*out_repaired_normal = false;
|
||||
*out_repaired_safe = false;
|
||||
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
/* Get verification state from NAND. */
|
||||
VerificationState verification_state;
|
||||
R_TRY(GetVerificationState(&verification_state, work_buffer, work_buffer_size));
|
||||
|
||||
/* If we don't need to verify anything, we're done. */
|
||||
if (!verification_state.needs_verify_normal && !verification_state.needs_verify_safe) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Get a session to ncm. */
|
||||
DoWithSmSession([&]() {
|
||||
if (R_FAILED(ncmInitialize())) {
|
||||
std::abort();
|
||||
}
|
||||
});
|
||||
ON_SCOPE_EXIT { ncmExit(); };
|
||||
|
||||
/* Verify normal, verify safe as needed. */
|
||||
if (verification_state.needs_verify_normal) {
|
||||
R_TRY_CATCH(VerifyBootImagesAndRepairIfNeeded(out_repaired_normal, BootModeType::Normal, work_buffer, work_buffer_size, boot_image_update_type)) {
|
||||
R_CATCH(ResultUpdaterBootImagePackageNotFound) {
|
||||
/* Nintendo considers failure to locate bip a success. TODO: don't do that? */
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
|
||||
if (verification_state.needs_verify_safe) {
|
||||
R_TRY_CATCH(VerifyBootImagesAndRepairIfNeeded(out_repaired_safe, BootModeType::Safe, work_buffer, work_buffer_size, boot_image_update_type)) {
|
||||
R_CATCH(ResultUpdaterBootImagePackageNotFound) {
|
||||
/* Nintendo considers failure to locate bip a success. TODO: don't do that? */
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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 "updater_bis_management.hpp"
|
||||
|
||||
namespace sts::updater {
|
||||
|
||||
Result BisAccessor::Initialize() {
|
||||
R_TRY(fsOpenBisStorage(&this->storage, this->partition_id));
|
||||
this->active = true;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void BisAccessor::Finalize() {
|
||||
if (this->active) {
|
||||
fsStorageClose(&this->storage);
|
||||
this->active = false;
|
||||
}
|
||||
}
|
||||
|
||||
Result BisAccessor::Read(void *dst, size_t size, u64 offset) {
|
||||
if (offset % SectorAlignment) {
|
||||
std::abort();
|
||||
}
|
||||
return fsStorageRead(&this->storage, offset, dst, size);
|
||||
}
|
||||
|
||||
Result BisAccessor::Write(u64 offset, const void *src, size_t size) {
|
||||
if (offset % SectorAlignment) {
|
||||
std::abort();
|
||||
}
|
||||
return fsStorageWrite(&this->storage, offset, src, size);
|
||||
}
|
||||
|
||||
Result BisAccessor::Write(u64 offset, size_t size, const char *bip_path, void *work_buffer, size_t work_buffer_size) {
|
||||
if (offset % SectorAlignment != 0 || work_buffer_size % SectorAlignment != 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
FILE *bip_fp = fopen(bip_path, "rb");
|
||||
if (bip_fp == NULL) {
|
||||
return ResultUpdaterInvalidBootImagePackage;
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(bip_fp); };
|
||||
|
||||
size_t written = 0;
|
||||
while (true) {
|
||||
std::memset(work_buffer, 0, work_buffer_size);
|
||||
size_t read_size = fread(work_buffer, 1, work_buffer_size, bip_fp);
|
||||
if (read_size != work_buffer_size) {
|
||||
if (ferror(bip_fp)) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
}
|
||||
if (written + read_size > size) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
size_t aligned_size = ((read_size + SectorAlignment - 1) / SectorAlignment) * SectorAlignment;
|
||||
R_TRY(this->Write(offset + written, work_buffer, aligned_size));
|
||||
written += read_size;
|
||||
|
||||
if (read_size != work_buffer_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BisAccessor::Clear(u64 offset, u64 size, void *work_buffer, size_t work_buffer_size) {
|
||||
if (offset % SectorAlignment != 0 || work_buffer_size % SectorAlignment != 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
std::memset(work_buffer, 0, work_buffer_size);
|
||||
|
||||
size_t written = 0;
|
||||
while (written < size) {
|
||||
size_t cur_write_size = std::min(work_buffer_size, size - written);
|
||||
R_TRY(this->Write(offset + written, work_buffer, cur_write_size));
|
||||
written += cur_write_size;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BisAccessor::GetHash(void *dst, u64 offset, u64 size, u64 hash_size, void *work_buffer, size_t work_buffer_size) {
|
||||
if (offset % SectorAlignment != 0 || work_buffer_size % SectorAlignment != 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Sha256Context sha_ctx;
|
||||
sha256ContextCreate(&sha_ctx);
|
||||
|
||||
size_t total_read = 0;
|
||||
while (total_read < hash_size) {
|
||||
size_t cur_read_size = std::min(work_buffer_size, size - total_read);
|
||||
size_t cur_update_size = std::min(cur_read_size, hash_size - total_read);
|
||||
R_TRY(this->Read(work_buffer, cur_read_size, offset + total_read));
|
||||
sha256ContextUpdate(&sha_ctx, work_buffer, cur_update_size);
|
||||
total_read += cur_read_size;
|
||||
}
|
||||
sha256ContextGetHash(&sha_ctx, dst);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
size_t Boot0Accessor::GetBootloaderVersion(void *bct) {
|
||||
u32 version = *reinterpret_cast<u32 *>(reinterpret_cast<uintptr_t>(bct) + BctVersionOffset);
|
||||
if (version > BctVersionMax) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return static_cast<size_t>(version);
|
||||
}
|
||||
|
||||
size_t Boot0Accessor::GetEksIndex(size_t bootloader_version) {
|
||||
if (bootloader_version > BctVersionMax) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return (bootloader_version > 0) ? bootloader_version - 1 : 0;
|
||||
}
|
||||
|
||||
void Boot0Accessor::CopyEks(void *dst_bct, const void *src_eks, size_t eks_index) {
|
||||
std::memcpy(reinterpret_cast<u8 *>(dst_bct) + BctEksOffset, reinterpret_cast<const u8 *>(src_eks) + eks_index * EksEntrySize, EksBlobSize);
|
||||
}
|
||||
|
||||
Result Boot0Accessor::UpdateEks(void *dst_bct, void *eks_work_buffer) {
|
||||
size_t read_size;
|
||||
R_TRY(this->Read(&read_size, eks_work_buffer, EksSize, Boot0Partition::Eks));
|
||||
|
||||
return this->UpdateEksManually(dst_bct, eks_work_buffer);
|
||||
}
|
||||
|
||||
Result Boot0Accessor::UpdateEksManually(void *dst_bct, const void *src_eks) {
|
||||
this->CopyEks(dst_bct, src_eks, GetEksIndex(GetBootloaderVersion(dst_bct)));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Boot0Accessor::PreserveAutoRcm(void *dst_bct, void *work_buffer, Boot0Partition which) {
|
||||
std::memset(work_buffer, 0, BctSize);
|
||||
|
||||
size_t read_size;
|
||||
R_TRY(this->Read(&read_size, work_buffer, BctSize, which));
|
||||
|
||||
void *dst_pubk = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(dst_bct) + BctPubkOffset);
|
||||
void *src_pubk = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + BctPubkOffset);
|
||||
std::memcpy(dst_pubk, src_pubk, BctPubkSize);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* 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/updater/updater_types.hpp>
|
||||
|
||||
namespace sts::updater {
|
||||
|
||||
class BisAccessor {
|
||||
public:
|
||||
static constexpr size_t SectorAlignment = 0x200;
|
||||
private:
|
||||
FsStorage storage = {};
|
||||
FsBisStorageId partition_id;
|
||||
bool active;
|
||||
public:
|
||||
BisAccessor(FsBisStorageId id) : partition_id(id), active(false) { }
|
||||
~BisAccessor() {
|
||||
if (this->active) {
|
||||
fsStorageClose(&storage);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
Result Initialize();
|
||||
void Finalize();
|
||||
protected:
|
||||
Result Read(void *dst, size_t size, u64 offset);
|
||||
Result Write(u64 offset, const void *src, size_t size);
|
||||
Result Write(u64 offset, size_t size, const char *bip_path, void *work_buffer, size_t work_buffer_size);
|
||||
Result Clear(u64 offset, u64 size, void *work_buffer, size_t work_buffer_size);
|
||||
Result GetHash(void *dst, u64 offset, u64 size, u64 hash_size, void *work_buffer, size_t work_buffer_size);
|
||||
};
|
||||
|
||||
template<typename EnumType>
|
||||
struct OffsetSizeEntry {
|
||||
EnumType which;
|
||||
u64 offset;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
enum class Boot0Partition {
|
||||
BctNormalMain,
|
||||
BctSafeMain,
|
||||
BctNormalSub,
|
||||
BctSafeSub,
|
||||
BctSave,
|
||||
Package1NormalMain,
|
||||
Package1NormalSub,
|
||||
Eks,
|
||||
Count,
|
||||
};
|
||||
|
||||
enum class Boot1Partition {
|
||||
Package1SafeMain,
|
||||
Package1SafeSub,
|
||||
Package1RepairMain,
|
||||
Package1RepairSub,
|
||||
Count,
|
||||
};
|
||||
|
||||
enum class Package2Partition {
|
||||
BootConfig,
|
||||
Package2,
|
||||
Count,
|
||||
};
|
||||
|
||||
struct Boot0Meta {
|
||||
using EnumType = Boot0Partition;
|
||||
using OffsetSizeType = OffsetSizeEntry<EnumType>;
|
||||
|
||||
static constexpr size_t NumEntries = static_cast<size_t>(EnumType::Count);
|
||||
static constexpr OffsetSizeType Entries[NumEntries] = {
|
||||
{Boot0Partition::BctNormalMain, 0 * BctSize, BctSize},
|
||||
{Boot0Partition::BctSafeMain, 1 * BctSize, BctSize},
|
||||
{Boot0Partition::BctNormalSub, 2 * BctSize, BctSize},
|
||||
{Boot0Partition::BctSafeSub, 3 * BctSize, BctSize},
|
||||
{Boot0Partition::BctSave, 63 * BctSize, BctSize},
|
||||
{Boot0Partition::Package1NormalMain, 0x100000, 0x40000},
|
||||
{Boot0Partition::Package1NormalSub, 0x140000, 0x40000},
|
||||
{Boot0Partition::Eks, 0x180000, EksSize},
|
||||
};
|
||||
};
|
||||
|
||||
struct Boot1Meta {
|
||||
using EnumType = Boot1Partition;
|
||||
using OffsetSizeType = OffsetSizeEntry<EnumType>;
|
||||
|
||||
static constexpr size_t NumEntries = static_cast<size_t>(EnumType::Count);
|
||||
static constexpr OffsetSizeType Entries[NumEntries] = {
|
||||
{Boot1Partition::Package1SafeMain, 0x00000, 0x40000},
|
||||
{Boot1Partition::Package1SafeSub, 0x40000, 0x40000},
|
||||
{Boot1Partition::Package1RepairMain, 0x80000, 0x40000},
|
||||
{Boot1Partition::Package1RepairSub, 0xC0000, 0x40000},
|
||||
};
|
||||
};
|
||||
|
||||
struct Package2Meta {
|
||||
using EnumType = Package2Partition;
|
||||
using OffsetSizeType = OffsetSizeEntry<EnumType>;
|
||||
|
||||
static constexpr size_t NumEntries = static_cast<size_t>(EnumType::Count);
|
||||
static constexpr OffsetSizeType Entries[NumEntries] = {
|
||||
{Package2Partition::BootConfig, 0x0000, 0x004000},
|
||||
{Package2Partition::Package2, 0x4000, 0x7FC000},
|
||||
};
|
||||
};
|
||||
|
||||
template<typename Meta>
|
||||
class PartitionAccessor : public BisAccessor {
|
||||
public:
|
||||
using EnumType = typename Meta::EnumType;
|
||||
using OffsetSizeType = typename Meta::OffsetSizeType;
|
||||
public:
|
||||
PartitionAccessor(FsBisStorageId id) : BisAccessor(id) { }
|
||||
private:
|
||||
constexpr const OffsetSizeType *FindEntry(EnumType which) {
|
||||
for (size_t i = 0; i < Meta::NumEntries; i++) {
|
||||
if (Meta::Entries[i].which == which) {
|
||||
return &Meta::Entries[i];
|
||||
}
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
public:
|
||||
Result Read(size_t *out_size, void *dst, size_t size, EnumType which) {
|
||||
const auto entry = FindEntry(which);
|
||||
if (size < entry->size) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
R_TRY(BisAccessor::Read(dst, entry->size, entry->offset));
|
||||
|
||||
*out_size = entry->size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Write(const void *src, size_t size, EnumType which) {
|
||||
const auto entry = FindEntry(which);
|
||||
if (size > entry->size || size % BisAccessor::SectorAlignment != 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return BisAccessor::Write(entry->offset, src, size);
|
||||
}
|
||||
|
||||
Result Write(const char *bip_path, void *work_buffer, size_t work_buffer_size, EnumType which) {
|
||||
const auto entry = FindEntry(which);
|
||||
return BisAccessor::Write(entry->offset, entry->size, bip_path, work_buffer, work_buffer_size);
|
||||
}
|
||||
|
||||
Result Clear(void *work_buffer, size_t work_buffer_size, EnumType which) {
|
||||
const auto entry = FindEntry(which);
|
||||
return BisAccessor::Clear(entry->offset, entry->size, work_buffer, work_buffer_size);
|
||||
}
|
||||
|
||||
Result GetHash(void *dst, u64 hash_size, void *work_buffer, size_t work_buffer_size, EnumType which) {
|
||||
const auto entry = FindEntry(which);
|
||||
return BisAccessor::GetHash(dst, entry->offset, entry->size, hash_size, work_buffer, work_buffer_size);
|
||||
}
|
||||
};
|
||||
|
||||
enum class Package2Type {
|
||||
NormalMain,
|
||||
NormalSub,
|
||||
SafeMain,
|
||||
SafeSub,
|
||||
RepairMain,
|
||||
RepairSub,
|
||||
};
|
||||
|
||||
static constexpr FsBisStorageId GetPackage2StorageId(Package2Type which) {
|
||||
switch (which) {
|
||||
case Package2Type::NormalMain:
|
||||
return FsBisStorageId_BootConfigAndPackage2NormalMain;
|
||||
case Package2Type::NormalSub:
|
||||
return FsBisStorageId_BootConfigAndPackage2NormalSub;
|
||||
case Package2Type::SafeMain:
|
||||
return FsBisStorageId_BootConfigAndPackage2SafeMain;
|
||||
case Package2Type::SafeSub:
|
||||
return FsBisStorageId_BootConfigAndPackage2SafeSub;
|
||||
case Package2Type::RepairMain:
|
||||
return FsBisStorageId_BootConfigAndPackage2RepairMain;
|
||||
case Package2Type::RepairSub:
|
||||
return FsBisStorageId_BootConfigAndPackage2RepairSub;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
class Boot0Accessor : public PartitionAccessor<Boot0Meta> {
|
||||
public:
|
||||
static constexpr FsBisStorageId PartitionId = FsBisStorageId_Boot0;
|
||||
static constexpr size_t BctPubkOffset = 0x210;
|
||||
static constexpr size_t BctPubkSize = 0x100;
|
||||
static constexpr size_t BctEksOffset = 0x450;
|
||||
static constexpr size_t BctVersionOffset = 0x2330;
|
||||
static constexpr size_t BctVersionMax = 0x20;
|
||||
public:
|
||||
Boot0Accessor() : PartitionAccessor<Boot0Meta>(PartitionId) { }
|
||||
private:
|
||||
static size_t GetBootloaderVersion(void *bct);
|
||||
static size_t GetEksIndex(size_t bootloader_version);
|
||||
static void CopyEks(void *dst_bct, const void *src_eks, size_t eks_index);
|
||||
public:
|
||||
Result UpdateEks(void *dst_bct, void *eks_work_buffer);
|
||||
Result UpdateEksManually(void *dst_bct, const void *src_eks);
|
||||
Result PreserveAutoRcm(void *dst_bct, void *work_buffer, Boot0Partition which);
|
||||
};
|
||||
|
||||
class Boot1Accessor : public PartitionAccessor<Boot1Meta> {
|
||||
public:
|
||||
static constexpr FsBisStorageId PartitionId = FsBisStorageId_Boot1;
|
||||
public:
|
||||
Boot1Accessor() : PartitionAccessor<Boot1Meta>(PartitionId) { }
|
||||
};
|
||||
|
||||
class Package2Accessor : public PartitionAccessor<Package2Meta> {
|
||||
public:
|
||||
Package2Accessor(Package2Type which) : PartitionAccessor<Package2Meta>(GetPackage2StorageId(which)) { }
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 "updater_bis_save.hpp"
|
||||
|
||||
namespace sts::updater {
|
||||
|
||||
size_t BisSave::GetVerificationFlagOffset(BootModeType mode) {
|
||||
switch (mode) {
|
||||
case BootModeType::Normal:
|
||||
return 0;
|
||||
case BootModeType::Safe:
|
||||
return 1;
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
Result BisSave::Initialize(void *work_buffer, size_t work_buffer_size) {
|
||||
if (work_buffer_size < SaveSize || reinterpret_cast<uintptr_t>(work_buffer) & 0xFFF || work_buffer_size & 0x1FF) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
R_TRY(this->accessor.Initialize());
|
||||
this->save_buffer = work_buffer;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void BisSave::Finalize() {
|
||||
this->accessor.Finalize();
|
||||
}
|
||||
|
||||
Result BisSave::Load() {
|
||||
size_t read_size;
|
||||
return this->accessor.Read(&read_size, this->save_buffer, SaveSize, Boot0Partition::BctSave);
|
||||
}
|
||||
|
||||
Result BisSave::Save() {
|
||||
return this->accessor.Write(this->save_buffer, SaveSize, Boot0Partition::BctSave);
|
||||
}
|
||||
|
||||
bool BisSave::GetNeedsVerification(BootModeType mode) {
|
||||
return reinterpret_cast<const u8 *>(this->save_buffer)[GetVerificationFlagOffset(mode)] != 0;
|
||||
}
|
||||
|
||||
void BisSave::SetNeedsVerification(BootModeType mode, bool needs_verification) {
|
||||
reinterpret_cast<u8 *>(this->save_buffer)[GetVerificationFlagOffset(mode)] = needs_verification ? 1 : 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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/updater/updater_types.hpp>
|
||||
|
||||
#include "updater_bis_management.hpp"
|
||||
|
||||
namespace sts::updater {
|
||||
|
||||
class BisSave {
|
||||
public:
|
||||
static constexpr size_t SaveSize = BctSize;
|
||||
private:
|
||||
Boot0Accessor accessor;
|
||||
void *save_buffer;
|
||||
public:
|
||||
BisSave() : save_buffer(nullptr) { }
|
||||
private:
|
||||
static size_t GetVerificationFlagOffset(BootModeType mode);
|
||||
public:
|
||||
Result Initialize(void *work_buffer, size_t work_buffer_size);
|
||||
void Finalize();
|
||||
|
||||
Result Load();
|
||||
Result Save();
|
||||
bool GetNeedsVerification(BootModeType mode);
|
||||
void SetNeedsVerification(BootModeType mode, bool needs_verification);
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 "updater_files.hpp"
|
||||
|
||||
namespace sts::updater {
|
||||
|
||||
Result ReadFile(size_t *out_size, void *dst, size_t dst_size, const char *path) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (fp == NULL) {
|
||||
return ResultUpdaterInvalidBootImagePackage;
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(fp); };
|
||||
|
||||
std::memset(dst, 0, dst_size);
|
||||
size_t read_size = fread(dst, 1, dst_size, fp);
|
||||
if (ferror(fp)) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
*out_size = read_size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result GetFileHash(size_t *out_size, void *dst_hash, const char *path, void *work_buffer, size_t work_buffer_size) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (fp == NULL) {
|
||||
return ResultUpdaterInvalidBootImagePackage;
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(fp); };
|
||||
|
||||
Sha256Context sha_ctx;
|
||||
sha256ContextCreate(&sha_ctx);
|
||||
|
||||
size_t total_size = 0;
|
||||
while (true) {
|
||||
size_t read_size = fread(work_buffer, 1, work_buffer_size, fp);
|
||||
if (ferror(fp)) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
if (read_size == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
sha256ContextUpdate(&sha_ctx, work_buffer, read_size);
|
||||
total_size += read_size;
|
||||
if (read_size != work_buffer_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sha256ContextGetHash(&sha_ctx, dst_hash);
|
||||
*out_size = total_size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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/updater/updater_types.hpp>
|
||||
|
||||
namespace sts::updater {
|
||||
|
||||
/* File helpers. */
|
||||
Result ReadFile(size_t *out_size, void *dst, size_t dst_size, const char *path);
|
||||
Result GetFileHash(size_t *out_size, void *dst_hash, const char *path, void *work_buffer, size_t work_buffer_size);
|
||||
|
||||
}
|
119
stratosphere/libstratosphere/source/updater/updater_paths.cpp
Normal file
119
stratosphere/libstratosphere/source/updater/updater_paths.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* 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 <sys/stat.h>
|
||||
|
||||
#include "updater_paths.hpp"
|
||||
|
||||
namespace sts::updater {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Actual paths. */
|
||||
constexpr const char *BootImagePackageMountPath = "bip";
|
||||
constexpr const char *BctPathNx = "bip:/nx/bct";
|
||||
constexpr const char *Package1PathNx = "bip:/nx/package1";
|
||||
constexpr const char *Package2PathNx = "bip:/nx/package2";
|
||||
constexpr const char *BctPathA = "bip:/a/bct";
|
||||
constexpr const char *Package1PathA = "bip:/a/package1";
|
||||
constexpr const char *Package2PathA = "bip:/a/package2";
|
||||
|
||||
const char *ChooseCandidatePath(const char * const *candidates, size_t num_candidates) {
|
||||
if (num_candidates == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_candidates; i++) {
|
||||
struct stat buf;
|
||||
if (stat(candidates[i], &buf) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!S_ISREG(buf.st_mode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return candidates[i];
|
||||
}
|
||||
|
||||
/* Nintendo just uses the last candidate if they all fail...should we abort? */
|
||||
return candidates[num_candidates - 1];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const char *GetBootImagePackageMountPath() {
|
||||
return BootImagePackageMountPath;
|
||||
}
|
||||
|
||||
|
||||
const char *GetBctPath(BootImageUpdateType boot_image_update_type) {
|
||||
switch (boot_image_update_type) {
|
||||
case BootImageUpdateType::Erista:
|
||||
{
|
||||
constexpr const char *candidates[] = {BctPathNx};
|
||||
return ChooseCandidatePath(candidates, util::size(candidates));
|
||||
}
|
||||
case BootImageUpdateType::Mariko:
|
||||
{
|
||||
constexpr const char *candidates[] = {BctPathA, BctPathNx};
|
||||
return ChooseCandidatePath(candidates, util::size(candidates));
|
||||
}
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
const char *GetPackage1Path(BootImageUpdateType boot_image_update_type) {
|
||||
switch (boot_image_update_type) {
|
||||
case BootImageUpdateType::Erista:
|
||||
{
|
||||
constexpr const char *candidates[] = {Package1PathNx};
|
||||
return ChooseCandidatePath(candidates, util::size(candidates));
|
||||
}
|
||||
case BootImageUpdateType::Mariko:
|
||||
{
|
||||
constexpr const char *candidates[] = {Package1PathA, Package1PathNx};
|
||||
return ChooseCandidatePath(candidates, util::size(candidates));
|
||||
}
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
const char *GetPackage2Path(BootImageUpdateType boot_image_update_type) {
|
||||
switch (boot_image_update_type) {
|
||||
case BootImageUpdateType::Erista:
|
||||
{
|
||||
constexpr const char *candidates[] = {Package2PathNx};
|
||||
return ChooseCandidatePath(candidates, util::size(candidates));
|
||||
}
|
||||
case BootImageUpdateType::Mariko:
|
||||
{
|
||||
constexpr const char *candidates[] = {Package2PathA, Package2PathNx};
|
||||
return ChooseCandidatePath(candidates, util::size(candidates));
|
||||
}
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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/updater/updater_types.hpp>
|
||||
|
||||
namespace sts::updater {
|
||||
|
||||
/* Path functionality. */
|
||||
const char *GetBootImagePackageMountPath();
|
||||
const char *GetBctPath(BootImageUpdateType boot_image_update_type);
|
||||
const char *GetPackage1Path(BootImageUpdateType boot_image_update_type);
|
||||
const char *GetPackage2Path(BootImageUpdateType boot_image_update_type);
|
||||
|
||||
}
|
269
stratosphere/libstratosphere/source/util/ini.c
Normal file
269
stratosphere/libstratosphere/source/util/ini.c
Normal file
|
@ -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 <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ini.h"
|
||||
|
||||
#if !INI_USE_STACK
|
||||
#include <stdlib.h>
|
||||
#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);
|
||||
}
|
130
stratosphere/libstratosphere/source/util/ini.h
Normal file
130
stratosphere/libstratosphere/source/util/ini.h
Normal file
|
@ -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 <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__ */
|
1857
stratosphere/libstratosphere/source/util/lz4.c
Normal file
1857
stratosphere/libstratosphere/source/util/lz4.c
Normal file
File diff suppressed because it is too large
Load diff
569
stratosphere/libstratosphere/source/util/lz4.h
Normal file
569
stratosphere/libstratosphere/source/util/lz4.h
Normal file
|
@ -0,0 +1,569 @@
|
|||
/*
|
||||
* 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
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 <stratosphere/util.hpp>
|
||||
|
||||
#include "lz4.h"
|
||||
|
||||
namespace sts::util {
|
||||
|
||||
/* Compression utilities. */
|
||||
int CompressLZ4(void *dst, size_t dst_size, const void *src, size_t src_size) {
|
||||
/* Size checks. */
|
||||
if (dst_size > std::numeric_limits<int>::max() || src_size > std::numeric_limits<int>::max()) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* This is just a thin wrapper around LZ4. */
|
||||
return LZ4_compress_default(reinterpret_cast<const char *>(src), reinterpret_cast<char *>(dst), static_cast<int>(src_size), static_cast<int>(dst_size));
|
||||
}
|
||||
|
||||
/* Decompression utilities. */
|
||||
int DecompressLZ4(void *dst, size_t dst_size, const void *src, size_t src_size) {
|
||||
/* Size checks. */
|
||||
if (dst_size > std::numeric_limits<int>::max() || src_size > std::numeric_limits<int>::max()) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* This is just a thin wrapper around LZ4. */
|
||||
return LZ4_decompress_safe(reinterpret_cast<const char *>(src), reinterpret_cast<char *>(dst), static_cast<int>(src_size), static_cast<int>(dst_size));
|
||||
}
|
||||
|
||||
}
|
96
stratosphere/libstratosphere/source/util/util_ini.cpp
Normal file
96
stratosphere/libstratosphere/source/util/util_ini.cpp
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 <stratosphere/util.hpp>
|
||||
|
||||
#include "ini.h"
|
||||
|
||||
namespace sts::util::ini {
|
||||
|
||||
/* Ensure that types are the same for Handler vs ini_handler. */
|
||||
static_assert(std::is_same<Handler, ::ini_handler>::value, "Bad ini::Handler definition!");
|
||||
|
||||
namespace {
|
||||
|
||||
struct FsFileContext {
|
||||
FsFile *f;
|
||||
size_t offset;
|
||||
size_t num_left;
|
||||
|
||||
explicit FsFileContext(FsFile *f) : f(f), offset(0) {
|
||||
u64 size;
|
||||
R_ASSERT(fsFileGetSize(this->f, &size));
|
||||
this->num_left = size_t(size);
|
||||
}
|
||||
};
|
||||
|
||||
char *ini_reader_fs_file(char *str, int num, void *stream) {
|
||||
FsFileContext *ctx = reinterpret_cast<FsFileContext *>(stream);
|
||||
|
||||
if (ctx->num_left == 0 || num < 2) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Read as many bytes as we can. */
|
||||
size_t try_read = std::min(size_t(num - 1), ctx->num_left);
|
||||
size_t actually_read;
|
||||
R_ASSERT(fsFileRead(ctx->f, ctx->offset, str, try_read, FS_READOPTION_NONE, &actually_read));
|
||||
if (actually_read != try_read) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Only "read" up to the first \n. */
|
||||
size_t offset = actually_read;
|
||||
for (size_t i = 0; i < actually_read; i++) {
|
||||
if (str[i] == '\n') {
|
||||
offset = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure null termination. */
|
||||
str[offset] = '\0';
|
||||
|
||||
/* Update context. */
|
||||
ctx->offset += offset;
|
||||
ctx->num_left -= offset;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Utilities for dealing with INI file configuration. */
|
||||
int ParseString(const char *ini_str, void *user_ctx, Handler h) {
|
||||
return ini_parse_string(ini_str, h, user_ctx);
|
||||
}
|
||||
|
||||
int ParseFile(FILE *f, void *user_ctx, Handler h) {
|
||||
return ini_parse_file(f, h, user_ctx);
|
||||
}
|
||||
|
||||
int ParseFile(FsFile *f, void *user_ctx, Handler h) {
|
||||
FsFileContext ctx(f);
|
||||
return ini_parse_stream(ini_reader_fs_file, &ctx, h, user_ctx);
|
||||
}
|
||||
|
||||
int ParseFile(const char *path, void *user_ctx, Handler h) {
|
||||
return ini_parse(path, h, user_ctx);
|
||||
}
|
||||
|
||||
}
|
30
stratosphere/libstratosphere/source/utilities.cpp
Normal file
30
stratosphere/libstratosphere/source/utilities.cpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
static HosRecursiveMutex g_sm_session_lock;
|
||||
static HosRecursiveMutex g_sm_mitm_session_lock;
|
||||
|
||||
|
||||
HosRecursiveMutex &GetSmSessionMutex() {
|
||||
return g_sm_session_lock;
|
||||
}
|
||||
|
||||
HosRecursiveMutex &GetSmMitmSessionMutex() {
|
||||
return g_sm_mitm_session_lock;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue