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:
Michael Scire 2019-07-17 20:04:00 -07:00
parent 3ea9f444db
commit f534d3498e
166 changed files with 20474 additions and 0 deletions

View 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;
}

View 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);
}
}

View 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;
}
}

View file

@ -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;
}
}

View 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();
}
}

View 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;
}

View 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;
}

View 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));
}

View 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;
}
}

View 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;
}
}

View file

@ -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;
}
}

View 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);
}

View 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

View 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));
}
}

View 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;
}
}

View 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];
}

View 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());
}
}

View 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)
);
}
}

View 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);
}
}
}

View 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;
}

View 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

View 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());
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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;
}

View 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

View 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);
});
}
}

View 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);
}
}

View 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);
});
}
}

View 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;
}
}

View 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();
}
}
}

View 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;
}

View 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

View 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]);
}
}

View 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();
}
}
}

View 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;
}
}

View file

@ -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;
}
}

View 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/>.
*/
#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)) { }
};
}

View file

@ -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;
}
}

View file

@ -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);
};
}

View file

@ -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;
}
}

View 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/>.
*/
#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);
}

View 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();
}
}
}

View 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/>.
*/
#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);
}

View 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);
}

View 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__ */

File diff suppressed because it is too large Load diff

View 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

View 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 <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));
}
}

View 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);
}
}

View 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;
}