subrepo:
  subdir:   "libraries"
  merged:   "07af583b"
upstream:
  origin:   "https://github.com/Atmosphere-NX/Atmosphere-libs"
  branch:   "master"
  commit:   "07af583b"
git-subrepo:
  version:  "0.4.0"
  origin:   "https://github.com/ingydotnet/git-subrepo"
  commit:   "5d6aba9"
This commit is contained in:
Michael Scire 2019-12-09 03:57:37 -08:00 committed by SciresM
parent 28717bfd27
commit 0105455086
294 changed files with 29915 additions and 0 deletions

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/>.
*/
#define NX_SERVICE_ASSUME_NON_DOMAIN
#include "../service_guard.h"
#include "ams_bpc.h"
static Service g_amsBpcSrv;
NX_GENERATE_SERVICE_GUARD(amsBpc);
Result _amsBpcInitialize(void) {
Handle h;
Result rc = svcConnectToNamedPort(&h, "bpc:ams"); /* TODO: ams:bpc */
if (R_SUCCEEDED(rc)) serviceCreate(&g_amsBpcSrv, h);
return rc;
}
void _amsBpcCleanup(void) {
serviceClose(&g_amsBpcSrv);
}
Service *amsBpcGetServiceSession(void) {
return &g_amsBpcSrv;
}
Result amsBpcRebootToFatalError(void *ctx) {
/* Note: this takes in an sts::ams::FatalErrorContext. */
/* static_assert(sizeof() == 0x350) is done at type definition. */
return serviceDispatch(&g_amsBpcSrv, 65000,
.buffer_attrs = { SfBufferAttr_Out | SfBufferAttr_HipcMapAlias | SfBufferAttr_FixedSize },
.buffers = { { ctx, 0x350 } },
);
}

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/>.
*/
#pragma once
#include <switch/types.h>
#include <switch/kernel/event.h>
#include <switch/services/sm.h>
#ifdef __cplusplus
extern "C" {
#endif
Result amsBpcInitialize(void);
void amsBpcExit(void);
Service *amsBpcGetServiceSession(void);
Result amsBpcRebootToFatalError(void *ctx);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,137 @@
/*
* 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>
namespace ams::emummc {
namespace {
/* Convenience Definitions. */
constexpr u32 StorageMagic = 0x30534645; /* EFS0 */
constexpr size_t MaxDirLen = 0x7F;
/* Types. */
struct BaseConfig {
u32 magic;
u32 type;
u32 id;
u32 fs_version;
};
struct PartitionConfig {
u64 start_sector;
};
struct FileConfig {
char path[MaxDirLen + 1];
};
struct ExosphereConfig {
BaseConfig base_cfg;
union {
PartitionConfig partition_cfg;
FileConfig file_cfg;
};
char emu_dir_path[MaxDirLen + 1];
};
enum Storage : u32 {
Storage_Emmc,
Storage_Sd,
Storage_SdFile,
Storage_Count,
};
/* Globals. */
os::Mutex g_lock;
ExosphereConfig g_exo_config;
bool g_is_emummc;
bool g_has_cached;
/* Helpers. */
void CacheValues() {
std::scoped_lock lk(g_lock);
if (g_has_cached) {
return;
}
/* Retrieve and cache values. */
{
typename std::aligned_storage<2 * (MaxDirLen + 1), os::MemoryPageSize>::type path_storage;
struct {
char file_path[MaxDirLen + 1];
char nintendo_path[MaxDirLen + 1];
} *paths = reinterpret_cast<decltype(paths)>(&path_storage);
/* Retrieve paths from secure monitor. */
AMS_ASSERT(spl::smc::AtmosphereGetEmummcConfig(&g_exo_config, paths, 0) == spl::smc::Result::Success);
const Storage storage = static_cast<Storage>(g_exo_config.base_cfg.type);
g_is_emummc = g_exo_config.base_cfg.magic == StorageMagic && storage != Storage_Emmc;
/* Format paths. Ignore string format warnings. */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
{
if (storage == Storage_SdFile) {
std::snprintf(g_exo_config.file_cfg.path, sizeof(g_exo_config.file_cfg.path), "/%s", paths->file_path);
}
std::snprintf(g_exo_config.emu_dir_path, sizeof(g_exo_config.emu_dir_path), "/%s", paths->nintendo_path);
/* If we're emummc, implement default nintendo redirection path. */
if (g_is_emummc && std::strcmp(g_exo_config.emu_dir_path, "/") == 0) {
std::snprintf(g_exo_config.emu_dir_path, sizeof(g_exo_config.emu_dir_path), "/emummc/Nintendo_%04x", g_exo_config.base_cfg.id);
}
}
#pragma GCC diagnostic pop
}
g_has_cached = true;
}
}
/* Get whether emummc is active. */
bool IsActive() {
CacheValues();
return g_is_emummc;
}
/* Get Nintendo redirection path. */
const char *GetNintendoDirPath() {
CacheValues();
if (!g_is_emummc) {
return nullptr;
}
return g_exo_config.emu_dir_path;
}
/* Get Emummc folderpath, NULL if not file-based. */
const char *GetFilePath() {
CacheValues();
if (!g_is_emummc || g_exo_config.base_cfg.type != Storage_SdFile) {
return nullptr;
}
return g_exo_config.file_cfg.path;
}
}

View file

@ -0,0 +1,176 @@
/*
* 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 "ams_bpc.h"
namespace ams {
namespace {
inline u64 GetPc() {
u64 pc;
__asm__ __volatile__ ("adr %[pc], ." : [pc]"=&r"(pc) :: );
return pc;
}
struct StackFrame {
u64 fp;
u64 lr;
};
}
extern ncm::ProgramId CurrentProgramId;
void WEAK ExceptionHandler(FatalErrorContext *ctx) {
R_ASSERT(amsBpcInitialize());
R_ASSERT(amsBpcRebootToFatalError(ctx));
while (1) { /* ... */ }
}
void CrashHandler(ThreadExceptionDump *ctx) {
FatalErrorContext ams_ctx;
/* Convert thread dump to atmosphere dump. */
{
ams_ctx.magic = FatalErrorContext::Magic;
ams_ctx.error_desc = ctx->error_desc;
ams_ctx.program_id = static_cast<u64>(CurrentProgramId);
for (size_t i = 0; i < FatalErrorContext::NumGprs; i++) {
ams_ctx.gprs[i] = ctx->cpu_gprs[i].x;
}
if (ams_ctx.error_desc == FatalErrorContext::DataAbortErrorDesc &&
ams_ctx.gprs[27] == FatalErrorContext::StdAbortMagicAddress &&
ams_ctx.gprs[28] == FatalErrorContext::StdAbortMagicValue)
{
/* Detect std::abort(). */
ams_ctx.error_desc = FatalErrorContext::StdAbortErrorDesc;
}
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 < FatalErrorContext::MaxStackTrace; 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 < FatalErrorContext::MaxStackTrace; 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(FatalErrorContext::MaxStackDumpSize, 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. */
::ams::ExceptionHandler(&ams_ctx);
}
inline NORETURN void AbortImpl() {
/* Just perform a data abort. */
register u64 addr __asm__("x27") = FatalErrorContext::StdAbortMagicAddress;
register u64 val __asm__("x28") = FatalErrorContext::StdAbortMagicValue;
while (true) {
__asm__ __volatile__ (
"str %[val], [%[addr]]"
:
: [val]"r"(val), [addr]"r"(addr)
);
}
__builtin_unreachable();
}
}
extern "C" {
/* Redefine abort to trigger these handlers. */
void abort();
/* Redefine C++ exception handlers. Requires wrap linker flag. */
#define WRAP_ABORT_FUNC(func) void NORETURN __wrap_##func(void) { abort(); __builtin_unreachable(); }
WRAP_ABORT_FUNC(__cxa_pure_virtual)
WRAP_ABORT_FUNC(__cxa_throw)
WRAP_ABORT_FUNC(__cxa_rethrow)
WRAP_ABORT_FUNC(__cxa_allocate_exception)
WRAP_ABORT_FUNC(__cxa_free_exception)
WRAP_ABORT_FUNC(__cxa_begin_catch)
WRAP_ABORT_FUNC(__cxa_end_catch)
WRAP_ABORT_FUNC(__cxa_call_unexpected)
WRAP_ABORT_FUNC(__cxa_call_terminate)
WRAP_ABORT_FUNC(__gxx_personality_v0)
WRAP_ABORT_FUNC(_ZSt19__throw_logic_errorPKc)
WRAP_ABORT_FUNC(_ZSt20__throw_length_errorPKc)
WRAP_ABORT_FUNC(_ZNSt11logic_errorC2EPKc)
/* TODO: We may wish to consider intentionally not defining an _Unwind_Resume wrapper. */
/* This would mean that a failure to wrap all exception functions is a linker error. */
WRAP_ABORT_FUNC(_Unwind_Resume)
#undef WRAP_ABORT_FUNC
}
/* Custom abort handler, so that std::abort will trigger these. */
void abort() {
ams::AbortImpl();
__builtin_unreachable();
}

View file

@ -0,0 +1,75 @@
/*
* 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>
#include <stratosphere/spl/smc/spl_smc.hpp>
namespace ams::exosphere {
ApiInfo GetApiInfo() {
u64 exosphere_cfg;
if (spl::smc::GetConfig(&exosphere_cfg, 1, SplConfigItem_ExosphereApiVersion) != spl::smc::Result::Success) {
R_ASSERT(ResultNotPresent());
}
return ApiInfo{
.major_version = static_cast<u32>((exosphere_cfg >> 0x20) & 0xFF),
.minor_version = static_cast<u32>((exosphere_cfg >> 0x18) & 0xFF),
.micro_version = static_cast<u32>((exosphere_cfg >> 0x10) & 0xFF),
.target_firmware = static_cast<TargetFirmware>((exosphere_cfg >> 0x08) & 0xFF),
.master_key_revision = static_cast<u32>((exosphere_cfg >> 0x00) & 0xFF),
};
}
void ForceRebootToRcm() {
R_ASSERT(spl::smc::ConvertResult(spl::smc::SetConfig(SplConfigItem_ExosphereNeedsReboot, 1)));
}
void ForceRebootToIramPayload() {
R_ASSERT(spl::smc::ConvertResult(spl::smc::SetConfig(SplConfigItem_ExosphereNeedsReboot, 2)));
}
void ForceShutdown() {
R_ASSERT(spl::smc::ConvertResult(spl::smc::SetConfig(SplConfigItem_ExosphereNeedsShutdown, 1)));
}
void CopyToIram(uintptr_t iram_dst, const void *dram_src, size_t size) {
spl::smc::AtmosphereCopyToIram(iram_dst, dram_src, size);
}
void CopyFromIram(void *dram_dst, uintptr_t iram_src, size_t size) {
spl::smc::AtmosphereCopyFromIram(dram_dst, iram_src, size);
}
namespace {
inline Result GetRcmBugPatched(bool *out) {
u64 tmp = 0;
R_TRY(spl::smc::ConvertResult(spl::smc::GetConfig(&tmp, 1, SplConfigItem_ExosphereHasRcmBugPatch)));
*out = (tmp != 0);
return ResultSuccess();
}
}
bool IsRcmBugPatched() {
bool rcm_bug_patched;
R_ASSERT(GetRcmBugPatched(&rcm_bug_patched));
return rcm_bug_patched;
}
}

View file

@ -0,0 +1,349 @@
/*
* 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 <dirent.h>
#include <stratosphere.hpp>
namespace ams::boot2 {
namespace {
/* Launch lists. */
/* psc, bus, pcv is the minimal set of required programs to get SD card. */
/* bus depends on pcie, and pcv depends on settings. */
constexpr ncm::ProgramId PreSdCardLaunchPrograms[] = {
ncm::ProgramId::Psc, /* psc */
ncm::ProgramId::Pcie, /* pcie */
ncm::ProgramId::Bus, /* bus */
ncm::ProgramId::Settings, /* settings */
ncm::ProgramId::Pcv, /* pcv */
ncm::ProgramId::Usb, /* usb */
};
constexpr size_t NumPreSdCardLaunchPrograms = util::size(PreSdCardLaunchPrograms);
constexpr ncm::ProgramId AdditionalLaunchPrograms[] = {
ncm::ProgramId::Tma, /* tma */
ncm::ProgramId::Am, /* am */
ncm::ProgramId::NvServices, /* nvservices */
ncm::ProgramId::NvnFlinger, /* nvnflinger */
ncm::ProgramId::Vi, /* vi */
ncm::ProgramId::Ns, /* ns */
ncm::ProgramId::LogManager, /* lm */
ncm::ProgramId::Ppc, /* ppc */
ncm::ProgramId::Ptm, /* ptm */
ncm::ProgramId::Hid, /* hid */
ncm::ProgramId::Audio, /* audio */
ncm::ProgramId::Lbl, /* lbl */
ncm::ProgramId::Wlan, /* wlan */
ncm::ProgramId::Bluetooth, /* bluetooth */
ncm::ProgramId::BsdSockets, /* bsdsockets */
ncm::ProgramId::Nifm, /* nifm */
ncm::ProgramId::Ldn, /* ldn */
ncm::ProgramId::Account, /* account */
ncm::ProgramId::Friends, /* friends */
ncm::ProgramId::Nfc, /* nfc */
ncm::ProgramId::JpegDec, /* jpegdec */
ncm::ProgramId::CapSrv, /* capsrv */
ncm::ProgramId::Ssl, /* ssl */
ncm::ProgramId::Nim, /* nim */
ncm::ProgramId::Bcat, /* bcat */
ncm::ProgramId::Erpt, /* erpt */
ncm::ProgramId::Es, /* es */
ncm::ProgramId::Pctl, /* pctl */
ncm::ProgramId::Btm, /* btm */
ncm::ProgramId::Eupld, /* eupld */
ncm::ProgramId::Glue, /* glue */
/* ncm::ProgramId::Eclct, */ /* eclct */ /* Skip launching error collection in Atmosphere to lessen telemetry. */
ncm::ProgramId::Npns, /* npns */
ncm::ProgramId::Fatal, /* fatal */
ncm::ProgramId::Ro, /* ro */
ncm::ProgramId::Profiler, /* profiler */
ncm::ProgramId::Sdb, /* sdb */
ncm::ProgramId::Migration, /* migration */
ncm::ProgramId::Grc, /* grc */
ncm::ProgramId::Olsc, /* olsc */
ncm::ProgramId::Ngct, /* ngct */
};
constexpr size_t NumAdditionalLaunchPrograms = util::size(AdditionalLaunchPrograms);
constexpr ncm::ProgramId AdditionalMaintenanceLaunchPrograms[] = {
ncm::ProgramId::Tma, /* tma */
ncm::ProgramId::Am, /* am */
ncm::ProgramId::NvServices, /* nvservices */
ncm::ProgramId::NvnFlinger, /* nvnflinger */
ncm::ProgramId::Vi, /* vi */
ncm::ProgramId::Ns, /* ns */
ncm::ProgramId::LogManager, /* lm */
ncm::ProgramId::Ppc, /* ppc */
ncm::ProgramId::Ptm, /* ptm */
ncm::ProgramId::Hid, /* hid */
ncm::ProgramId::Audio, /* audio */
ncm::ProgramId::Lbl, /* lbl */
ncm::ProgramId::Wlan, /* wlan */
ncm::ProgramId::Bluetooth, /* bluetooth */
ncm::ProgramId::BsdSockets, /* bsdsockets */
ncm::ProgramId::Nifm, /* nifm */
ncm::ProgramId::Ldn, /* ldn */
ncm::ProgramId::Account, /* account */
ncm::ProgramId::Nfc, /* nfc */
ncm::ProgramId::JpegDec, /* jpegdec */
ncm::ProgramId::CapSrv, /* capsrv */
ncm::ProgramId::Ssl, /* ssl */
ncm::ProgramId::Nim, /* nim */
ncm::ProgramId::Erpt, /* erpt */
ncm::ProgramId::Es, /* es */
ncm::ProgramId::Pctl, /* pctl */
ncm::ProgramId::Btm, /* btm */
ncm::ProgramId::Glue, /* glue */
/* ncm::ProgramId::Eclct, */ /* eclct */ /* Skip launching error collection in Atmosphere to lessen telemetry. */
ncm::ProgramId::Fatal, /* fatal */
ncm::ProgramId::Ro, /* ro */
ncm::ProgramId::Profiler, /* profiler */
ncm::ProgramId::Sdb, /* sdb */
ncm::ProgramId::Migration, /* migration */
ncm::ProgramId::Grc, /* grc */
ncm::ProgramId::Olsc, /* olsc */
ncm::ProgramId::Ngct, /* ngct */
};
constexpr size_t NumAdditionalMaintenanceLaunchPrograms = util::size(AdditionalMaintenanceLaunchPrograms);
/* Helpers. */
inline bool IsHexadecimal(const char *str) {
while (*str) {
if (!std::isxdigit(static_cast<unsigned char>(*(str++)))) {
return false;
}
}
return true;
}
inline bool IsNewLine(char c) {
return c == '\r' || c == '\n';
}
void LaunchProgram(os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 launch_flags) {
os::ProcessId process_id = os::InvalidProcessId;
/* Launch, lightly validate result. */
{
const auto launch_result = pm::shell::LaunchProgram(&process_id, loc, launch_flags);
AMS_ASSERT(!(svc::ResultOutOfResource::Includes(launch_result)));
AMS_ASSERT(!(svc::ResultOutOfMemory::Includes(launch_result)));
AMS_ASSERT(!(svc::ResultLimitReached::Includes(launch_result)));
}
if (out_process_id) {
*out_process_id = process_id;
}
}
void LaunchList(const ncm::ProgramId *launch_list, size_t num_entries) {
for (size_t i = 0; i < num_entries; i++) {
LaunchProgram(nullptr, ncm::ProgramLocation::Make(launch_list[i], ncm::StorageId::BuiltInSystem), 0);
}
}
bool GetGpioPadLow(GpioPadName pad) {
GpioPadSession button;
if (R_FAILED(gpioOpenSession(&button, pad))) {
return false;
}
/* Ensure we close even on early return. */
ON_SCOPE_EXIT { gpioPadClose(&button); };
/* Set direction input. */
gpioPadSetDirection(&button, GpioDirection_Input);
GpioValue val;
return R_SUCCEEDED(gpioPadGetValue(&button, &val)) && val == GpioValue_Low;
}
bool IsMaintenanceMode() {
/* Contact set:sys, retrieve boot!force_maintenance. */
{
u8 force_maintenance = 1;
settings::fwdbg::GetSettingsItemValue(&force_maintenance, sizeof(force_maintenance), "boot", "force_maintenance");
if (force_maintenance != 0) {
return true;
}
}
/* Contact GPIO, read plus/minus buttons. */
{
return GetGpioPadLow(GpioPadName_ButtonVolUp) && GetGpioPadLow(GpioPadName_ButtonVolDown);
}
}
template<typename F>
void IterateOverFlaggedProgramsOnSdCard(F f) {
/* Validate that the contents directory exists. */
DIR *contents_dir = opendir("sdmc:/atmosphere/contents");
if (contents_dir == nullptr) {
return;
}
ON_SCOPE_EXIT { closedir(contents_dir); };
/* Iterate over entries in the contents directory */
struct dirent *ent;
while ((ent = readdir(contents_dir)) != nullptr) {
/* Check that the subdirectory can be converted to a program id. */
if (std::strlen(ent->d_name) == 2 * sizeof(ncm::ProgramId) && IsHexadecimal(ent->d_name)) {
/* Check if we've already launched the program. */
ncm::ProgramId program_id{std::strtoul(ent->d_name, nullptr, 16)};
/* Check if the program is flagged. */
if (!cfg::HasContentSpecificFlag(program_id, "boot2")) {
continue;
}
/* Call the iteration callback. */
f(program_id);
}
}
}
void DetectAndDeclareFutureMitms() {
IterateOverFlaggedProgramsOnSdCard([](ncm::ProgramId program_id) {
/* When we find a flagged program, check if it has a mitm list. */
char mitm_list[0x400];
size_t mitm_list_size = 0;
/* Read the mitm list off the SD card. */
{
char path[FS_MAX_PATH];
std::snprintf(mitm_list, sizeof(mitm_list), "sdmc:/atmosphere/contents/%016lx/mitm.lst", static_cast<u64>(program_id));
FILE *f = fopen(path, "rb");
if (f == nullptr) {
return;
}
mitm_list_size = static_cast<size_t>(fread(mitm_list, 1, sizeof(mitm_list), f));
fclose(f);
}
/* Validate read size. */
if (mitm_list_size > sizeof(mitm_list) || mitm_list_size == 0) {
return;
}
/* Iterate over the contents of the file. */
/* We expect one service name per non-empty line, 1-8 characters. */
size_t offset = 0;
while (offset < mitm_list_size) {
/* Continue to the start of the next name. */
while (IsNewLine(mitm_list[offset])) {
offset++;
}
if (offset >= mitm_list_size) {
break;
}
/* Get the length of the current name. */
size_t name_len;
for (name_len = 0; name_len <= sizeof(sm::ServiceName) && offset + name_len < mitm_list_size; name_len++) {
if (IsNewLine(mitm_list[offset + name_len])) {
break;
}
}
/* Allow empty lines. */
if (name_len == 0) {
continue;
}
/* Don't allow invalid lines. */
AMS_ASSERT(name_len <= sizeof(sm::ServiceName));
/* Declare the service. */
R_ASSERT(sm::mitm::DeclareFutureMitm(sm::ServiceName::Encode(mitm_list + offset, name_len)));
/* Advance to the next line. */
offset += name_len;
}
});
}
void LaunchFlaggedProgramsOnSdCard() {
IterateOverFlaggedProgramsOnSdCard([](ncm::ProgramId program_id) {
/* Check if we've already launched the program. */
if (pm::info::HasLaunchedProgram(program_id)) {
return;
}
/* Launch the program. */
LaunchProgram(nullptr, ncm::ProgramLocation::Make(program_id, ncm::StorageId::None), 0);
});
}
}
/* Boot2 API. */
void LaunchPreSdCardBootProgramsAndBoot2() {
/* This code is normally run by PM. */
/* Wait until fs.mitm has installed itself. We want this to happen as early as possible. */
R_ASSERT(sm::mitm::WaitMitm(sm::ServiceName::Encode("fsp-srv")));
/* Launch programs required to mount the SD card. */
LaunchList(PreSdCardLaunchPrograms, NumPreSdCardLaunchPrograms);
/* Wait for the SD card required services to be ready. */
cfg::WaitSdCardRequiredServicesReady();
/* Wait for other atmosphere mitm modules to initialize. */
R_ASSERT(sm::mitm::WaitMitm(sm::ServiceName::Encode("set:sys")));
if (hos::GetVersion() >= hos::Version_200) {
R_ASSERT(sm::mitm::WaitMitm(sm::ServiceName::Encode("bpc")));
} else {
R_ASSERT(sm::mitm::WaitMitm(sm::ServiceName::Encode("bpc:c")));
}
/* Launch Atmosphere boot2, using NcmStorageId_None to force SD card boot. */
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::ProgramId::Boot2, ncm::StorageId::None), 0);
}
void LaunchPostSdCardBootPrograms() {
/* This code is normally run by boot2. */
/* Find out whether we are maintenance mode. */
const bool maintenance = IsMaintenanceMode();
if (maintenance) {
pm::bm::SetMaintenanceBoot();
}
/* Launch Atmosphere dmnt, using NcmStorageId_None to force SD card boot. */
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::ProgramId::Dmnt, ncm::StorageId::None), 0);
/* Check for and forward declare non-atmosphere mitm modules. */
DetectAndDeclareFutureMitms();
/* Launch additional programs. */
if (maintenance) {
LaunchList(AdditionalMaintenanceLaunchPrograms, NumAdditionalMaintenanceLaunchPrograms);
/* Starting in 7.0.0, npns is launched during maintenance boot. */
if (hos::GetVersion() >= hos::Version_700) {
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::ProgramId::Npns, ncm::StorageId::BuiltInSystem), 0);
}
} else {
LaunchList(AdditionalLaunchPrograms, NumAdditionalLaunchPrograms);
}
/* Launch user programs off of the SD. */
LaunchFlaggedProgramsOnSdCard();
}
}

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 <stratosphere.hpp>
namespace ams::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(fsOpenSdCardFileSystem(&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, FsOpenMode_Read, &flag_file))) {
return false;
}
fsFileClose(&flag_file);
return true;
}
}
/* Flag utilities. */
bool HasFlag(const sm::MitmProcessInfo &process_info, const char *flag) {
return HasContentSpecificFlag(process_info.program_id, flag) || (process_info.override_status.IsHbl() && HasHblFlag(flag));
}
bool HasContentSpecificFlag(ncm::ProgramId program_id, const char *flag) {
char content_flag[FS_MAX_PATH];
std::snprintf(content_flag, sizeof(content_flag) - 1, "/atmosphere/contents/%016lx/flags/%s.flag", static_cast<u64>(program_id), flag);
return HasFlagFile(content_flag);
}
bool HasGlobalFlag(const char *flag) {
char global_flag[FS_MAX_PATH];
std::snprintf(global_flag, sizeof(global_flag) - 1, "/atmosphere/flags/%s.flag", flag);
return HasFlagFile(global_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,308 @@
/*
* 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>
namespace ams::cfg {
namespace {
/* Types. */
struct OverrideKey {
u64 key_combination;
bool override_by_default;
};
struct HblOverrideConfig {
OverrideKey override_key;
OverrideKey override_any_app_key;
ncm::ProgramId program_id;
bool override_any_app;
};
struct ContentSpecificOverrideConfig {
OverrideKey override_key;
OverrideKey cheat_enable_key;
OverrideLocale locale;
};
/* 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 = true,
},
.override_any_app_key = {
.key_combination = KEY_R,
.override_by_default = false,
},
.program_id = ncm::ProgramId::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 OverrideConfigIniHandler(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) {
/* TODO: Consider deprecating "title_id" string in the future." */
if (strcasecmp(name, "program_id") == 0 || strcasecmp(name, "title_id") == 0) {
u64 override_program_id = strtoul(value, NULL, 16);
if (override_program_id != 0) {
g_hbl_override_config.program_id = {override_program_id};
}
} 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';
for (size_t i = 0; i < sizeof(g_hbl_sd_path); i++) {
if (g_hbl_sd_path[i] == '\\') {
g_hbl_sd_path[i] = '/';
}
}
} 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(name, "override_any_app_key") == 0) {
g_hbl_override_config.override_any_app_key = ParseOverrideKey(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 ContentSpecificIniHandler(void *user, const char *section, const char *name, const char *value) {
ContentSpecificOverrideConfig *config = reinterpret_cast<ContentSpecificOverrideConfig *>(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 if (strcasecmp(name, "override_language") == 0) {
config->locale.language_code = settings::LanguageCode::Encode(value);
} else if (strcasecmp(name, "override_region") == 0) {
if (strcasecmp(value, "jpn") == 0) {
config->locale.region_code = settings::RegionCode_Japan;
} else if (strcasecmp(value, "usa") == 0) {
config->locale.region_code = settings::RegionCode_America;
} else if (strcasecmp(value, "eur") == 0) {
config->locale.region_code = settings::RegionCode_Europe;
} else if (strcasecmp(value, "aus") == 0) {
config->locale.region_code = settings::RegionCode_Australia;
} else if (strcasecmp(value, "chn") == 0) {
config->locale.region_code = settings::RegionCode_China;
} else if (strcasecmp(value, "kor") == 0) {
config->locale.region_code = settings::RegionCode_Korea;
} else if (strcasecmp(value, "twn") == 0) {
config->locale.region_code = settings::RegionCode_Taiwan;
}
}
} else {
return 0;
}
return 1;
}
constexpr inline bool IsOverrideMatch(const OverrideStatus &status, const OverrideKey &cfg) {
bool keys_triggered = ((status.keys_held & cfg.key_combination) != 0);
return (cfg.override_by_default ^ keys_triggered);
}
inline bool IsApplicationHblProgramId(ncm::ProgramId program_id) {
return g_hbl_override_config.override_any_app && ncm::IsApplicationProgramId(program_id);
}
inline bool IsSpecificHblProgramId(ncm::ProgramId program_id) {
return program_id == g_hbl_override_config.program_id;
}
void ParseIniFile(util::ini::Handler handler, const char *path, void *user_ctx) {
/* Mount the SD card. */
FsFileSystem sd_fs = {};
if (R_FAILED(fsOpenSdCardFileSystem(&sd_fs))) {
return;
}
ON_SCOPE_EXIT { serviceClose(&sd_fs.s); };
/* Open the file. */
FsFile config_file;
if (R_FAILED(fsFsOpenFile(&sd_fs, path, FsOpenMode_Read, &config_file))) {
return;
}
ON_SCOPE_EXIT { fsFileClose(&config_file); };
/* Parse the config. */
util::ini::ParseFile(&config_file, user_ctx, handler);
}
void RefreshOverrideConfiguration() {
ParseIniFile(OverrideConfigIniHandler, "/atmosphere/config/override_config.ini", nullptr);
}
ContentSpecificOverrideConfig GetContentOverrideConfig(ncm::ProgramId program_id) {
char path[FS_MAX_PATH];
std::snprintf(path, sizeof(path) - 1, "/atmosphere/contents/%016lx/config.ini", static_cast<u64>(program_id));
ContentSpecificOverrideConfig config = {
.override_key = g_default_override_key,
.cheat_enable_key = g_default_cheat_enable_key,
};
std::memset(&config.locale, 0xCC, sizeof(config.locale));
ParseIniFile(ContentSpecificIniHandler, path, &config);
return config;
}
}
OverrideStatus CaptureOverrideStatus(ncm::ProgramId program_id) {
OverrideStatus status = {};
/* If the SD card isn't initialized, we can't override. */
if (!IsSdCardInitialized()) {
return status;
}
/* For system modules and anything launched before the home menu, always override. */
if (program_id < ncm::ProgramId::AppletStart || !pm::info::HasLaunchedProgram(ncm::ProgramId::AppletQlaunch)) {
status.SetProgramSpecific();
return status;
}
/* Unconditionally refresh override_config.ini contents. */
RefreshOverrideConfiguration();
/* If we can't read the key state, don't override anything. */
if (R_FAILED(hid::GetKeysHeld(&status.keys_held))) {
return status;
}
/* Detect Hbl. */
if ((IsApplicationHblProgramId(program_id) && IsOverrideMatch(status, g_hbl_override_config.override_any_app_key)) ||
(IsSpecificHblProgramId(program_id) && IsOverrideMatch(status, g_hbl_override_config.override_key)))
{
status.SetHbl();
}
/* Detect content specific keys. */
const auto content_cfg = GetContentOverrideConfig(program_id);
if (IsOverrideMatch(status, content_cfg.override_key)) {
status.SetProgramSpecific();
}
/* Only allow cheat enable if not HBL. */
if (!status.IsHbl() && IsOverrideMatch(status, content_cfg.cheat_enable_key)) {
status.SetCheatEnabled();
}
return status;
}
OverrideLocale GetOverrideLocale(ncm::ProgramId program_id) {
return GetContentOverrideConfig(program_id).locale;
}
/* HBL Configuration utilities. */
bool IsHblProgramId(ncm::ProgramId program_id) {
return IsApplicationHblProgramId(program_id) || IsSpecificHblProgramId(program_id);
}
const char *GetHblPath() {
return g_hbl_sd_path;
}
}

View file

@ -0,0 +1,87 @@
/*
* 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>
namespace ams::cfg {
namespace {
/* Convenience definitions. */
constexpr os::ProcessId InitialProcessIdMinDeprecated = {0x00};
constexpr os::ProcessId InitialProcessIdMaxDeprecated = {0x50};
/* Privileged process globals. */
os::Mutex g_lock;
bool g_got_privileged_process_status = false;
os::ProcessId g_min_initial_process_id = os::InvalidProcessId, g_max_initial_process_id = os::InvalidProcessId;
os::ProcessId g_cur_process_id = os::InvalidProcessId;
/* SD card helpers. */
void GetPrivilegedProcessIdRange(os::ProcessId *out_min, os::ProcessId *out_max) {
os::ProcessId min = os::InvalidProcessId, max = os::InvalidProcessId;
if (hos::GetVersion() >= hos::Version_500) {
/* On 5.0.0+, we can get precise limits from svcGetSystemInfo. */
R_ASSERT(svcGetSystemInfo(reinterpret_cast<u64 *>(&min), SystemInfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Minimum));
R_ASSERT(svcGetSystemInfo(reinterpret_cast<u64 *>(&max), SystemInfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Maximum));
} else if (hos::GetVersion() >= hos::Version_400) {
/* On 4.0.0-4.1.0, we can get the precise limits from normal svcGetInfo. */
R_ASSERT(svcGetInfo(reinterpret_cast<u64 *>(&min), InfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Minimum));
R_ASSERT(svcGetInfo(reinterpret_cast<u64 *>(&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;
}
void GetPrivilegedProcessStatus() {
GetPrivilegedProcessIdRange(&g_min_initial_process_id, &g_max_initial_process_id);
g_cur_process_id = os::GetCurrentProcessId();
g_got_privileged_process_status = true;
}
}
/* Privileged Process utilities. */
bool IsInitialProcess() {
std::scoped_lock 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(os::ProcessId *out_min, os::ProcessId *out_max) {
std::scoped_lock 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,97 @@
/*
* 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>
namespace ams::cfg {
namespace {
/* Convenience definitions. */
constexpr sm::ServiceName RequiredServicesForSdCardAccess[] = {
sm::ServiceName::Encode("pcv"),
sm::ServiceName::Encode("gpio"),
sm::ServiceName::Encode("pinmux"),
sm::ServiceName::Encode("psc:m"),
};
constexpr size_t NumRequiredServicesForSdCardAccess = util::size(RequiredServicesForSdCardAccess);
/* SD card globals. */
os::Mutex g_sd_card_lock;
bool g_sd_card_initialized = false;
FsFileSystem g_sd_card_filesystem = {};
/* SD card helpers. */
Result CheckSdCardServicesReady() {
for (size_t i = 0; i < NumRequiredServicesForSdCardAccess; i++) {
bool service_present = false;
R_TRY(sm::HasService(&service_present, RequiredServicesForSdCardAccess[i]));
if (!service_present) {
return fs::ResultSdCardNotPresent();
}
}
return ResultSuccess();
}
void WaitSdCardServicesReadyImpl() {
for (size_t i = 0; i < NumRequiredServicesForSdCardAccess; i++) {
R_ASSERT(sm::WaitService(RequiredServicesForSdCardAccess[i]));
}
}
Result TryInitializeSdCard() {
R_TRY(CheckSdCardServicesReady());
R_ASSERT(fsOpenSdCardFileSystem(&g_sd_card_filesystem));
g_sd_card_initialized = true;
return ResultSuccess();
}
void InitializeSdCard() {
WaitSdCardServicesReadyImpl();
R_ASSERT(fsOpenSdCardFileSystem(&g_sd_card_filesystem));
g_sd_card_initialized = true;
}
}
/* SD card utilities. */
bool IsSdCardRequiredServicesReady() {
return R_SUCCEEDED(CheckSdCardServicesReady());
}
void WaitSdCardRequiredServicesReady() {
WaitSdCardServicesReadyImpl();
}
bool IsSdCardInitialized() {
std::scoped_lock 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 lk(g_sd_card_lock);
InitializeSdCard();
}
}

View file

@ -0,0 +1,55 @@
/*
* 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>
namespace ams::dd {
uintptr_t QueryIoMapping(uintptr_t phys_addr, size_t size) {
u64 virtual_addr;
const u64 aligned_addr = util::AlignDown(phys_addr, os::MemoryPageSize);
const size_t offset = phys_addr - aligned_addr;
const u64 aligned_size = size + offset;
R_TRY_CATCH(svcQueryIoMapping(&virtual_addr, aligned_addr, aligned_size)) {
/* Official software handles this by returning 0. */
R_CATCH(svc::ResultNotFound) { return 0; }
} R_END_TRY_CATCH_WITH_ASSERT;
return static_cast<uintptr_t>(virtual_addr + offset);
}
namespace {
inline u32 ReadWriteRegisterImpl(uintptr_t phys_addr, u32 value, u32 mask) {
u32 out_value;
R_ASSERT(svcReadWriteRegister(&out_value, phys_addr, mask, value));
return out_value;
}
}
u32 ReadRegister(uintptr_t phys_addr) {
return ReadWriteRegisterImpl(phys_addr, 0, 0);
}
void WriteRegister(uintptr_t phys_addr, u32 value) {
ReadWriteRegisterImpl(phys_addr, value, ~u32());
}
u32 ReadWriteRegister(uintptr_t phys_addr, u32 value, u32 mask) {
return ReadWriteRegisterImpl(phys_addr, value, mask);
}
}

View file

@ -0,0 +1,167 @@
/*
* 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/>.
*/
#define NX_SERVICE_ASSUME_NON_DOMAIN
#include "../service_guard.h"
#include "dmntcht.h"
static Service g_dmntchtSrv;
NX_GENERATE_SERVICE_GUARD(dmntcht);
Result _dmntchtInitialize(void) {
return smGetService(&g_dmntchtSrv, "dmnt:cht");
}
void _dmntchtCleanup(void) {
serviceClose(&g_dmntchtSrv);
}
Service* dmntchtGetServiceSession(void) {
return &g_dmntchtSrv;
}
Result dmntchtHasCheatProcess(bool *out) {
u8 tmp;
Result rc = serviceDispatchOut(&g_dmntchtSrv, 65000, tmp);
if (R_SUCCEEDED(rc) && out) *out = tmp & 1;
return rc;
}
Result dmntchtGetCheatProcessEvent(Event *event) {
Handle evt_handle;
Result rc = serviceDispatch(&g_dmntchtSrv, 65001,
.out_handle_attrs = { SfOutHandleAttr_HipcCopy },
.out_handles = &evt_handle,
);
if (R_SUCCEEDED(rc)) {
eventLoadRemote(event, evt_handle, true);
}
return rc;
}
Result dmntchtGetCheatProcessMetadata(DmntCheatProcessMetadata *out_metadata) {
return serviceDispatchOut(&g_dmntchtSrv, 65002, *out_metadata);
}
Result dmntchtForceOpenCheatProcess(void) {
return serviceDispatch(&g_dmntchtSrv, 65003);
}
static Result _dmntchtGetCount(u64 *out_count, u32 cmd_id) {
return serviceDispatchOut(&g_dmntchtSrv, cmd_id, *out_count);
}
static Result _dmntchtGetEntries(void *buffer, u64 buffer_size, u64 offset, u64 *out_count, u32 cmd_id) {
return serviceDispatchInOut(&g_dmntchtSrv, cmd_id, offset, *out_count,
.buffer_attrs = { SfBufferAttr_Out | SfBufferAttr_HipcMapAlias },
.buffers = { { buffer, buffer_size } },
);
}
static Result _dmntchtCmdInU32NoOut(u32 in, u32 cmd_id) {
return serviceDispatchIn(&g_dmntchtSrv, cmd_id, in);
}
Result dmntchtGetCheatProcessMappingCount(u64 *out_count) {
return _dmntchtGetCount(out_count, 65100);
}
Result dmntchtGetCheatProcessMappings(MemoryInfo *buffer, u64 max_count, u64 offset, u64 *out_count) {
return _dmntchtGetEntries(buffer, sizeof(*buffer) * max_count, offset, out_count, 65101);
}
Result dmntchtReadCheatProcessMemory(u64 address, void *buffer, size_t size) {
const struct {
u64 address;
u64 size;
} in = { address, size };
return serviceDispatchIn(&g_dmntchtSrv, 65102, in,
.buffer_attrs = { SfBufferAttr_Out | SfBufferAttr_HipcMapAlias },
.buffers = { { buffer, size } },
);
}
Result dmntchtWriteCheatProcessMemory(u64 address, const void *buffer, size_t size) {
const struct {
u64 address;
u64 size;
} in = { address, size };
return serviceDispatchIn(&g_dmntchtSrv, 65103, in,
.buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcMapAlias },
.buffers = { { buffer, size } },
);
}
Result dmntchtQueryCheatProcessMemory(MemoryInfo *mem_info, u64 address){
return serviceDispatchInOut(&g_dmntchtSrv, 65104, address, *mem_info);
}
Result dmntchtGetCheatCount(u64 *out_count) {
return _dmntchtGetCount(out_count, 65200);
}
Result dmntchtGetCheats(DmntCheatEntry *buffer, u64 max_count, u64 offset, u64 *out_count) {
return _dmntchtGetEntries(buffer, sizeof(*buffer) * max_count, offset, out_count, 65201);
}
Result dmntchtGetCheatById(DmntCheatEntry *out, u32 cheat_id) {
return serviceDispatchIn(&g_dmntchtSrv, 65202, cheat_id,
.buffer_attrs = { SfBufferAttr_Out | SfBufferAttr_HipcMapAlias | SfBufferAttr_FixedSize },
.buffers = { { out, sizeof(*out) } },
);
}
Result dmntchtToggleCheat(u32 cheat_id) {
return _dmntchtCmdInU32NoOut(cheat_id, 65203);
}
Result dmntchtAddCheat(DmntCheatDefinition *cheat_def, bool enabled, u32 *out_cheat_id) {
const u8 in = enabled != 0;
return serviceDispatchInOut(&g_dmntchtSrv, 65204, in, *out_cheat_id,
.buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcMapAlias | SfBufferAttr_FixedSize },
.buffers = { { cheat_def, sizeof(*cheat_def) } },
);
}
Result dmntchtRemoveCheat(u32 cheat_id) {
return _dmntchtCmdInU32NoOut(cheat_id, 65205);
}
Result dmntchtGetFrozenAddressCount(u64 *out_count) {
return _dmntchtGetCount(out_count, 65300);
}
Result dmntchtGetFrozenAddresses(DmntFrozenAddressEntry *buffer, u64 max_count, u64 offset, u64 *out_count) {
return _dmntchtGetEntries(buffer, sizeof(*buffer) * max_count, offset, out_count, 65301);
}
Result dmntchtGetFrozenAddress(DmntFrozenAddressEntry *out, u64 address) {
return serviceDispatchInOut(&g_dmntchtSrv, 65302, address, *out);
}
Result dmntchtEnableFrozenAddress(u64 address, u64 width, u64 *out_value) {
const struct {
u64 address;
u64 width;
} in = { address, width };
return serviceDispatchInOut(&g_dmntchtSrv, 65303, in, *out_value);
}
Result dmntchtDisableFrozenAddress(u64 address) {
return serviceDispatchIn(&g_dmntchtSrv, 65304, address);
}

View file

@ -0,0 +1,93 @@
/*
* 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/types.h>
#include <switch/kernel/event.h>
#include <switch/services/sm.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
u64 base;
u64 size;
} DmntMemoryRegionExtents;
typedef struct {
u64 process_id;
u64 title_id;
DmntMemoryRegionExtents main_nso_extents;
DmntMemoryRegionExtents heap_extents;
DmntMemoryRegionExtents alias_extents;
DmntMemoryRegionExtents address_space_extents;
u8 main_nso_build_id[0x20];
} DmntCheatProcessMetadata;
typedef struct {
char readable_name[0x40];
uint32_t num_opcodes;
uint32_t opcodes[0x100];
} DmntCheatDefinition;
typedef struct {
bool enabled;
uint32_t cheat_id;
DmntCheatDefinition definition;
} DmntCheatEntry;
typedef struct {
u64 value;
u8 width;
} DmntFrozenAddressValue;
typedef struct {
u64 address;
DmntFrozenAddressValue value;
} DmntFrozenAddressEntry;
Result dmntchtInitialize(void);
void dmntchtExit(void);
Service* dmntchtGetServiceSession(void);
Result dmntchtHasCheatProcess(bool *out);
Result dmntchtGetCheatProcessEvent(Event *event);
Result dmntchtGetCheatProcessMetadata(DmntCheatProcessMetadata *out_metadata);
Result dmntchtForceOpenCheatProcess(void);
Result dmntchtGetCheatProcessMappingCount(u64 *out_count);
Result dmntchtGetCheatProcessMappings(MemoryInfo *buffer, u64 max_count, u64 offset, u64 *out_count);
Result dmntchtReadCheatProcessMemory(u64 address, void *buffer, size_t size);
Result dmntchtWriteCheatProcessMemory(u64 address, const void *buffer, size_t size);
Result dmntchtQueryCheatProcessMemory(MemoryInfo *mem_info, u64 address);
Result dmntchtGetCheatCount(u64 *out_count);
Result dmntchtGetCheats(DmntCheatEntry *buffer, u64 max_count, u64 offset, u64 *out_count);
Result dmntchtGetCheatById(DmntCheatEntry *out_cheat, u32 cheat_id);
Result dmntchtToggleCheat(u32 cheat_id);
Result dmntchtAddCheat(DmntCheatDefinition *cheat, bool enabled, u32 *out_cheat_id);
Result dmntchtRemoveCheat(u32 cheat_id);
Result dmntchtGetFrozenAddressCount(u64 *out_count);
Result dmntchtGetFrozenAddresses(DmntFrozenAddressEntry *buffer, u64 max_count, u64 offset, u64 *out_count);
Result dmntchtGetFrozenAddress(DmntFrozenAddressEntry *out, u64 address);
Result dmntchtEnableFrozenAddress(u64 address, u64 width, u64 *out_value);
Result dmntchtDisableFrozenAddress(u64 address);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,93 @@
/*
* 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>
namespace ams::fs {
Result FileStorage::UpdateSize() {
R_UNLESS(this->size == InvalidSize, ResultSuccess());
return this->base_file->GetSize(&this->size);
}
Result FileStorage::Read(s64 offset, void *buffer, size_t size) {
/* Immediately succeed if there's nothing to read. */
R_UNLESS(size > 0, ResultSuccess());
/* Validate buffer. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Ensure our size is valid. */
R_TRY(this->UpdateSize());
/* Ensure our access is valid. */
R_UNLESS(IStorage::IsRangeValid(offset, size, this->size), fs::ResultOutOfRange());
size_t read_size;
return this->base_file->Read(&read_size, offset, buffer, size);
}
Result FileStorage::Write(s64 offset, const void *buffer, size_t size) {
/* Immediately succeed if there's nothing to write. */
R_UNLESS(size > 0, ResultSuccess());
/* Validate buffer. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Ensure our size is valid. */
R_TRY(this->UpdateSize());
/* Ensure our access is valid. */
R_UNLESS(IStorage::IsRangeValid(offset, size, this->size), fs::ResultOutOfRange());
return this->base_file->Write(offset, buffer, size, fs::WriteOption());
}
Result FileStorage::Flush() {
return this->base_file->Flush();
}
Result FileStorage::GetSize(s64 *out_size) {
R_TRY(this->UpdateSize());
*out_size = this->size;
return ResultSuccess();
}
Result FileStorage::SetSize(s64 size) {
this->size = InvalidSize;
return this->base_file->SetSize(size);
}
Result FileStorage::OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
switch (op_id) {
case OperationId::InvalidateCache:
case OperationId::QueryRange:
if (size == 0) {
if (op_id == OperationId::QueryRange) {
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
R_UNLESS(dst_size == sizeof(QueryRangeInfo), fs::ResultInvalidSize());
reinterpret_cast<QueryRangeInfo *>(dst)->Clear();
}
return ResultSuccess();
}
R_TRY(this->UpdateSize());
R_UNLESS(IStorage::IsOffsetAndSizeValid(offset, size), fs::ResultOutOfRange());
return this->base_file->OperateRange(dst, dst_size, op_id, offset, size, src, src_size);
default:
return fs::ResultUnsupportedOperation();
}
}
}

View file

@ -0,0 +1,248 @@
/*
* 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>
namespace ams::fs {
Result PathTool::Normalize(char *out, size_t *out_len, const char *src, size_t max_out_size, bool unc_preserved) {
/* Paths must start with / */
R_UNLESS(IsSeparator(src[0]), fs::ResultInvalidPathFormat());
bool skip_next_sep = false;
size_t i = 0;
size_t len = 0;
while (!IsNullTerminator(src[i])) {
if (IsSeparator(src[i])) {
/* Swallow separators. */
while (IsSeparator(src[++i])) { }
if (IsNullTerminator(src[i])) {
break;
}
/* Handle skip if needed */
if (!skip_next_sep) {
if (len + 1 == max_out_size) {
out[len] = StringTraits::NullTerminator;
if (out_len != nullptr) {
*out_len = len;
}
return fs::ResultTooLongPath();
}
out[len++] = StringTraits::DirectorySeparator;
if (unc_preserved && len == 1) {
while (len < i) {
if (len + 1 == max_out_size) {
out[len] = StringTraits::NullTerminator;
if (out_len != nullptr) {
*out_len = len;
}
return fs::ResultTooLongPath();
}
out[len++] = StringTraits::DirectorySeparator;
}
}
}
skip_next_sep = false;
}
/* See length of current dir. */
size_t dir_len = 0;
while (!IsSeparator(src[i+dir_len]) && !IsNullTerminator(src[i+dir_len])) {
dir_len++;
}
if (IsCurrentDirectory(&src[i])) {
skip_next_sep = true;
} else if (IsParentDirectory(&src[i])) {
AMS_ASSERT(IsSeparator(out[0]));
AMS_ASSERT(IsSeparator(out[len - 1]));
R_UNLESS(len != 1, fs::ResultDirectoryUnobtainable());
/* Walk up a directory. */
len -= 2;
while (!IsSeparator(out[len])) {
len--;
}
} else {
/* Copy, possibly truncating. */
if (len + dir_len + 1 <= max_out_size) {
for (size_t j = 0; j < dir_len; j++) {
out[len++] = src[i+j];
}
} else {
const size_t copy_len = max_out_size - 1 - len;
for (size_t j = 0; j < copy_len; j++) {
out[len++] = src[i+j];
}
out[len] = StringTraits::NullTerminator;
if (out_len != nullptr) {
*out_len = len;
}
return fs::ResultTooLongPath();
}
}
i += dir_len;
}
if (skip_next_sep) {
len--;
}
if (len == 0 && max_out_size) {
out[len++] = StringTraits::DirectorySeparator;
}
R_UNLESS(max_out_size >= len - 1, fs::ResultTooLongPath());
/* Null terminate. */
out[len] = StringTraits::NullTerminator;
if (out_len != nullptr) {
*out_len = len;
}
/* Assert normalized. */
bool normalized = false;
AMS_ASSERT(unc_preserved || (R_SUCCEEDED(IsNormalized(&normalized, out)) && normalized));
return ResultSuccess();
}
Result PathTool::IsNormalized(bool *out, const char *path) {
/* Nintendo uses a state machine here. */
enum class PathState {
Start,
Normal,
FirstSeparator,
Separator,
CurrentDir,
ParentDir,
WindowsDriveLetter,
};
PathState state = PathState::Start;
for (const char *cur = path; *cur != StringTraits::NullTerminator; cur++) {
const char c = *cur;
switch (state) {
case PathState::Start:
if (IsWindowsDriveCharacter(c)) {
state = PathState::WindowsDriveLetter;
} else if (IsSeparator(c)) {
state = PathState::FirstSeparator;
} else {
return fs::ResultInvalidPathFormat();
}
break;
case PathState::Normal:
if (IsSeparator(c)) {
state = PathState::Separator;
}
break;
case PathState::FirstSeparator:
case PathState::Separator:
if (IsSeparator(c)) {
*out = false;
return ResultSuccess();
} else if (IsDot(c)) {
state = PathState::CurrentDir;
} else {
state = PathState::Normal;
}
break;
case PathState::CurrentDir:
if (IsSeparator(c)) {
*out = false;
return ResultSuccess();
} else if (IsDot(c)) {
state = PathState::ParentDir;
} else {
state = PathState::Normal;
}
break;
case PathState::ParentDir:
if (IsSeparator(c)) {
*out = false;
return ResultSuccess();
} else {
state = PathState::Normal;
}
break;
case PathState::WindowsDriveLetter:
if (IsDriveSeparator(c)) {
*out = true;
return ResultSuccess();
} else {
return fs::ResultInvalidPathFormat();
}
break;
}
}
switch (state) {
case PathState::Start:
case PathState::WindowsDriveLetter:
return fs::ResultInvalidPathFormat();
case PathState::FirstSeparator:
case PathState::Normal:
*out = true;
break;
case PathState::CurrentDir:
case PathState::ParentDir:
case PathState::Separator:
*out = false;
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
return ResultSuccess();
}
bool PathTool::IsSubPath(const char *lhs, const char *rhs) {
AMS_ASSERT(lhs != nullptr);
AMS_ASSERT(rhs != nullptr);
/* Special case certain paths. */
if (IsSeparator(lhs[0]) && !IsSeparator(lhs[1]) && IsSeparator(rhs[0]) && IsSeparator(rhs[1])) {
return false;
}
if (IsSeparator(rhs[0]) && !IsSeparator(rhs[1]) && IsSeparator(lhs[0]) && IsSeparator(lhs[1])) {
return false;
}
if (IsSeparator(lhs[0]) && IsNullTerminator(lhs[1]) && IsSeparator(rhs[0]) && !IsNullTerminator(rhs[1])) {
return true;
}
if (IsSeparator(rhs[0]) && IsNullTerminator(rhs[1]) && IsSeparator(lhs[0]) && !IsNullTerminator(lhs[1])) {
return true;
}
/* Check subpath. */
for (size_t i = 0; /* No terminating condition */; i++) {
if (IsNullTerminator(lhs[i])) {
return IsSeparator(rhs[i]);
} else if (IsNullTerminator(rhs[i])) {
return IsSeparator(lhs[i]);
} else if (lhs[i] != rhs[i]) {
return false;
}
}
}
}

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 <stratosphere.hpp>
namespace ams::fs {
Result VerifyPath(const char *path, size_t max_path_len, size_t max_name_len) {
const char *cur = path;
size_t name_len = 0;
for (size_t path_len = 0; path_len <= max_path_len && name_len <= max_name_len; path_len++) {
const char c = *(cur++);
/* If terminated, we're done. */
R_UNLESS(c != StringTraits::NullTerminator, ResultSuccess());
/* TODO: Nintendo converts the path from utf-8 to utf-32, one character at a time. */
/* We should do this. */
/* Banned characters: :*?<>| */
R_UNLESS((c != ':' && c != '*' && c != '?' && c != '<' && c != '>' && c != '|'), fs::ResultInvalidCharacter());
name_len++;
/* Check for separator. */
if (c == '\\' || c == '/') {
name_len = 0;
}
}
return fs::ResultTooLongPath();
}
}

View file

@ -0,0 +1,343 @@
/*
* 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/fssrv/fssrv_interface_adapters.hpp>
namespace ams::fssrv::impl {
FileInterfaceAdapter::FileInterfaceAdapter(std::unique_ptr<fs::fsa::IFile> &&file, std::shared_ptr<FileSystemInterfaceAdapter> &&parent, std::unique_lock<fssystem::SemaphoreAdapter> &&sema)
: parent_filesystem(std::move(parent)), base_file(std::move(file)), open_count_semaphore(std::move(sema))
{
/* ... */
}
FileInterfaceAdapter::~FileInterfaceAdapter() {
/* ... */
}
void FileInterfaceAdapter::InvalidateCache() {
AMS_ASSERT(this->parent_filesystem->IsDeepRetryEnabled());
std::scoped_lock<os::ReadWriteLock> scoped_write_lock(this->parent_filesystem->GetReadWriteLockForCacheInvalidation());
this->base_file->OperateRange(nullptr, 0, fs::OperationId::InvalidateCache, 0, std::numeric_limits<s64>::max(), nullptr, 0);
}
Result FileInterfaceAdapter::Read(ams::sf::Out<s64> out, s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size, fs::ReadOption option) {
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
/* TODO: Deep retry */
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
R_UNLESS(size >= 0, fs::ResultInvalidSize());
size_t read_size = 0;
R_TRY(this->base_file->Read(&read_size, offset, buffer.GetPointer(), static_cast<size_t>(size), option));
out.SetValue(read_size);
return ResultSuccess();
}
Result FileInterfaceAdapter::Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size, fs::WriteOption option) {
/* TODO: N increases thread priority temporarily when writing. We may want to eventually. */
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
R_UNLESS(size >= 0, fs::ResultInvalidSize());
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
return this->base_file->Write(offset, buffer.GetPointer(), size, option);
}
Result FileInterfaceAdapter::Flush() {
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
return this->base_file->Flush();
}
Result FileInterfaceAdapter::SetSize(s64 size) {
R_UNLESS(size >= 0, fs::ResultInvalidSize());
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
return this->base_file->SetSize(size);
}
Result FileInterfaceAdapter::GetSize(ams::sf::Out<s64> out) {
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
return this->base_file->GetSize(out.GetPointer());
}
Result FileInterfaceAdapter::OperateRange(ams::sf::Out<fs::FileQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) {
/* N includes this redundant check, so we will too. */
R_UNLESS(out.GetPointer() != nullptr, fs::ResultNullptrArgument());
out->Clear();
if (op_id == static_cast<s32>(fs::OperationId::QueryRange)) {
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
fs::FileQueryRangeInfo info;
R_TRY(this->base_file->OperateRange(&info, sizeof(info), fs::OperationId::QueryRange, offset, size, nullptr, 0));
out->Merge(info);
}
return ResultSuccess();
}
DirectoryInterfaceAdapter::DirectoryInterfaceAdapter(std::unique_ptr<fs::fsa::IDirectory> &&dir, std::shared_ptr<FileSystemInterfaceAdapter> &&parent, std::unique_lock<fssystem::SemaphoreAdapter> &&sema)
: parent_filesystem(std::move(parent)), base_dir(std::move(dir)), open_count_semaphore(std::move(sema))
{
/* ... */
}
DirectoryInterfaceAdapter::~DirectoryInterfaceAdapter() {
/* ... */
}
Result DirectoryInterfaceAdapter::Read(ams::sf::Out<s64> out, const ams::sf::OutBuffer &out_entries) {
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
const size_t max_num_entries = out_entries.GetSize() / sizeof(fs::DirectoryEntry);
R_UNLESS(max_num_entries >= 0, fs::ResultInvalidSize());
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
return this->base_dir->Read(out.GetPointer(), reinterpret_cast<fs::DirectoryEntry *>(out_entries.GetPointer()), max_num_entries);
}
Result DirectoryInterfaceAdapter::GetEntryCount(ams::sf::Out<s64> out) {
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
return this->base_dir->GetEntryCount(out.GetPointer());
}
FileSystemInterfaceAdapter::FileSystemInterfaceAdapter(std::shared_ptr<fs::fsa::IFileSystem> &&fs, bool open_limited)
: base_fs(std::move(fs)), open_count_limited(open_limited), deep_retry_enabled(false)
{
/* ... */
}
FileSystemInterfaceAdapter::~FileSystemInterfaceAdapter() {
/* ... */
}
bool FileSystemInterfaceAdapter::IsDeepRetryEnabled() const {
return this->deep_retry_enabled;
}
bool FileSystemInterfaceAdapter::IsAccessFailureDetectionObserved() const {
/* TODO: This calls into fssrv::FileSystemProxyImpl, which we don't have yet. */
AMS_ASSERT(false);
}
std::optional<std::shared_lock<os::ReadWriteLock>> FileSystemInterfaceAdapter::AcquireCacheInvalidationReadLock() {
std::optional<std::shared_lock<os::ReadWriteLock>> lock;
if (this->deep_retry_enabled) {
lock.emplace(this->invalidation_lock);
}
return lock;
}
os::ReadWriteLock &FileSystemInterfaceAdapter::GetReadWriteLockForCacheInvalidation() {
return this->invalidation_lock;
}
Result FileSystemInterfaceAdapter::CreateFile(const fssrv::sf::Path &path, s64 size, s32 option) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
R_UNLESS(size >= 0, fs::ResultInvalidSize());
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
return this->base_fs->CreateFile(normalizer.GetPath(), size, option);
}
Result FileSystemInterfaceAdapter::DeleteFile(const fssrv::sf::Path &path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
return this->base_fs->DeleteFile(normalizer.GetPath());
}
Result FileSystemInterfaceAdapter::CreateDirectory(const fssrv::sf::Path &path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
R_UNLESS(strncmp(normalizer.GetPath(), "/", 2) != 0, fs::ResultPathAlreadyExists());
return this->base_fs->CreateDirectory(normalizer.GetPath());
}
Result FileSystemInterfaceAdapter::DeleteDirectory(const fssrv::sf::Path &path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
R_UNLESS(strncmp(normalizer.GetPath(), "/", 2) != 0, fs::ResultDirectoryNotDeletable());
return this->base_fs->DeleteDirectory(normalizer.GetPath());
}
Result FileSystemInterfaceAdapter::DeleteDirectoryRecursively(const fssrv::sf::Path &path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
R_UNLESS(strncmp(normalizer.GetPath(), "/", 2) != 0, fs::ResultDirectoryNotDeletable());
return this->base_fs->DeleteDirectoryRecursively(normalizer.GetPath());
}
Result FileSystemInterfaceAdapter::RenameFile(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
PathNormalizer old_normalizer(old_path.str);
PathNormalizer new_normalizer(new_path.str);
R_UNLESS(old_normalizer.GetPath() != nullptr, old_normalizer.GetResult());
R_UNLESS(new_normalizer.GetPath() != nullptr, new_normalizer.GetResult());
return this->base_fs->RenameFile(old_normalizer.GetPath(), new_normalizer.GetPath());
}
Result FileSystemInterfaceAdapter::RenameDirectory(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
PathNormalizer old_normalizer(old_path.str);
PathNormalizer new_normalizer(new_path.str);
R_UNLESS(old_normalizer.GetPath() != nullptr, old_normalizer.GetResult());
R_UNLESS(new_normalizer.GetPath() != nullptr, new_normalizer.GetResult());
const bool is_subpath = fssystem::PathTool::IsSubPath(old_normalizer.GetPath(), new_normalizer.GetPath());
R_UNLESS(!is_subpath, fs::ResultDirectoryNotRenamable());
return this->base_fs->RenameFile(old_normalizer.GetPath(), new_normalizer.GetPath());
}
Result FileSystemInterfaceAdapter::GetEntryType(ams::sf::Out<u32> out, const fssrv::sf::Path &path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
static_assert(sizeof(*out.GetPointer()) == sizeof(fs::DirectoryEntryType));
return this->base_fs->GetEntryType(reinterpret_cast<fs::DirectoryEntryType *>(out.GetPointer()), normalizer.GetPath());
}
Result FileSystemInterfaceAdapter::OpenFile(ams::sf::Out<std::shared_ptr<FileInterfaceAdapter>> out, const fssrv::sf::Path &path, u32 mode) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
std::unique_lock<fssystem::SemaphoreAdapter> open_count_semaphore;
if (this->open_count_limited) {
/* TODO: This calls into fssrv::FileSystemProxyImpl, which we don't have yet. */
AMS_ASSERT(false);
}
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
std::unique_ptr<fs::fsa::IFile> file;
R_TRY(this->base_fs->OpenFile(&file, normalizer.GetPath(), static_cast<fs::OpenMode>(mode)));
/* TODO: This is a hack to get the mitm API to work. Better solution? */
const auto target_object_id = file->GetDomainObjectId();
/* TODO: N creates an nn::fssystem::AsynchronousAccessFile here. */
std::shared_ptr<FileSystemInterfaceAdapter> shared_this = this->shared_from_this();
std::shared_ptr<FileInterfaceAdapter> file_intf = std::make_shared<FileInterfaceAdapter>(std::move(file), std::move(shared_this), std::move(open_count_semaphore));
R_UNLESS(file_intf != nullptr, fs::ResultAllocationFailureInFileSystemInterfaceAdapter());
out.SetValue(std::move(file_intf), target_object_id);
return ResultSuccess();
}
Result FileSystemInterfaceAdapter::OpenDirectory(ams::sf::Out<std::shared_ptr<DirectoryInterfaceAdapter>> out, const fssrv::sf::Path &path, u32 mode) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
std::unique_lock<fssystem::SemaphoreAdapter> open_count_semaphore;
if (this->open_count_limited) {
/* TODO: This calls into fssrv::FileSystemProxyImpl, which we don't have yet. */
AMS_ASSERT(false);
}
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
std::unique_ptr<fs::fsa::IDirectory> dir;
R_TRY(this->base_fs->OpenDirectory(&dir, normalizer.GetPath(), static_cast<fs::OpenDirectoryMode>(mode)));
/* TODO: This is a hack to get the mitm API to work. Better solution? */
const auto target_object_id = dir->GetDomainObjectId();
std::shared_ptr<FileSystemInterfaceAdapter> shared_this = this->shared_from_this();
std::shared_ptr<DirectoryInterfaceAdapter> dir_intf = std::make_shared<DirectoryInterfaceAdapter>(std::move(dir), std::move(shared_this), std::move(open_count_semaphore));
R_UNLESS(dir_intf != nullptr, fs::ResultAllocationFailureInFileSystemInterfaceAdapter());
out.SetValue(std::move(dir_intf), target_object_id);
return ResultSuccess();
}
Result FileSystemInterfaceAdapter::Commit() {
auto read_lock = this->AcquireCacheInvalidationReadLock();
return this->base_fs->Commit();
}
Result FileSystemInterfaceAdapter::GetFreeSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
return this->base_fs->GetFreeSpaceSize(out.GetPointer(), normalizer.GetPath());
}
Result FileSystemInterfaceAdapter::GetTotalSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
return this->base_fs->GetTotalSpaceSize(out.GetPointer(), normalizer.GetPath());
}
Result FileSystemInterfaceAdapter::CleanDirectoryRecursively(const fssrv::sf::Path &path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
return this->base_fs->CleanDirectoryRecursively(normalizer.GetPath());
}
Result FileSystemInterfaceAdapter::GetFileTimeStampRaw(ams::sf::Out<fs::FileTimeStampRaw> out, const fssrv::sf::Path &path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
PathNormalizer normalizer(path.str);
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
return this->base_fs->GetFileTimeStampRaw(out.GetPointer(), normalizer.GetPath());
}
Result FileSystemInterfaceAdapter::QueryEntry(const ams::sf::OutBuffer &out_buf, const ams::sf::InBuffer &in_buf, s32 query_id, const fssrv::sf::Path &path) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
/* TODO: Nintendo does not normalize the path. Should we? */
char *dst = reinterpret_cast< char *>(out_buf.GetPointer());
const char *src = reinterpret_cast<const char *>(in_buf.GetPointer());
return this->base_fs->QueryEntry(dst, out_buf.GetSize(), src, in_buf.GetSize(), static_cast<fs::fsa::QueryId>(query_id), path.str);
}
}

View file

@ -0,0 +1,77 @@
/*
* 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>
namespace ams::fssrv {
Result PathNormalizer::Normalize(const char **out_path, Buffer *out_buf, const char *path, bool preserve_unc, bool preserve_tail_sep, bool has_mount_name) {
/* Clear output. */
*out_path = nullptr;
*out_buf = Buffer();
/* Find start of path. */
const char *path_start = path;
if (has_mount_name) {
while (path_start < path + fs::MountNameLengthMax + 1) {
if (fssystem::PathTool::IsNullTerminator(*path_start)) {
break;
} else if (fssystem::PathTool::IsDriveSeparator(*(path_start++))) {
break;
}
}
R_UNLESS(path < path_start - 1, fs::ResultInvalidPath());
R_UNLESS(fssystem::PathTool::IsDriveSeparator(*(path_start - 1)), fs::ResultInvalidPath());
}
/* Check if we're normalized. */
bool normalized = false;
R_TRY(fssystem::PathTool::IsNormalized(&normalized, path_start));
if (normalized) {
/* If we're already normalized, no allocation is needed. */
*out_path = path;
} else {
/* Allocate a new buffer. */
auto buffer = std::make_unique<char[]>(fs::EntryNameLengthMax + 1);
R_UNLESS(buffer != nullptr, fs::ResultAllocationFailureInPathNormalizer());
/* Copy in mount name. */
const size_t mount_name_len = path_start - path;
std::memcpy(buffer.get(), path, mount_name_len);
/* Generate normalized path. */
size_t normalized_len = 0;
R_TRY(fssystem::PathTool::Normalize(buffer.get() + mount_name_len, &normalized_len, path_start, fs::EntryNameLengthMax + 1 - mount_name_len, preserve_unc));
/* Preserve the tail separator, if we should. */
if (preserve_tail_sep) {
if (fssystem::PathTool::IsSeparator(path[strnlen(path, fs::EntryNameLengthMax) - 1])) {
/* Nintendo doesn't actually validate this. */
R_UNLESS(mount_name_len + normalized_len < fs::EntryNameLengthMax, fs::ResultTooLongPath());
buffer[mount_name_len + normalized_len] = fssystem::StringTraits::DirectorySeparator;
buffer[mount_name_len + normalized_len + 1] = fssystem::StringTraits::NullTerminator;
}
}
/* Save output. */
*out_path = buffer.get();
*out_buf = std::move(buffer);
}
return ResultSuccess();
}
}

View file

@ -0,0 +1,94 @@
/*
* 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/fssrv/fssrv_interface_adapters.hpp>
namespace ams::fssrv::impl {
StorageInterfaceAdapter::StorageInterfaceAdapter(fs::IStorage *storage) : base_storage(storage) {
/* ... */
}
StorageInterfaceAdapter::StorageInterfaceAdapter(std::unique_ptr<fs::IStorage> storage) : base_storage(storage.release()) {
/* ... */
}
StorageInterfaceAdapter::StorageInterfaceAdapter(std::shared_ptr<fs::IStorage> &&storage) : base_storage(std::move(storage)) {
/* ... */
}
StorageInterfaceAdapter::~StorageInterfaceAdapter() {
/* ... */
}
std::optional<std::shared_lock<os::ReadWriteLock>> StorageInterfaceAdapter::AcquireCacheInvalidationReadLock() {
std::optional<std::shared_lock<os::ReadWriteLock>> lock;
if (this->deep_retry_enabled) {
lock.emplace(this->invalidation_lock);
}
return lock;
}
Result StorageInterfaceAdapter::Read(s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size) {
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
/* TODO: Deep retry */
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
R_UNLESS(size >= 0, fs::ResultInvalidSize());
return this->base_storage->Read(offset, buffer.GetPointer(), size);
}
Result StorageInterfaceAdapter::Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size) {
/* TODO: N increases thread priority temporarily when writing. We may want to eventually. */
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
R_UNLESS(size >= 0, fs::ResultInvalidSize());
auto read_lock = this->AcquireCacheInvalidationReadLock();
return this->base_storage->Write(offset, buffer.GetPointer(), size);
}
Result StorageInterfaceAdapter::Flush() {
auto read_lock = this->AcquireCacheInvalidationReadLock();
return this->base_storage->Flush();
}
Result StorageInterfaceAdapter::SetSize(s64 size) {
R_UNLESS(size >= 0, fs::ResultInvalidSize());
auto read_lock = this->AcquireCacheInvalidationReadLock();
return this->base_storage->SetSize(size);
}
Result StorageInterfaceAdapter::GetSize(ams::sf::Out<s64> out) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
return this->base_storage->GetSize(out.GetPointer());
}
Result StorageInterfaceAdapter::OperateRange(ams::sf::Out<fs::StorageQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) {
/* N includes this redundant check, so we will too. */
R_UNLESS(out.GetPointer() != nullptr, fs::ResultNullptrArgument());
out->Clear();
if (op_id == static_cast<s32>(fs::OperationId::QueryRange)) {
auto read_lock = this->AcquireCacheInvalidationReadLock();
fs::StorageQueryRangeInfo info;
R_TRY(this->base_storage->OperateRange(&info, sizeof(info), fs::OperationId::QueryRange, offset, size, nullptr, 0));
out->Merge(info);
}
return ResultSuccess();
}
}

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 <stratosphere.hpp>
namespace ams::fssystem {
DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after, bool unc)
: PathResolutionFileSystem(fs, unc)
{
this->before_dir = nullptr;
this->after_dir = nullptr;
R_ASSERT(this->Initialize(before, after));
}
DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::unique_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after, bool unc)
: PathResolutionFileSystem(std::move(fs), unc)
{
this->before_dir = nullptr;
this->after_dir = nullptr;
R_ASSERT(this->Initialize(before, after));
}
DirectoryRedirectionFileSystem::~DirectoryRedirectionFileSystem() {
if (this->before_dir != nullptr) {
std::free(this->before_dir);
}
if (this->after_dir != nullptr) {
std::free(this->after_dir);
}
}
Result DirectoryRedirectionFileSystem::GetNormalizedDirectoryPath(char **out, size_t *out_size, const char *dir) {
/* Clear output. */
*out = nullptr;
*out_size = 0;
/* Make sure the path isn't too long. */
R_UNLESS(strnlen(dir, fs::EntryNameLengthMax + 1) <= fs::EntryNameLengthMax, fs::ResultTooLongPath());
/* Normalize the path. */
char normalized_path[fs::EntryNameLengthMax + 2];
size_t normalized_path_len;
R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, dir, sizeof(normalized_path), this->IsUncPreserved()));
/* Ensure terminating '/' */
if (!PathTool::IsSeparator(normalized_path[normalized_path_len - 1])) {
AMS_ASSERT(normalized_path_len + 2 <= sizeof(normalized_path));
normalized_path[normalized_path_len] = StringTraits::DirectorySeparator;
normalized_path[normalized_path_len + 1] = StringTraits::NullTerminator;
normalized_path_len++;
}
/* Allocate new path. */
const size_t size = normalized_path_len + 1;
char *new_dir = static_cast<char *>(std::malloc(size));
AMS_ASSERT(new_dir != nullptr);
/* TODO: custom ResultAllocationFailure? */
/* Copy path in. */
std::memcpy(new_dir, normalized_path, normalized_path_len);
new_dir[normalized_path_len] = StringTraits::NullTerminator;
/* Set output. */
*out = new_dir;
*out_size = size;
return ResultSuccess();
}
Result DirectoryRedirectionFileSystem::Initialize(const char *before, const char *after) {
/* Normalize both directories. */
this->GetNormalizedDirectoryPath(&this->before_dir, &this->before_dir_len, before);
this->GetNormalizedDirectoryPath(&this->after_dir, &this->after_dir_len, after);
return ResultSuccess();
}
Result DirectoryRedirectionFileSystem::ResolveFullPath(char *out, size_t out_size, const char *relative_path) {
/* Normalize the relative path. */
char normalized_rel_path[fs::EntryNameLengthMax + 1];
size_t normalized_rel_path_len;
R_TRY(PathTool::Normalize(normalized_rel_path, &normalized_rel_path_len, relative_path, sizeof(normalized_rel_path), this->IsUncPreserved()));
const bool is_prefixed = std::memcmp(normalized_rel_path, this->before_dir, this->before_dir_len - 2) == 0 &&
(PathTool::IsSeparator(normalized_rel_path[this->before_dir_len - 2]) || PathTool::IsNullTerminator(normalized_rel_path[this->before_dir_len - 2]));
if (is_prefixed) {
const size_t before_prefix_len = this->before_dir_len - 2;
const size_t after_prefix_len = this->after_dir_len - 2;
const size_t final_str_len = after_prefix_len + normalized_rel_path_len - before_prefix_len;
R_UNLESS(final_str_len < out_size, fs::ResultTooLongPath());
/* Copy normalized path. */
std::memcpy(out, this->after_dir, after_prefix_len);
std::memcpy(out + after_prefix_len, normalized_rel_path + before_prefix_len, normalized_rel_path_len - before_prefix_len);
out[final_str_len] = StringTraits::NullTerminator;
} else {
/* Path is not prefixed. */
R_UNLESS(normalized_rel_path_len + 1 <= out_size, fs::ResultTooLongPath());
std::memcpy(out, normalized_rel_path, normalized_rel_path_len);
out[normalized_rel_path_len] = StringTraits::NullTerminator;
}
return ResultSuccess();
}
}

View file

@ -0,0 +1,272 @@
/*
* 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>
namespace ams::fssystem {
namespace {
constexpr size_t IdealWorkBufferSize = 0x100000; /* 1 MiB */
constexpr const char CommittedDirectoryPath[] = "/0/";
constexpr const char WorkingDirectoryPath[] = "/1/";
constexpr const char SynchronizingDirectoryPath[] = "/_/";
class DirectorySaveDataFile : public fs::fsa::IFile {
private:
std::unique_ptr<fs::fsa::IFile> base_file;
DirectorySaveDataFileSystem *parent_fs;
fs::OpenMode open_mode;
public:
DirectorySaveDataFile(std::unique_ptr<fs::fsa::IFile> f, DirectorySaveDataFileSystem *p, fs::OpenMode m) : base_file(std::move(f)), parent_fs(p), open_mode(m) {
/* ... */
}
virtual ~DirectorySaveDataFile() {
/* Observe closing of writable file. */
if (this->open_mode & fs::OpenMode_Write) {
this->parent_fs->OnWritableFileClose();
}
}
public:
virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override {
return this->base_file->Read(out, offset, buffer, size, option);
}
virtual Result GetSizeImpl(s64 *out) override {
return this->base_file->GetSize(out);
}
virtual Result FlushImpl() override {
return this->base_file->Flush();
}
virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override {
return this->base_file->Write(offset, buffer, size, option);
}
virtual Result SetSizeImpl(s64 size) override {
return this->base_file->SetSize(size);
}
virtual Result OperateRangeImpl(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
return this->base_file->OperateRange(dst, dst_size, op_id, offset, size, src, src_size);
}
public:
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
return this->base_file->GetDomainObjectId();
}
};
}
DirectorySaveDataFileSystem::DirectorySaveDataFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs)
: PathResolutionFileSystem(fs), open_writable_files(0)
{
/* ... */
}
DirectorySaveDataFileSystem::DirectorySaveDataFileSystem(std::unique_ptr<fs::fsa::IFileSystem> fs)
: PathResolutionFileSystem(std::move(fs)), open_writable_files(0)
{
/* ... */
}
DirectorySaveDataFileSystem::~DirectorySaveDataFileSystem() {
/* ... */
}
Result DirectorySaveDataFileSystem::Initialize() {
/* Nintendo does not acquire the lock here, but I think we probably should. */
std::scoped_lock lk(this->accessor_mutex);
fs::DirectoryEntryType type;
/* Check that the working directory exists. */
R_TRY_CATCH(this->base_fs->GetEntryType(&type, WorkingDirectoryPath)) {
/* If path isn't found, create working directory and committed directory. */
R_CATCH(fs::ResultPathNotFound) {
R_TRY(this->base_fs->CreateDirectory(WorkingDirectoryPath));
R_TRY(this->base_fs->CreateDirectory(CommittedDirectoryPath));
}
} R_END_TRY_CATCH;
/* Now check for the committed directory. */
R_TRY_CATCH(this->base_fs->GetEntryType(&type, CommittedDirectoryPath)) {
/* Committed doesn't exist, so synchronize and rename. */
R_CATCH(fs::ResultPathNotFound) {
R_TRY(this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath));
R_TRY(this->base_fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath));
return ResultSuccess();
}
} R_END_TRY_CATCH;
/* The committed directory exists, so synchronize it to the working directory. */
return this->SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath);
}
Result DirectorySaveDataFileSystem::AllocateWorkBuffer(std::unique_ptr<u8[]> *out, size_t *out_size, size_t size) {
/* Repeatedly try to allocate until success. */
while (size > 0x200) {
/* Allocate the buffer. */
if (auto mem = new (std::nothrow) u8[size]; mem != nullptr) {
out->reset(mem);
*out_size = size;
return ResultSuccess();
} else {
/* Divide size by two. */
size >>= 1;
}
}
/* TODO: Return a result here? Nintendo does not, but they have other allocation failed results. */
/* Consider returning ResultFsAllocationFailureInDirectorySaveDataFileSystem? */
AMS_ASSERT(false);
}
Result DirectorySaveDataFileSystem::SynchronizeDirectory(const char *dst, const char *src) {
/* Delete destination dir and recreate it. */
R_TRY_CATCH(this->base_fs->DeleteDirectoryRecursively(dst)) {
R_CATCH(fs::ResultPathNotFound) { /* Nintendo returns error unconditionally, but I think that's a bug in their code. */}
} R_END_TRY_CATCH;
R_TRY(this->base_fs->CreateDirectory(dst));
/* Get a work buffer to work with. */
std::unique_ptr<u8[]> work_buf;
size_t work_buf_size;
R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBufferSize));
/* Copy the directory recursively. */
return fssystem::CopyDirectoryRecursively(this->base_fs, dst, src, work_buf.get(), work_buf_size);
}
Result DirectorySaveDataFileSystem::ResolveFullPath(char *out, size_t out_size, const char *relative_path) {
R_UNLESS(strnlen(relative_path, fs::EntryNameLengthMax + 1) < fs::EntryNameLengthMax + 1, fs::ResultTooLongPath());
R_UNLESS(PathTool::IsSeparator(relative_path[0]), fs::ResultInvalidPath());
/* Copy working directory path. */
std::strncpy(out, WorkingDirectoryPath, out_size);
out[out_size - 1] = StringTraits::NullTerminator;
/* Normalize it. */
constexpr size_t WorkingDirectoryPathLength = sizeof(WorkingDirectoryPath) - 1;
size_t normalized_length;
return PathTool::Normalize(out + WorkingDirectoryPathLength - 1, &normalized_length, relative_path, out_size - (WorkingDirectoryPathLength - 1));
}
void DirectorySaveDataFileSystem::OnWritableFileClose() {
std::scoped_lock lk(this->accessor_mutex);
this->open_writable_files--;
/* Nintendo does not check this, but I think it's sensible to do so. */
AMS_ASSERT(this->open_writable_files >= 0);
}
Result DirectorySaveDataFileSystem::CopySaveFromFileSystem(fs::fsa::IFileSystem *save_fs) {
/* If the input save is null, there's nothing to copy. */
R_UNLESS(save_fs != nullptr, ResultSuccess());
/* Get a work buffer to work with. */
std::unique_ptr<u8[]> work_buf;
size_t work_buf_size;
R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBufferSize));
/* Copy the directory recursively. */
R_TRY(fssystem::CopyDirectoryRecursively(this->base_fs, save_fs, PathTool::RootPath, PathTool::RootPath, work_buf.get(), work_buf_size));
return this->Commit();
}
/* Overridden from IPathResolutionFileSystem */
Result DirectorySaveDataFileSystem::OpenFileImpl(std::unique_ptr<fs::fsa::IFile> *out_file, const char *path, fs::OpenMode mode) {
char full_path[fs::EntryNameLengthMax + 1];
R_TRY(this->ResolveFullPath(full_path, sizeof(full_path), path));
std::scoped_lock lk(this->accessor_mutex);
std::unique_ptr<fs::fsa::IFile> base_file;
R_TRY(this->base_fs->OpenFile(&base_file, full_path, mode));
std::unique_ptr<DirectorySaveDataFile> file(new (std::nothrow) DirectorySaveDataFile(std::move(base_file), this, mode));
R_UNLESS(file != nullptr, fs::ResultAllocationFailureInDirectorySaveDataFileSystem());
if (mode & fs::OpenMode_Write) {
this->open_writable_files++;
}
*out_file = std::move(file);
return ResultSuccess();
}
Result DirectorySaveDataFileSystem::CommitImpl() {
/* Here, Nintendo does the following (with retries): */
/* - Rename Committed -> Synchronizing. */
/* - Synchronize Working -> Synchronizing (deleting Synchronizing). */
/* - Rename Synchronizing -> Committed. */
std::scoped_lock lk(this->accessor_mutex);
R_UNLESS(this->open_writable_files == 0, fs::ResultPreconditionViolation());
const auto RenameCommitedDir = [&]() { return this->base_fs->RenameDirectory(CommittedDirectoryPath, SynchronizingDirectoryPath); };
const auto SynchronizeWorkingDir = [&]() { return this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath); };
const auto RenameSynchronizingDir = [&]() { return this->base_fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath); };
/* Rename Committed -> Synchronizing. */
R_TRY(fssystem::RetryFinitelyForTargetLocked(std::move(RenameCommitedDir)));
/* - Synchronize Working -> Synchronizing (deleting Synchronizing). */
R_TRY(fssystem::RetryFinitelyForTargetLocked(std::move(SynchronizeWorkingDir)));
/* - Rename Synchronizing -> Committed. */
R_TRY(fssystem::RetryFinitelyForTargetLocked(std::move(RenameSynchronizingDir)));
/* TODO: Should I call this->base_fs->Commit()? Nintendo does not. */
return ResultSuccess();
}
/* Overridden from IPathResolutionFileSystem but not commands. */
Result DirectorySaveDataFileSystem::CommitProvisionallyImpl(s64 counter) {
/* Nintendo does nothing here. */
return ResultSuccess();
}
Result DirectorySaveDataFileSystem::RollbackImpl() {
/* Initialize overwrites the working directory with the committed directory. */
return this->Initialize();
}
/* Explicitly overridden to be not implemented. */
Result DirectorySaveDataFileSystem::GetFreeSpaceSizeImpl(s64 *out, const char *path) {
return fs::ResultNotImplemented();
}
Result DirectorySaveDataFileSystem::GetTotalSpaceSizeImpl(s64 *out, const char *path) {
return fs::ResultNotImplemented();
}
Result DirectorySaveDataFileSystem::GetFileTimeStampRawImpl(fs::FileTimeStampRaw *out, const char *path) {
return fs::ResultNotImplemented();
}
Result DirectorySaveDataFileSystem::QueryEntryImpl(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const char *path) {
return fs::ResultNotImplemented();
}
Result DirectorySaveDataFileSystem::FlushImpl() {
return fs::ResultNotImplemented();
}
}

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 <stratosphere.hpp>
namespace ams::fssystem {
SubDirectoryFileSystem::SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *bp, bool unc)
: PathResolutionFileSystem(fs, unc)
{
this->base_path = nullptr;
R_ASSERT(this->Initialize(bp));
}
SubDirectoryFileSystem::SubDirectoryFileSystem(std::unique_ptr<fs::fsa::IFileSystem> fs, const char *bp, bool unc)
: PathResolutionFileSystem(std::move(fs), unc)
{
this->base_path = nullptr;
R_ASSERT(this->Initialize(bp));
}
SubDirectoryFileSystem::~SubDirectoryFileSystem() {
if (this->base_path != nullptr) {
std::free(this->base_path);
}
}
Result SubDirectoryFileSystem::Initialize(const char *bp) {
/* Make sure the path isn't too long. */
R_UNLESS(strnlen(bp, fs::EntryNameLengthMax + 1) <= fs::EntryNameLengthMax, fs::ResultTooLongPath());
/* Normalize the path. */
char normalized_path[fs::EntryNameLengthMax + 2];
size_t normalized_path_len;
R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, bp, sizeof(normalized_path), this->IsUncPreserved()));
/* Ensure terminating '/' */
if (!PathTool::IsSeparator(normalized_path[normalized_path_len - 1])) {
AMS_ASSERT(normalized_path_len + 2 <= sizeof(normalized_path));
normalized_path[normalized_path_len] = StringTraits::DirectorySeparator;
normalized_path[normalized_path_len + 1] = StringTraits::NullTerminator;
normalized_path_len++;
}
/* Allocate new path. */
this->base_path_len = normalized_path_len + 1;
this->base_path = static_cast<char *>(std::malloc(this->base_path_len));
R_UNLESS(this->base_path != nullptr, fs::ResultAllocationFailureInSubDirectoryFileSystem());
/* Copy path in. */
std::memcpy(this->base_path, normalized_path, normalized_path_len);
this->base_path[normalized_path_len] = StringTraits::NullTerminator;
return ResultSuccess();
}
Result SubDirectoryFileSystem::ResolveFullPath(char *out, size_t out_size, const char *relative_path) {
/* Ensure path will fit. */
R_UNLESS(this->base_path_len + strnlen(relative_path, fs::EntryNameLengthMax + 1) <= out_size, fs::ResultTooLongPath());
/* Copy base path. */
std::memcpy(out, this->base_path, this->base_path_len);
/* Normalize it. */
const size_t prefix_size = this->base_path_len - 2;
size_t normalized_len;
return PathTool::Normalize(out + prefix_size, &normalized_len, relative_path, out_size - prefix_size, this->IsUncPreserved());
}
}

View file

@ -0,0 +1,118 @@
/*
* 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>
namespace ams::fssystem {
namespace {
inline Result EnsureDirectoryExists(fs::fsa::IFileSystem *fs, const char *path) {
R_TRY_CATCH(fs->CreateDirectory(path)) {
R_CATCH(fs::ResultPathAlreadyExists) { /* If path already exists, there's no problem. */ }
} R_END_TRY_CATCH;
return ResultSuccess();
}
}
Result CopyFile(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_parent_path, const char *src_path, const fs::DirectoryEntry *entry, void *work_buf, size_t work_buf_size) {
/* Open source file. */
std::unique_ptr<fs::fsa::IFile> src_file;
R_TRY(src_fs->OpenFile(&src_file, src_path, fs::OpenMode_Read));
/* Open dst file. */
std::unique_ptr<fs::fsa::IFile> dst_file;
{
char dst_path[fs::EntryNameLengthMax + 1];
const size_t original_size = static_cast<size_t>(std::snprintf(dst_path, sizeof(dst_path), "%s%s", dst_parent_path, entry->name));
/* TODO: Error code? N aborts here. */
AMS_ASSERT(original_size < sizeof(dst_path));
R_TRY(dst_fs->CreateFile(dst_path, entry->file_size));
R_TRY(dst_fs->OpenFile(&dst_file, dst_path, fs::OpenMode_Write));
}
/* Read/Write file in work buffer sized chunks. */
s64 remaining = entry->file_size;
s64 offset = 0;
while (remaining > 0) {
size_t read_size;
R_TRY(src_file->Read(&read_size, offset, work_buf, work_buf_size, fs::ReadOption()));
R_TRY(dst_file->Write(offset, work_buf, read_size, fs::WriteOption()));
remaining -= read_size;
offset += read_size;
}
return ResultSuccess();
}
Result CopyDirectoryRecursively(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_path, const char *src_path, void *work_buf, size_t work_buf_size) {
char dst_path_buf[fs::EntryNameLengthMax + 1];
const size_t original_size = static_cast<size_t>(std::snprintf(dst_path_buf, sizeof(dst_path_buf), "%s", dst_path));
AMS_ASSERT(original_size < sizeof(dst_path_buf));
return IterateDirectoryRecursively(src_fs, src_path,
[&](const char *path, const fs::DirectoryEntry &entry) -> Result { /* On Enter Directory */
/* Update path, create new dir. */
std::strncat(dst_path_buf, entry.name, sizeof(dst_path_buf) - strnlen(dst_path_buf, sizeof(dst_path_buf) - 1) - 1);
std::strncat(dst_path_buf, "/", sizeof(dst_path_buf) - strnlen(dst_path_buf, sizeof(dst_path_buf) - 1) - 1);
return dst_fs->CreateDirectory(dst_path_buf);
},
[&](const char *path, const fs::DirectoryEntry &entry) -> Result { /* On Exit Directory */
/* Check we have a parent directory. */
const size_t len = strnlen(dst_path_buf, sizeof(dst_path_buf));
R_UNLESS(len >= 2, fs::ResultInvalidPathFormat());
/* Find previous separator, add null terminator */
char *cur = &dst_path_buf[len - 2];
while (!PathTool::IsSeparator(*cur) && cur > dst_path_buf) {
cur--;
}
cur[1] = StringTraits::NullTerminator;
return ResultSuccess();
},
[&](const char *path, const fs::DirectoryEntry &entry) -> Result { /* On File */
return CopyFile(dst_fs, src_fs, dst_path_buf, path, &entry, work_buf, work_buf_size);
}
);
}
Result EnsureDirectoryExistsRecursively(fs::fsa::IFileSystem *fs, const char *path) {
/* Normalize the path. */
char normalized_path[fs::EntryNameLengthMax + 1];
size_t normalized_path_len;
R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, path, sizeof(normalized_path)));
/* Repeatedly call CreateDirectory on each directory leading to the target. */
for (size_t i = 1; i < normalized_path_len; i++) {
/* If we detect a separator, create the directory. */
if (PathTool::IsSeparator(normalized_path[i])) {
normalized_path[i] = StringTraits::NullTerminator;
R_TRY(EnsureDirectoryExists(fs, normalized_path));
normalized_path[i] = StringTraits::DirectorySeparator;
}
}
/* Call CreateDirectory on the final path. */
R_TRY(EnsureDirectoryExists(fs, normalized_path));
return ResultSuccess();
}
}

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 <stratosphere.hpp>
namespace ams::hid {
namespace {
/* Global lock. */
os::Mutex 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::HasLaunchedProgram(ncm::ProgramId::Hid)) {
return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
}
InitializeHid();
}
g_initialized_hid = true;
}
return ResultSuccess();
}
}
Result GetKeysHeld(u64 *out) {
std::scoped_lock 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,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 <stratosphere.hpp>
namespace ams::hos {
namespace {
hos::Version g_hos_version;
bool g_has_cached;
os::Mutex g_mutex;
void CacheValues() {
if (__atomic_load_n(&g_has_cached, __ATOMIC_SEQ_CST)) {
return;
}
std::scoped_lock lk(g_mutex);
if (g_has_cached) {
return;
}
switch (exosphere::GetApiInfo().GetTargetFirmware()) {
case exosphere::TargetFirmware_100:
g_hos_version = hos::Version_100;
break;
case exosphere::TargetFirmware_200:
g_hos_version = hos::Version_200;
break;
case exosphere::TargetFirmware_300:
g_hos_version = hos::Version_300;
break;
case exosphere::TargetFirmware_400:
g_hos_version = hos::Version_400;
break;
case exosphere::TargetFirmware_500:
g_hos_version = hos::Version_500;
break;
case exosphere::TargetFirmware_600:
case exosphere::TargetFirmware_620:
g_hos_version = hos::Version_600;
break;
case exosphere::TargetFirmware_700:
g_hos_version = hos::Version_700;
break;
case exosphere::TargetFirmware_800:
g_hos_version = hos::Version_800;
break;
case exosphere::TargetFirmware_810:
g_hos_version = hos::Version_810;
break;
case exosphere::TargetFirmware_900:
g_hos_version = hos::Version_900;
case exosphere::TargetFirmware_910:
g_hos_version = hos::Version_910;
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
__atomic_store_n(&g_has_cached, true, __ATOMIC_SEQ_CST);
}
}
::ams::hos::Version GetVersion() {
CacheValues();
return g_hos_version;
}
void SetVersionForLibnx() {
u32 major = 0, minor = 0, micro = 0;
switch (hos::GetVersion()) {
case hos::Version_100:
major = 1;
minor = 0;
micro = 0;
break;
case hos::Version_200:
major = 2;
minor = 0;
micro = 0;
break;
case hos::Version_300:
major = 3;
minor = 0;
micro = 0;
break;
case hos::Version_400:
major = 4;
minor = 0;
micro = 0;
break;
case hos::Version_500:
major = 5;
minor = 0;
micro = 0;
break;
case hos::Version_600:
major = 6;
minor = 0;
micro = 0;
break;
case hos::Version_700:
major = 7;
minor = 0;
micro = 0;
break;
case hos::Version_800:
major = 8;
minor = 0;
micro = 0;
break;
case hos::Version_810:
major = 8;
minor = 1;
micro = 0;
break;
case hos::Version_900:
major = 9;
minor = 0;
micro = 0;
case hos::Version_910:
major = 9;
minor = 1;
micro = 0;
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
hosversionSet(MAKEHOSVERSION(major, minor, micro));
}
}

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 <stratosphere.hpp>
namespace ams::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 {
R_UNLESS(std::memcmp(this->magic, ArchiveHeaderMagic, sizeof(ArchiveHeaderMagic)) == 0, ResultInvalidKeyValue());
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 {
R_UNLESS(std::memcmp(this->magic, ArchiveEntryMagic, sizeof(ArchiveEntryMagic)) == 0, ResultInvalidKeyValue());
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. */
R_UNLESS(this->offset + size <= this->buffer.GetSize(), ResultInvalidKeyValue());
R_UNLESS(this->offset + size <= this->offset, ResultInvalidKeyValue());
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. */
AMS_ASSERT(this->offset == 0);
/* 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. */
AMS_ASSERT(this->offset != 0);
/* 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. */
AMS_ASSERT(this->offset != 0);
/* 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. */
AMS_ASSERT(key_size == header.key_size);
AMS_ASSERT(value_size == header.value_size);
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. */
R_UNLESS(this->offset + size <= this->buffer.GetSize(), ResultInvalidKeyValue());
R_UNLESS(this->offset + size <= this->offset, ResultInvalidKeyValue());
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. */
AMS_ASSERT(this->offset == 0);
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. */
AMS_ASSERT(this->offset != 0);
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,318 @@
/*
* 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>
namespace ams::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));
R_UNLESS(this->entries != nullptr, ResultBufferInsufficient());
}
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));
AMS_ASSERT(this->entries != nullptr);
}
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. */
AMS_ASSERT(key_size <= MaxKeySize);
/* 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;
R_UNLESS(file_name_len >= FileExtensionLength + 2, ResultInvalidKeyValue());
R_UNLESS(file_name.EndsWith(FileExtension), ResultInvalidKeyValue());
R_UNLESS(util::IsAligned(key_name_len, 2), ResultInvalidKeyValue());
/* Validate that we have space for the converted key. */
const size_t key_size = key_name_len / 2;
R_UNLESS(key_size <= max_out_size, ResultBufferInsufficient());
/* 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;
R_UNLESS(stat(dir, &st) == 0, fs::ResultPathNotFound());
R_UNLESS((S_ISDIR(st.st_mode)), fs::ResultPathNotFound());
}
/* 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. */
R_UNLESS(key_size <= MaxKeySize, ResultOutOfKeyResource());
/* 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_CONVERT(fs::ResultPathNotFound, ResultKeyNotFound())
} 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. */
R_UNLESS(value_size <= max_out_size, ResultBufferInsufficient());
/* Read the value. */
R_UNLESS(fread(out_value, value_size, 1, fp) == 1, 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. */
R_UNLESS(key_size <= MaxKeySize, ResultOutOfKeyResource());
/* 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_CONVERT(fs::ResultPathNotFound, ResultKeyNotFound())
} 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. */
R_UNLESS(key_size <= MaxKeySize, ResultOutOfKeyResource());
/* 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");
R_UNLESS(fp != nullptr, fsdevGetLastResult());
ON_SCOPE_EXIT { fclose(fp); };
/* Write the value file. */
R_UNLESS(fwrite(value, value_size, 1, fp) == 1, 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. */
R_UNLESS(key_size <= MaxKeySize, ResultOutOfKeyResource());
/* 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_CONVERT(fs::ResultPathNotFound, ResultKeyNotFound())
} R_END_TRY_CATCH;
}
return ResultSuccess();
}
}

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 <switch.h>
#include "ldr_ams.h"
static Result _ldrAtmosphereHasLaunchedProgram(Service *srv, bool *out, u64 program_id) {
u8 tmp;
Result rc = serviceDispatchInOut(srv, 65000, program_id, tmp);
if (R_SUCCEEDED(rc) && out) *out = tmp & 1;
return rc;
}
Result ldrDmntAtmosphereHasLaunchedProgram(bool *out, u64 program_id) {
return _ldrAtmosphereHasLaunchedProgram(ldrDmntGetServiceSession(), out, program_id);
}
Result ldrPmAtmosphereHasLaunchedProgram(bool *out, u64 program_id) {
return _ldrAtmosphereHasLaunchedProgram(ldrPmGetServiceSession(), out, program_id);
}
Result ldrPmAtmosphereGetProgramInfo(LoaderProgramInfo *out_program_info, CfgOverrideStatus *out_status, const NcmProgramLocation *loc) {
return serviceDispatchInOut(ldrPmGetServiceSession(), 65001, *loc, *out_status,
.buffer_attrs = { SfBufferAttr_Out | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize },
.buffers = { { out_program_info, sizeof(*out_program_info) } },
);
}
Result ldrPmAtmospherePinProgram(u64 *out, const NcmProgramLocation *loc, const CfgOverrideStatus *status) {
const struct {
NcmProgramLocation loc;
CfgOverrideStatus status;
} in = { *loc, *status };
return serviceDispatchInOut(ldrPmGetServiceSession(), 65002, in, *out);
}

View file

@ -0,0 +1,27 @@
/**
* @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
typedef struct {
u64 keys_down;
u64 flags;
} CfgOverrideStatus;
Result ldrPmAtmosphereHasLaunchedProgram(bool *out, u64 program_id);
Result ldrDmntAtmosphereHasLaunchedProgram(bool *out, u64 program_id);
Result ldrPmAtmosphereGetProgramInfo(LoaderProgramInfo *out, CfgOverrideStatus *out_status, const NcmProgramLocation *loc);
Result ldrPmAtmospherePinProgram(u64 *out, const NcmProgramLocation *loc, const CfgOverrideStatus *status);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,54 @@
/*
* 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 "ldr_ams.h"
namespace ams::ldr::pm {
/* Information API. */
Result CreateProcess(Handle *out, PinId pin_id, u32 flags, Handle reslimit) {
return ldrPmCreateProcess(pin_id.value, flags, reslimit, out);
}
Result GetProgramInfo(ProgramInfo *out, const ncm::ProgramLocation &loc) {
return ldrPmGetProgramInfo(reinterpret_cast<const NcmProgramLocation *>(&loc), reinterpret_cast<LoaderProgramInfo *>(out));
}
Result PinProgram(PinId *out, const ncm::ProgramLocation &loc) {
static_assert(sizeof(*out) == sizeof(u64), "PinId definition!");
return ldrPmPinProgram(reinterpret_cast<const NcmProgramLocation *>(&loc), reinterpret_cast<u64 *>(out));
}
Result UnpinProgram(PinId pin_id) {
return ldrPmUnpinProgram(pin_id.value);
}
Result HasLaunchedProgram(bool *out, ncm::ProgramId program_id) {
return ldrPmAtmosphereHasLaunchedProgram(out, static_cast<u64>(program_id));
}
Result AtmosphereGetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc) {
static_assert(sizeof(*out_status) == sizeof(CfgOverrideStatus), "CfgOverrideStatus definition!");
return ldrPmAtmosphereGetProgramInfo(reinterpret_cast<LoaderProgramInfo *>(out), reinterpret_cast<CfgOverrideStatus *>(out_status), reinterpret_cast<const NcmProgramLocation *>(&loc));
}
Result AtmospherePinProgram(PinId *out, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status) {
static_assert(sizeof(*out) == sizeof(u64), "PinId definition!");
static_assert(sizeof(status) == sizeof(CfgOverrideStatus), "CfgOverrideStatus definition!");
return ldrPmAtmospherePinProgram(reinterpret_cast<u64 *>(out), reinterpret_cast<const NcmProgramLocation *>(&loc), reinterpret_cast<const CfgOverrideStatus *>(&status));
}
}

View file

@ -0,0 +1,236 @@
/*
* 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>
namespace ams::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, dd::GetCurrentProcessHandle()));
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 svc::ResultOutOfMemory();
}
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, dd::GetCurrentProcessHandle()));
cur_base = address_space.aslr_base;
cur_end = cur_base + size;
if (cur_end <= cur_base) {
return svc::ResultOutOfMemory();
}
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 svc::ResultOutOfMemory();
}
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 svc::ResultOutOfMemory();
}
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 svc::ResultOutOfMemory();
}
cur_base = mem_info.addr + mem_info.size;
if (cur_base >= address_space.aslr_end) {
return svc::ResultOutOfMemory();
}
}
cur_end = cur_base + size;
if (cur_base + size <= cur_base) {
return svc::ResultOutOfMemory();
}
}
}
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 ro::ResultOutOfAddressSpace();
}
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(svc::ResultInvalidCurrentMemoryState) {
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 ro::ResultOutOfAddressSpace();
}
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 ro::ResultOutOfAddressSpace();
}
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(svc::ResultInvalidCurrentMemoryState) {
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 ro::ResultOutOfAddressSpace();
}
}
/* 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 (hos::GetVersion() >= hos::Version_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 (hos::GetVersion() >= hos::Version_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 (hos::GetVersion() >= hos::Version_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,179 @@
/*
* 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 "os_inter_process_event.hpp"
namespace ams::os::impl {
namespace {
Result CreateEventHandles(Handle *out_readable, Handle *out_writable) {
/* Create the event handles. */
R_TRY_CATCH(svcCreateEvent(out_writable, out_readable)) {
R_CONVERT(svc::ResultOutOfResource, ResultOutOfResource());
} R_END_TRY_CATCH_WITH_ASSERT;
return ResultSuccess();
}
}
InterProcessEvent::InterProcessEvent(bool autoclear) : is_initialized(false) {
R_ASSERT(this->Initialize(autoclear));
}
InterProcessEvent::~InterProcessEvent() {
this->Finalize();
}
Result InterProcessEvent::Initialize(bool autoclear) {
AMS_ASSERT(!this->is_initialized);
Handle rh, wh;
R_TRY(CreateEventHandles(&rh, &wh));
this->Initialize(rh, true, wh, true, autoclear);
return ResultSuccess();
}
void InterProcessEvent::Initialize(Handle read_handle, bool manage_read_handle, Handle write_handle, bool manage_write_handle, bool autoclear) {
AMS_ASSERT(!this->is_initialized);
AMS_ASSERT(read_handle != INVALID_HANDLE || write_handle != INVALID_HANDLE);
this->read_handle = read_handle;
this->manage_read_handle = manage_read_handle;
this->write_handle = write_handle;
this->manage_write_handle = manage_write_handle;
this->auto_clear = autoclear;
this->is_initialized = true;
}
Handle InterProcessEvent::DetachReadableHandle() {
AMS_ASSERT(this->is_initialized);
const Handle handle = this->read_handle;
AMS_ASSERT(handle != INVALID_HANDLE);
this->read_handle = INVALID_HANDLE;
this->manage_read_handle = false;
return handle;
}
Handle InterProcessEvent::DetachWritableHandle() {
AMS_ASSERT(this->is_initialized);
const Handle handle = this->write_handle;
AMS_ASSERT(handle != INVALID_HANDLE);
this->write_handle = INVALID_HANDLE;
this->manage_write_handle = false;
return handle;
}
Handle InterProcessEvent::GetReadableHandle() const {
AMS_ASSERT(this->is_initialized);
return this->read_handle;
}
Handle InterProcessEvent::GetWritableHandle() const {
AMS_ASSERT(this->is_initialized);
return this->write_handle;
}
void InterProcessEvent::Finalize() {
if (this->is_initialized) {
if (this->manage_read_handle && this->read_handle != INVALID_HANDLE) {
R_ASSERT(svcCloseHandle(this->read_handle));
}
if (this->manage_write_handle && this->write_handle != INVALID_HANDLE) {
R_ASSERT(svcCloseHandle(this->write_handle));
}
}
this->read_handle = INVALID_HANDLE;
this->manage_read_handle = false;
this->write_handle = INVALID_HANDLE;
this->manage_write_handle = false;
this->is_initialized = false;
}
void InterProcessEvent::Signal() {
R_ASSERT(svcSignalEvent(this->GetWritableHandle()));
}
void InterProcessEvent::Reset() {
Handle handle = this->GetReadableHandle();
if (handle == INVALID_HANDLE) {
handle = this->GetWritableHandle();
}
R_ASSERT(svcClearEvent(handle));
}
void InterProcessEvent::Wait() {
const Handle handle = this->GetReadableHandle();
while (true) {
/* Continuously wait, until success. */
R_TRY_CATCH(svcWaitSynchronizationSingle(handle, U64_MAX)) {
R_CATCH(svc::ResultCancelled) { continue; }
} R_END_TRY_CATCH_WITH_ASSERT;
/* Clear, if we must. */
if (this->auto_clear) {
R_TRY_CATCH(svcResetSignal(handle)) {
/* Some other thread might have caught this before we did. */
R_CATCH(svc::ResultInvalidState) { continue; }
} R_END_TRY_CATCH_WITH_ASSERT;
}
return;
}
}
bool InterProcessEvent::TryWait() {
const Handle handle = this->GetReadableHandle();
if (this->auto_clear) {
/* Auto-clear. Just try to reset. */
return R_SUCCEEDED(svcResetSignal(handle));
} else {
/* Not auto-clear. */
while (true) {
/* Continuously wait, until success or timeout. */
R_TRY_CATCH(svcWaitSynchronizationSingle(handle, 0)) {
R_CATCH(svc::ResultTimedOut) { return false; }
R_CATCH(svc::ResultCancelled) { continue; }
} R_END_TRY_CATCH_WITH_ASSERT;
/* We succeeded, so we're signaled. */
return true;
}
}
}
bool InterProcessEvent::TimedWait(u64 ns) {
const Handle handle = this->GetReadableHandle();
TimeoutHelper timeout_helper(ns);
while (true) {
/* Continuously wait, until success or timeout. */
R_TRY_CATCH(svcWaitSynchronizationSingle(handle, timeout_helper.NsUntilTimeout())) {
R_CATCH(svc::ResultTimedOut) { return false; }
R_CATCH(svc::ResultCancelled) { continue; }
} R_END_TRY_CATCH_WITH_ASSERT;
/* Clear, if we must. */
if (this->auto_clear) {
R_TRY_CATCH(svcResetSignal(handle)) {
/* Some other thread might have caught this before we did. */
R_CATCH(svc::ResultInvalidState) { continue; }
} R_END_TRY_CATCH_WITH_ASSERT;
}
return true;
}
}
}

View file

@ -0,0 +1,54 @@
/*
* 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 <stratosphere.hpp>
namespace ams::os::impl {
class WaitableHolderOfInterProcessEvent;
class InterProcessEvent {
friend class WaitableHolderOfInterProcessEvent;
NON_COPYABLE(InterProcessEvent);
NON_MOVEABLE(InterProcessEvent);
private:
Handle read_handle;
Handle write_handle;
bool manage_read_handle;
bool manage_write_handle;
bool auto_clear;
bool is_initialized;
public:
InterProcessEvent() : is_initialized(false) { /* ... */ }
InterProcessEvent(bool autoclear);
~InterProcessEvent();
Result Initialize(bool autoclear = true);
void Initialize(Handle read_handle, bool manage_read_handle, Handle write_handle, bool manage_write_handle, bool autoclear = true);
Handle DetachReadableHandle();
Handle DetachWritableHandle();
Handle GetReadableHandle() const;
Handle GetWritableHandle() const;
void Finalize();
void Signal();
void Reset();
void Wait();
bool TryWait();
bool TimedWait(u64 ns);
};
}

View file

@ -0,0 +1,77 @@
/*
* 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 <stratosphere.hpp>
namespace ams::os::impl {
class WaitableObjectList;
class WaitableManagerImpl;
class WaitableHolderBase {
public:
util::IntrusiveListNode manager_node;
util::IntrusiveListNode object_list_node;
private:
WaitableManagerImpl *manager = nullptr;
public:
/* Gets whether the held waitable is currently signaled. */
virtual TriBool IsSignaled() const = 0;
/* Adds to manager's object list, returns is signaled. */
virtual TriBool LinkToObjectList() = 0;
/* Removes from the manager's object list. */
virtual void UnlinkFromObjectList() = 0;
/* Gets handle to output, returns INVALID_HANDLE on failure. */
virtual Handle GetHandle() const = 0;
/* Gets the amount of time remaining until this wakes up. */
virtual u64 GetWakeupTime() const {
return U64_MAX;
}
/* Interface with manager. */
void SetManager(WaitableManagerImpl *m) {
this->manager = m;
}
WaitableManagerImpl *GetManager() const {
return this->manager;
}
bool IsLinkedToManager() const {
return this->manager != nullptr;
}
};
class WaitableHolderOfUserObject : public WaitableHolderBase {
public:
/* All user objects have no handle to wait on. */
virtual Handle GetHandle() const override {
return INVALID_HANDLE;
}
};
class WaitableHolderOfKernelObject : public WaitableHolderBase {
public:
/* All kernel objects have native handles, and thus don't have object list semantics. */
virtual TriBool LinkToObjectList() override {
return TriBool::Undefined;
}
virtual void UnlinkFromObjectList() override {
/* ... */
}
};
}

View file

@ -0,0 +1,56 @@
/*
* 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 "os_waitable_holder_of_handle.hpp"
#include "os_waitable_holder_of_event.hpp"
#include "os_waitable_holder_of_inter_process_event.hpp"
#include "os_waitable_holder_of_interrupt_event.hpp"
#include "os_waitable_holder_of_thread.hpp"
#include "os_waitable_holder_of_semaphore.hpp"
#include "os_waitable_holder_of_message_queue.hpp"
namespace ams::os::impl {
struct WaitableHolderImpl {
union {
TYPED_STORAGE(WaitableHolderOfHandle) holder_of_handle_storage;
TYPED_STORAGE(WaitableHolderOfEvent) holder_of_event_storage;
TYPED_STORAGE(WaitableHolderOfInterProcessEvent) holder_of_inter_process_event_storage;
TYPED_STORAGE(WaitableHolderOfInterruptEvent) holder_of_interrupt_event_storage;
TYPED_STORAGE(WaitableHolderOfThread) holder_of_thread_storage;
TYPED_STORAGE(WaitableHolderOfSemaphore) holder_of_semaphore_storage;
TYPED_STORAGE(WaitableHolderOfMessageQueueForNotFull) holder_of_mq_for_not_full_storage;
TYPED_STORAGE(WaitableHolderOfMessageQueueForNotEmpty) holder_of_mq_for_not_empty_storage;
};
};
#define CHECK_HOLDER(T) \
static_assert(std::is_base_of<::ams::os::impl::WaitableHolderBase, T>::value && std::is_trivially_destructible<T>::value, #T)
CHECK_HOLDER(WaitableHolderOfHandle);
CHECK_HOLDER(WaitableHolderOfEvent);
CHECK_HOLDER(WaitableHolderOfInterProcessEvent);
CHECK_HOLDER(WaitableHolderOfInterruptEvent);
CHECK_HOLDER(WaitableHolderOfThread);
CHECK_HOLDER(WaitableHolderOfSemaphore);
CHECK_HOLDER(WaitableHolderOfMessageQueueForNotFull);
CHECK_HOLDER(WaitableHolderOfMessageQueueForNotEmpty);
#undef CHECK_HOLDER
static_assert(std::is_trivial<WaitableHolderImpl>::value && std::is_trivially_destructible<WaitableHolderImpl>::value, "WaitableHolderImpl");
static_assert(sizeof(WaitableHolderImpl) == WaitableHolder::ImplStorageSize, "WaitableHolderImpl size");
}

View file

@ -0,0 +1,52 @@
/*
* 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 "os_waitable_holder_base.hpp"
#include "os_waitable_object_list.hpp"
namespace ams::os::impl {
class WaitableHolderOfEvent : public WaitableHolderOfUserObject {
private:
Event *event;
private:
TriBool IsSignaledImpl() const {
return this->event->signaled ? TriBool::True : TriBool::False;
}
public:
explicit WaitableHolderOfEvent(Event *e) : event(e) { /* ... */ }
/* IsSignaled, Link, Unlink implemented. */
virtual TriBool IsSignaled() const override {
std::scoped_lock lk(this->event->lock);
return this->IsSignaledImpl();
}
virtual TriBool LinkToObjectList() override {
std::scoped_lock lk(this->event->lock);
GetReference(this->event->waitable_object_list_storage).LinkWaitableHolder(*this);
return this->IsSignaledImpl();
}
virtual void UnlinkFromObjectList() override {
std::scoped_lock lk(this->event->lock);
GetReference(this->event->waitable_object_list_storage).UnlinkWaitableHolder(*this);
}
};
}

View file

@ -0,0 +1,37 @@
/*
* 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 "os_waitable_holder_base.hpp"
namespace ams::os::impl {
class WaitableHolderOfHandle : public WaitableHolderOfKernelObject {
private:
Handle handle;
public:
explicit WaitableHolderOfHandle(Handle h) : handle(h) { /* ... */ }
/* IsSignaled, GetHandle both implemented. */
virtual TriBool IsSignaled() const override {
return TriBool::Undefined;
}
virtual Handle GetHandle() const override {
return this->handle;
}
};
}

View file

@ -0,0 +1,39 @@
/*
* 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 "os_waitable_holder_base.hpp"
#include "os_inter_process_event.hpp"
namespace ams::os::impl {
class WaitableHolderOfInterProcessEvent : public WaitableHolderOfKernelObject {
private:
InterProcessEvent *event;
public:
explicit WaitableHolderOfInterProcessEvent(InterProcessEvent *e) : event(e) { /* ... */ }
/* IsSignaled, GetHandle both implemented. */
virtual TriBool IsSignaled() const override {
return TriBool::Undefined;
}
virtual Handle GetHandle() const override {
AMS_ASSERT(this->event->is_initialized);
return this->event->GetReadableHandle();
}
};
}

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/>.
*/
#pragma once
#include "os_waitable_holder_base.hpp"
namespace ams::os::impl {
class WaitableHolderOfInterruptEvent : public WaitableHolderOfKernelObject {
private:
InterruptEvent *event;
public:
explicit WaitableHolderOfInterruptEvent(InterruptEvent *e) : event(e) { /* ... */ }
/* IsSignaled, GetHandle both implemented. */
virtual TriBool IsSignaled() const override {
return TriBool::Undefined;
}
virtual Handle GetHandle() const override {
AMS_ASSERT(this->event->is_initialized);
return this->event->handle.Get();
}
};
}

View file

@ -0,0 +1,75 @@
/*
* 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 "os_waitable_holder_base.hpp"
#include "os_waitable_object_list.hpp"
namespace ams::os::impl {
template<MessageQueueWaitKind WaitKind>
class WaitableHolderOfMessageQueue : public WaitableHolderOfUserObject {
static_assert(WaitKind == MessageQueueWaitKind::ForNotEmpty || WaitKind == MessageQueueWaitKind::ForNotFull, "MessageQueueHolder WaitKind!");
private:
MessageQueue *message_queue;
private:
constexpr inline TriBool IsSignaledImpl() const {
if constexpr (WaitKind == MessageQueueWaitKind::ForNotEmpty) {
/* ForNotEmpty. */
return this->message_queue->IsEmpty() ? TriBool::False : TriBool::True;
} else if constexpr (WaitKind == MessageQueueWaitKind::ForNotFull) {
/* ForNotFull */
return this->message_queue->IsFull() ? TriBool::False : TriBool::True;
} else {
static_assert(WaitKind != WaitKind);
}
}
constexpr inline WaitableObjectList &GetObjectList() const {
if constexpr (WaitKind == MessageQueueWaitKind::ForNotEmpty) {
return GetReference(this->message_queue->waitlist_not_empty);
} else if constexpr (WaitKind == MessageQueueWaitKind::ForNotFull) {
return GetReference(this->message_queue->waitlist_not_full);
} else {
static_assert(WaitKind != WaitKind);
}
}
public:
explicit WaitableHolderOfMessageQueue(MessageQueue *mq) : message_queue(mq) { /* ... */ }
/* IsSignaled, Link, Unlink implemented. */
virtual TriBool IsSignaled() const override {
std::scoped_lock lk(this->message_queue->queue_lock);
return this->IsSignaledImpl();
}
virtual TriBool LinkToObjectList() override {
std::scoped_lock lk(this->message_queue->queue_lock);
this->GetObjectList().LinkWaitableHolder(*this);
return this->IsSignaledImpl();
}
virtual void UnlinkFromObjectList() override {
std::scoped_lock lk(this->message_queue->queue_lock);
this->GetObjectList().UnlinkWaitableHolder(*this);
}
};
using WaitableHolderOfMessageQueueForNotEmpty = WaitableHolderOfMessageQueue<MessageQueueWaitKind::ForNotEmpty>;
using WaitableHolderOfMessageQueueForNotFull = WaitableHolderOfMessageQueue<MessageQueueWaitKind::ForNotFull>;
}

View file

@ -0,0 +1,52 @@
/*
* 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 "os_waitable_holder_base.hpp"
#include "os_waitable_object_list.hpp"
namespace ams::os::impl {
class WaitableHolderOfSemaphore : public WaitableHolderOfUserObject {
private:
Semaphore *semaphore;
private:
TriBool IsSignaledImpl() const {
return this->semaphore->count > 0 ? TriBool::True : TriBool::False;
}
public:
explicit WaitableHolderOfSemaphore(Semaphore *s) : semaphore(s) { /* ... */ }
/* IsSignaled, Link, Unlink implemented. */
virtual TriBool IsSignaled() const override {
std::scoped_lock lk(this->semaphore->mutex);
return this->IsSignaledImpl();
}
virtual TriBool LinkToObjectList() override {
std::scoped_lock lk(this->semaphore->mutex);
GetReference(this->semaphore->waitlist).LinkWaitableHolder(*this);
return this->IsSignaledImpl();
}
virtual void UnlinkFromObjectList() override {
std::scoped_lock lk(this->semaphore->mutex);
GetReference(this->semaphore->waitlist).UnlinkWaitableHolder(*this);
}
};
}

View file

@ -0,0 +1,39 @@
/*
* 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 "os_waitable_holder_base.hpp"
namespace ams::os::impl {
/* Nintendo implements this as a user wait object, operating on Thread state. */
/* Libnx doesn't have an equivalent, so we'll use the thread's handle for kernel semantics. */
class WaitableHolderOfThread : public WaitableHolderOfKernelObject {
private:
Thread *thread;
public:
explicit WaitableHolderOfThread(Thread *t) : thread(t) { /* ... */ }
/* IsSignaled, GetHandle both implemented. */
virtual TriBool IsSignaled() const override {
return TriBool::Undefined;
}
virtual Handle GetHandle() const override {
return this->thread->GetHandle();
}
};
}

View file

@ -0,0 +1,177 @@
/*
* 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 "os_waitable_manager_impl.hpp"
#include "os_waitable_object_list.hpp"
namespace ams::os::impl{
WaitableHolderBase *WaitableManagerImpl::WaitAnyImpl(bool infinite, u64 timeout) {
/* Set processing thread handle while in scope. */
this->waiting_thread_handle = threadGetCurHandle();
ON_SCOPE_EXIT { this->waiting_thread_handle = INVALID_HANDLE; };
/* Prepare for processing. */
this->signaled_holder = nullptr;
WaitableHolderBase *result = this->LinkHoldersToObjectList();
/* Check if we've been signaled. */
{
std::scoped_lock lk(this->lock);
if (this->signaled_holder != nullptr) {
result = this->signaled_holder;
}
}
/* Process object array. */
if (result == nullptr) {
result = this->WaitAnyHandleImpl(infinite, timeout);
}
/* Unlink holders from the current object list. */
this->UnlinkHoldersFromObjectList();
return result;
}
WaitableHolderBase *WaitableManagerImpl::WaitAnyHandleImpl(bool infinite, u64 timeout) {
Handle object_handles[MaximumHandleCount];
WaitableHolderBase *objects[MaximumHandleCount];
const size_t count = this->BuildHandleArray(object_handles, objects);
const u64 end_time = infinite ? U64_MAX : armTicksToNs(armGetSystemTick());
while (true) {
this->current_time = armTicksToNs(armGetSystemTick());
u64 min_timeout = 0;
WaitableHolderBase *min_timeout_object = this->RecalculateNextTimeout(&min_timeout, end_time);
s32 index;
if (count == 0 && min_timeout == 0) {
index = WaitTimedOut;
} else {
index = this->WaitSynchronization(object_handles, count, min_timeout);
AMS_ASSERT(index != WaitInvalid);
}
switch (index) {
case WaitTimedOut:
if (min_timeout_object) {
this->current_time = armTicksToNs(armGetSystemTick());
if (min_timeout_object->IsSignaled() == TriBool::True) {
std::scoped_lock lk(this->lock);
this->signaled_holder = min_timeout_object;
return this->signaled_holder;
}
continue;
}
return nullptr;
case WaitCancelled:
if (this->signaled_holder) {
return this->signaled_holder;
}
continue;
default: /* 0 - 0x3F, valid. */
{
std::scoped_lock lk(this->lock);
this->signaled_holder = objects[index];
return this->signaled_holder;
}
}
}
}
s32 WaitableManagerImpl::WaitSynchronization(Handle *handles, size_t count, u64 timeout) {
s32 index = WaitInvalid;
R_TRY_CATCH(svcWaitSynchronization(&index, handles, count, timeout)) {
R_CATCH(svc::ResultTimedOut) { return WaitTimedOut; }
R_CATCH(svc::ResultCancelled) { return WaitCancelled; }
/* All other results are critical errors. */
/* svc::ResultThreadTerminating */
/* svc::ResultInvalidHandle. */
/* svc::ResultInvalidPointer */
/* svc::ResultOutOfRange */
} R_END_TRY_CATCH_WITH_ASSERT;
return index;
}
size_t WaitableManagerImpl::BuildHandleArray(Handle *out_handles, WaitableHolderBase **out_objects) {
size_t count = 0;
for (WaitableHolderBase &holder_base : this->waitable_list) {
if (Handle handle = holder_base.GetHandle(); handle != INVALID_HANDLE) {
AMS_ASSERT(count < MaximumHandleCount);
out_handles[count] = handle;
out_objects[count] = &holder_base;
count++;
}
}
return count;
}
WaitableHolderBase *WaitableManagerImpl::LinkHoldersToObjectList() {
WaitableHolderBase *signaled_holder = nullptr;
for (WaitableHolderBase &holder_base : this->waitable_list) {
TriBool is_signaled = holder_base.LinkToObjectList();
if (signaled_holder == nullptr && is_signaled == TriBool::True) {
signaled_holder = &holder_base;
}
}
return signaled_holder;
}
void WaitableManagerImpl::UnlinkHoldersFromObjectList() {
for (WaitableHolderBase &holder_base : this->waitable_list) {
holder_base.UnlinkFromObjectList();
}
}
WaitableHolderBase *WaitableManagerImpl::RecalculateNextTimeout(u64 *out_min_timeout, u64 end_time) {
WaitableHolderBase *min_timeout_holder = nullptr;
u64 min_time = end_time;
for (WaitableHolderBase &holder_base : this->waitable_list) {
if (const u64 cur_time = holder_base.GetWakeupTime(); cur_time < min_time) {
min_timeout_holder = &holder_base;
min_time = cur_time;
}
}
if (min_time < this->current_time) {
*out_min_timeout = 0;
} else {
*out_min_timeout = min_time - this->current_time;
}
return min_timeout_holder;
}
void WaitableManagerImpl::SignalAndWakeupThread(WaitableHolderBase *holder_base) {
std::scoped_lock lk(this->lock);
if (this->signaled_holder == nullptr) {
this->signaled_holder = holder_base;
R_ASSERT(svcCancelSynchronization(this->waiting_thread_handle));
}
}
}

View file

@ -0,0 +1,94 @@
/*
* 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 "os_waitable_holder_base.hpp"
namespace ams::os::impl {
class WaitableManagerImpl {
public:
static constexpr size_t MaximumHandleCount = 0x40;
static constexpr s32 WaitInvalid = -3;
static constexpr s32 WaitCancelled = -2;
static constexpr s32 WaitTimedOut = -1;
using ListType = util::IntrusiveListMemberTraits<&WaitableHolderBase::manager_node>::ListType;
private:
ListType waitable_list;
WaitableHolderBase *signaled_holder;
u64 current_time;
Mutex lock;
Handle waiting_thread_handle;
private:
WaitableHolderBase *WaitAnyImpl(bool infinite, u64 timeout);
WaitableHolderBase *WaitAnyHandleImpl(bool infinite, u64 timeout);
s32 WaitSynchronization(Handle *handles, size_t count, u64 timeout);
size_t BuildHandleArray(Handle *out_handles, WaitableHolderBase **out_objects);
WaitableHolderBase *LinkHoldersToObjectList();
void UnlinkHoldersFromObjectList();
WaitableHolderBase *RecalculateNextTimeout(u64 *out_min_timeout, u64 end_time);
public:
/* Wait. */
WaitableHolderBase *WaitAny() {
return this->WaitAnyImpl(true, U64_MAX);
}
WaitableHolderBase *TryWaitAny() {
return this->WaitAnyImpl(false, 0);
}
WaitableHolderBase *TimedWaitAny(u64 timeout) {
return this->WaitAnyImpl(false, timeout);
}
/* List management. */
bool IsEmpty() const {
return this->waitable_list.empty();
}
void LinkWaitableHolder(WaitableHolderBase &holder_base) {
this->waitable_list.push_back(holder_base);
}
void UnlinkWaitableHolder(WaitableHolderBase &holder_base) {
this->waitable_list.erase(this->waitable_list.iterator_to(holder_base));
}
void UnlinkAll() {
while (!this->IsEmpty()) {
this->waitable_list.front().SetManager(nullptr);
this->waitable_list.pop_front();
}
}
void MoveAllFrom(WaitableManagerImpl &other) {
/* Set manager for all of the other's waitables. */
for (auto &w : other.waitable_list) {
w.SetManager(this);
}
this->waitable_list.splice(this->waitable_list.end(), other.waitable_list);
}
/* Other. */
u64 GetCurrentTime() const {
return this->current_time;
}
void SignalAndWakeupThread(WaitableHolderBase *holder_base);
};
}

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 "os_waitable_holder_base.hpp"
#include "os_waitable_manager_impl.hpp"
namespace ams::os::impl {
class WaitableObjectList {
public:
using ListType = util::IntrusiveListMemberTraits<&WaitableHolderBase::object_list_node>::ListType;
private:
ListType object_list;
public:
void SignalAllThreads() {
for (WaitableHolderBase &holder_base : this->object_list) {
holder_base.GetManager()->SignalAndWakeupThread(&holder_base);
}
}
void BroadcastAllThreads() {
for (WaitableHolderBase &holder_base : this->object_list) {
holder_base.GetManager()->SignalAndWakeupThread(nullptr);
}
}
bool IsEmpty() const {
return this->object_list.empty();
}
void LinkWaitableHolder(WaitableHolderBase &holder_base) {
this->object_list.push_back(holder_base);
}
void UnlinkWaitableHolder(WaitableHolderBase &holder_base) {
this->object_list.erase(this->object_list.iterator_to(holder_base));
}
};
}

View file

@ -0,0 +1,105 @@
/*
* 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 "impl/os_waitable_object_list.hpp"
namespace ams::os {
Event::Event(bool a, bool s) : auto_clear(a), signaled(s) {
new (GetPointer(this->waitable_object_list_storage)) impl::WaitableObjectList();
}
Event::~Event() {
GetReference(this->waitable_object_list_storage).~WaitableObjectList();
}
void Event::Signal() {
std::scoped_lock lk(this->lock);
/* If we're already signaled, nothing more to do. */
if (this->signaled) {
return;
}
this->signaled = true;
/* Signal! */
if (this->auto_clear) {
/* If we're auto clear, signal one thread, which will clear. */
this->cv.Signal();
} else {
/* If we're manual clear, increment counter and wake all. */
this->counter++;
this->cv.Broadcast();
}
/* Wake up whatever manager, if any. */
GetReference(this->waitable_object_list_storage).SignalAllThreads();
}
void Event::Reset() {
std::scoped_lock lk(this->lock);
this->signaled = false;
}
void Event::Wait() {
std::scoped_lock lk(this->lock);
u64 cur_counter = this->counter;
while (!this->signaled) {
if (this->counter != cur_counter) {
break;
}
this->cv.Wait(&this->lock);
}
if (this->auto_clear) {
this->signaled = false;
}
}
bool Event::TryWait() {
std::scoped_lock lk(this->lock);
const bool success = this->signaled;
if (this->auto_clear) {
this->signaled = false;
}
return success;
}
bool Event::TimedWait(u64 ns) {
TimeoutHelper timeout_helper(ns);
std::scoped_lock lk(this->lock);
u64 cur_counter = this->counter;
while (!this->signaled) {
if (this->counter != cur_counter) {
break;
}
if (this->cv.TimedWait(&this->lock, timeout_helper.NsUntilTimeout()) == ConditionVariableStatus::TimedOut) {
return false;
}
}
if (this->auto_clear) {
this->signaled = false;
}
return true;
}
}

View file

@ -0,0 +1,111 @@
/*
* 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 "impl/os_waitable_object_list.hpp"
namespace ams::os {
Result InterruptEvent::Initialize(u32 interrupt_id, bool autoclear) {
AMS_ASSERT(!this->is_initialized);
this->auto_clear = autoclear;
const auto type = this->auto_clear ? svc::InterruptType_Edge : svc::InterruptType_Level;
R_TRY(svcCreateInterruptEvent(this->handle.GetPointer(), interrupt_id, type));
this->is_initialized = true;
return ResultSuccess();
}
void InterruptEvent::Finalize() {
AMS_ASSERT(this->is_initialized);
R_ASSERT(svcCloseHandle(this->handle.Move()));
this->auto_clear = true;
this->is_initialized = false;
}
InterruptEvent::InterruptEvent(u32 interrupt_id, bool autoclear) {
this->is_initialized = false;
R_ASSERT(this->Initialize(interrupt_id, autoclear));
}
void InterruptEvent::Reset() {
R_ASSERT(svcClearEvent(this->handle.Get()));
}
void InterruptEvent::Wait() {
AMS_ASSERT(this->is_initialized);
while (true) {
/* Continuously wait, until success. */
R_TRY_CATCH(svcWaitSynchronizationSingle(this->handle.Get(), U64_MAX)) {
R_CATCH(svc::ResultCancelled) { continue; }
} R_END_TRY_CATCH_WITH_ASSERT;
/* Clear, if we must. */
if (this->auto_clear) {
R_TRY_CATCH(svcResetSignal(this->handle.Get())) {
/* Some other thread might have caught this before we did. */
R_CATCH(svc::ResultInvalidState) { continue; }
} R_END_TRY_CATCH_WITH_ASSERT;
}
return;
}
}
bool InterruptEvent::TryWait() {
AMS_ASSERT(this->is_initialized);
if (this->auto_clear) {
/* Auto-clear. Just try to reset. */
return R_SUCCEEDED(svcResetSignal(this->handle.Get()));
} else {
/* Not auto-clear. */
while (true) {
/* Continuously wait, until success or timeout. */
R_TRY_CATCH(svcWaitSynchronizationSingle(this->handle.Get(), 0)) {
R_CATCH(svc::ResultTimedOut) { return false; }
R_CATCH(svc::ResultCancelled) { continue; }
} R_END_TRY_CATCH_WITH_ASSERT;
/* We succeeded, so we're signaled. */
return true;
}
}
}
bool InterruptEvent::TimedWait(u64 ns) {
AMS_ASSERT(this->is_initialized);
TimeoutHelper timeout_helper(ns);
while (true) {
/* Continuously wait, until success or timeout. */
R_TRY_CATCH(svcWaitSynchronizationSingle(this->handle.Get(), timeout_helper.NsUntilTimeout())) {
R_CATCH(svc::ResultTimedOut) { return false; }
R_CATCH(svc::ResultCancelled) { continue; }
} R_END_TRY_CATCH_WITH_ASSERT;
/* Clear, if we must. */
if (this->auto_clear) {
R_TRY_CATCH(svcResetSignal(this->handle.Get())) {
/* Some other thread might have caught this before we did. */
R_CATCH(svc::ResultInvalidState) { continue; }
} R_END_TRY_CATCH_WITH_ASSERT;
}
return true;
}
}
}

View file

@ -0,0 +1,247 @@
/*
* 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 "impl/os_waitable_object_list.hpp"
namespace ams::os {
MessageQueue::MessageQueue(std::unique_ptr<uintptr_t[]> buf, size_t c): buffer(std::move(buf)), capacity(c), count(0), offset(0) {
new (GetPointer(this->waitlist_not_empty)) impl::WaitableObjectList();
new (GetPointer(this->waitlist_not_full)) impl::WaitableObjectList();
}
MessageQueue::~MessageQueue() {
GetReference(this->waitlist_not_empty).~WaitableObjectList();
GetReference(this->waitlist_not_full).~WaitableObjectList();
}
void MessageQueue::SendInternal(uintptr_t data) {
/* Ensure we don't corrupt the queue, but this should never happen. */
AMS_ASSERT(this->count < this->capacity);
/* Write data to tail of queue. */
this->buffer[(this->count++ + this->offset) % this->capacity] = data;
}
void MessageQueue::SendNextInternal(uintptr_t data) {
/* Ensure we don't corrupt the queue, but this should never happen. */
AMS_ASSERT(this->count < this->capacity);
/* Write data to head of queue. */
this->offset = (this->offset + this->capacity - 1) % this->capacity;
this->buffer[this->offset] = data;
this->count++;
}
uintptr_t MessageQueue::ReceiveInternal() {
/* Ensure we don't corrupt the queue, but this should never happen. */
AMS_ASSERT(this->count > 0);
uintptr_t data = this->buffer[this->offset];
this->offset = (this->offset + 1) % this->capacity;
this->count--;
return data;
}
inline uintptr_t MessageQueue::PeekInternal() {
/* Ensure we don't corrupt the queue, but this should never happen. */
AMS_ASSERT(this->count > 0);
return this->buffer[this->offset];
}
void MessageQueue::Send(uintptr_t data) {
/* Acquire mutex, wait sendable. */
std::scoped_lock lock(this->queue_lock);
while (this->IsFull()) {
this->cv_not_full.Wait(&this->queue_lock);
}
/* Send, signal. */
this->SendInternal(data);
this->cv_not_empty.Broadcast();
GetReference(this->waitlist_not_empty).SignalAllThreads();
}
bool MessageQueue::TrySend(uintptr_t data) {
std::scoped_lock lock(this->queue_lock);
if (this->IsFull()) {
return false;
}
/* Send, signal. */
this->SendInternal(data);
this->cv_not_empty.Broadcast();
GetReference(this->waitlist_not_empty).SignalAllThreads();
return true;
}
bool MessageQueue::TimedSend(uintptr_t data, u64 timeout) {
std::scoped_lock lock(this->queue_lock);
TimeoutHelper timeout_helper(timeout);
while (this->IsFull()) {
if (timeout_helper.TimedOut()) {
return false;
}
this->cv_not_full.TimedWait(&this->queue_lock, timeout_helper.NsUntilTimeout());
}
/* Send, signal. */
this->SendInternal(data);
this->cv_not_empty.Broadcast();
GetReference(this->waitlist_not_empty).SignalAllThreads();
return true;
}
void MessageQueue::SendNext(uintptr_t data) {
/* Acquire mutex, wait sendable. */
std::scoped_lock lock(this->queue_lock);
while (this->IsFull()) {
this->cv_not_full.Wait(&this->queue_lock);
}
/* Send, signal. */
this->SendNextInternal(data);
this->cv_not_empty.Broadcast();
GetReference(this->waitlist_not_empty).SignalAllThreads();
}
bool MessageQueue::TrySendNext(uintptr_t data) {
std::scoped_lock lock(this->queue_lock);
if (this->IsFull()) {
return false;
}
/* Send, signal. */
this->SendNextInternal(data);
this->cv_not_empty.Broadcast();
GetReference(this->waitlist_not_empty).SignalAllThreads();
return true;
}
bool MessageQueue::TimedSendNext(uintptr_t data, u64 timeout) {
std::scoped_lock lock(this->queue_lock);
TimeoutHelper timeout_helper(timeout);
while (this->IsFull()) {
if (timeout_helper.TimedOut()) {
return false;
}
this->cv_not_full.TimedWait(&this->queue_lock, timeout_helper.NsUntilTimeout());
}
/* Send, signal. */
this->SendNextInternal(data);
this->cv_not_empty.Broadcast();
GetReference(this->waitlist_not_empty).SignalAllThreads();
return true;
}
void MessageQueue::Receive(uintptr_t *out) {
/* Acquire mutex, wait receivable. */
std::scoped_lock lock(this->queue_lock);
while (this->IsEmpty()) {
this->cv_not_empty.Wait(&this->queue_lock);
}
/* Receive, signal. */
*out = this->ReceiveInternal();
this->cv_not_full.Broadcast();
GetReference(this->waitlist_not_full).SignalAllThreads();
}
bool MessageQueue::TryReceive(uintptr_t *out) {
/* Acquire mutex, wait receivable. */
std::scoped_lock lock(this->queue_lock);
if (this->IsEmpty()) {
return false;
}
/* Receive, signal. */
*out = this->ReceiveInternal();
this->cv_not_full.Broadcast();
GetReference(this->waitlist_not_full).SignalAllThreads();
return true;
}
bool MessageQueue::TimedReceive(uintptr_t *out, u64 timeout) {
std::scoped_lock lock(this->queue_lock);
TimeoutHelper timeout_helper(timeout);
while (this->IsEmpty()) {
if (timeout_helper.TimedOut()) {
return false;
}
this->cv_not_empty.TimedWait(&this->queue_lock, timeout_helper.NsUntilTimeout());
}
/* Receive, signal. */
*out = this->ReceiveInternal();
this->cv_not_full.Broadcast();
GetReference(this->waitlist_not_full).SignalAllThreads();
return true;
}
void MessageQueue::Peek(uintptr_t *out) {
/* Acquire mutex, wait receivable. */
std::scoped_lock lock(this->queue_lock);
while (this->IsEmpty()) {
this->cv_not_empty.Wait(&this->queue_lock);
}
/* Peek. */
*out = this->PeekInternal();
}
bool MessageQueue::TryPeek(uintptr_t *out) {
/* Acquire mutex, wait receivable. */
std::scoped_lock lock(this->queue_lock);
if (this->IsEmpty()) {
return false;
}
/* Peek. */
*out = this->PeekInternal();
return true;
}
bool MessageQueue::TimedPeek(uintptr_t *out, u64 timeout) {
std::scoped_lock lock(this->queue_lock);
TimeoutHelper timeout_helper(timeout);
while (this->IsEmpty()) {
if (timeout_helper.TimedOut()) {
return false;
}
this->cv_not_empty.TimedWait(&this->queue_lock, timeout_helper.NsUntilTimeout());
}
/* Peek. */
*out = this->PeekInternal();
return true;
}
}

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 <stratosphere.hpp>
namespace ams {
namespace {
constexpr inline ::Handle GetCurrentProcessHandleImpl() {
return CUR_PROCESS_HANDLE;
}
}
namespace os {
::Handle __attribute__((const)) GetCurrentProcessHandle() {
return GetCurrentProcessHandleImpl();
}
}
namespace dd {
::Handle __attribute__((const)) GetCurrentProcessHandle() {
return GetCurrentProcessHandleImpl();
}
}
}

View file

@ -0,0 +1,85 @@
/*
* 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 "impl/os_waitable_object_list.hpp"
namespace ams::os {
Semaphore::Semaphore(int c, int mc) : count(c), max_count(mc) {
new (GetPointer(this->waitlist)) impl::WaitableObjectList();
}
Semaphore::~Semaphore() {
GetReference(this->waitlist).~WaitableObjectList();
}
void Semaphore::Acquire() {
std::scoped_lock lk(this->mutex);
while (this->count == 0) {
this->condvar.Wait(&this->mutex);
}
this->count--;
}
bool Semaphore::TryAcquire() {
std::scoped_lock lk(this->mutex);
if (this->count == 0) {
return false;
}
this->count--;
return true;
}
bool Semaphore::TimedAcquire(u64 timeout) {
std::scoped_lock lk(this->mutex);
TimeoutHelper timeout_helper(timeout);
while (this->count == 0) {
if (timeout_helper.TimedOut()) {
return false;
}
this->condvar.TimedWait(&this->mutex, timeout_helper.NsUntilTimeout());
}
this->count--;
return true;
}
void Semaphore::Release() {
std::scoped_lock lk(this->mutex);
AMS_ASSERT(this->count + 1 <= this->max_count);
this->count++;
this->condvar.Signal();
GetReference(this->waitlist).SignalAllThreads();
}
void Semaphore::Release(int count) {
std::scoped_lock lk(this->mutex);
AMS_ASSERT(this->count + count <= this->max_count);
this->count += count;
this->condvar.Broadcast();
GetReference(this->waitlist).SignalAllThreads();
}
}

View file

@ -0,0 +1,188 @@
/*
* 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 "impl/os_waitable_holder_impl.hpp"
namespace ams::os {
SystemEvent::SystemEvent(bool inter_process, bool autoclear) : state(SystemEventState::Uninitialized) {
if (inter_process) {
R_ASSERT(this->InitializeAsInterProcessEvent(autoclear));
} else {
R_ASSERT(this->InitializeAsEvent(autoclear));
}
}
SystemEvent::SystemEvent(Handle read_handle, bool manage_read_handle, Handle write_handle, bool manage_write_handle, bool autoclear) : state(SystemEventState::Uninitialized) {
this->AttachHandles(read_handle, manage_read_handle, write_handle, manage_write_handle, autoclear);
}
SystemEvent::~SystemEvent() {
this->Finalize();
}
Event &SystemEvent::GetEvent() {
AMS_ASSERT(this->state == SystemEventState::Event);
return GetReference(this->storage_for_event);
}
const Event &SystemEvent::GetEvent() const {
AMS_ASSERT(this->state == SystemEventState::Event);
return GetReference(this->storage_for_event);
}
impl::InterProcessEvent &SystemEvent::GetInterProcessEvent() {
AMS_ASSERT(this->state == SystemEventState::InterProcessEvent);
return GetReference(this->storage_for_inter_process_event);
}
const impl::InterProcessEvent &SystemEvent::GetInterProcessEvent() const {
AMS_ASSERT(this->state == SystemEventState::InterProcessEvent);
return GetReference(this->storage_for_inter_process_event);
}
Result SystemEvent::InitializeAsEvent(bool autoclear) {
AMS_ASSERT(this->state == SystemEventState::Uninitialized);
new (GetPointer(this->storage_for_event)) Event(autoclear);
this->state = SystemEventState::Event;
return ResultSuccess();
}
Result SystemEvent::InitializeAsInterProcessEvent(bool autoclear) {
AMS_ASSERT(this->state == SystemEventState::Uninitialized);
new (GetPointer(this->storage_for_inter_process_event)) impl::InterProcessEvent();
this->state = SystemEventState::InterProcessEvent;
/* Ensure we end up in a correct state if initialization fails. */
{
auto guard = SCOPE_GUARD { this->Finalize(); };
R_TRY(this->GetInterProcessEvent().Initialize(autoclear));
guard.Cancel();
}
return ResultSuccess();
}
void SystemEvent::AttachHandles(Handle read_handle, bool manage_read_handle, Handle write_handle, bool manage_write_handle, bool autoclear) {
AMS_ASSERT(this->state == SystemEventState::Uninitialized);
new (GetPointer(this->storage_for_inter_process_event)) impl::InterProcessEvent();
this->state = SystemEventState::InterProcessEvent;
this->GetInterProcessEvent().Initialize(read_handle, manage_read_handle, write_handle, manage_write_handle, autoclear);
}
void SystemEvent::AttachReadableHandle(Handle read_handle, bool manage_read_handle, bool autoclear) {
this->AttachHandles(read_handle, manage_read_handle, INVALID_HANDLE, false, autoclear);
}
void SystemEvent::AttachWritableHandle(Handle write_handle, bool manage_write_handle, bool autoclear) {
this->AttachHandles(INVALID_HANDLE, false, write_handle, manage_write_handle, autoclear);
}
Handle SystemEvent::DetachReadableHandle() {
AMS_ASSERT(this->state == SystemEventState::InterProcessEvent);
return this->GetInterProcessEvent().DetachReadableHandle();
}
Handle SystemEvent::DetachWritableHandle() {
AMS_ASSERT(this->state == SystemEventState::InterProcessEvent);
return this->GetInterProcessEvent().DetachWritableHandle();
}
Handle SystemEvent::GetReadableHandle() const {
AMS_ASSERT(this->state == SystemEventState::InterProcessEvent);
return this->GetInterProcessEvent().GetReadableHandle();
}
Handle SystemEvent::GetWritableHandle() const {
AMS_ASSERT(this->state == SystemEventState::InterProcessEvent);
return this->GetInterProcessEvent().GetWritableHandle();
}
void SystemEvent::Finalize() {
switch (this->state) {
case SystemEventState::Uninitialized:
break;
case SystemEventState::Event:
this->GetEvent().~Event();
break;
case SystemEventState::InterProcessEvent:
this->GetInterProcessEvent().~InterProcessEvent();
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
this->state = SystemEventState::Uninitialized;
}
void SystemEvent::Signal() {
switch (this->state) {
case SystemEventState::Event:
this->GetEvent().Signal();
break;
case SystemEventState::InterProcessEvent:
this->GetInterProcessEvent().Signal();
break;
case SystemEventState::Uninitialized:
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
void SystemEvent::Reset() {
switch (this->state) {
case SystemEventState::Event:
this->GetEvent().Reset();
break;
case SystemEventState::InterProcessEvent:
this->GetInterProcessEvent().Reset();
break;
case SystemEventState::Uninitialized:
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
void SystemEvent::Wait() {
switch (this->state) {
case SystemEventState::Event:
this->GetEvent().Wait();
break;
case SystemEventState::InterProcessEvent:
this->GetInterProcessEvent().Wait();
break;
case SystemEventState::Uninitialized:
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
bool SystemEvent::TryWait() {
switch (this->state) {
case SystemEventState::Event:
return this->GetEvent().TryWait();
case SystemEventState::InterProcessEvent:
return this->GetInterProcessEvent().TryWait();
case SystemEventState::Uninitialized:
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
bool SystemEvent::TimedWait(u64 ns) {
switch (this->state) {
case SystemEventState::Event:
return this->GetEvent().TimedWait(ns);
case SystemEventState::InterProcessEvent:
return this->GetInterProcessEvent().TimedWait(ns);
case SystemEventState::Uninitialized:
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
}

View file

@ -0,0 +1,116 @@
/*
* 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 "impl/os_waitable_holder_impl.hpp"
#include "impl/os_waitable_manager_impl.hpp"
namespace ams::os {
WaitableHolder::WaitableHolder(Handle handle) {
/* Don't allow invalid handles. */
AMS_ASSERT(handle != INVALID_HANDLE);
/* Initialize appropriate holder. */
new (GetPointer(this->impl_storage)) impl::WaitableHolderOfHandle(handle);
/* Set user-data. */
this->user_data = 0;
}
WaitableHolder::WaitableHolder(Event *event) {
/* Initialize appropriate holder. */
new (GetPointer(this->impl_storage)) impl::WaitableHolderOfEvent(event);
/* Set user-data. */
this->user_data = 0;
}
WaitableHolder::WaitableHolder(SystemEvent *event) {
/* Initialize appropriate holder. */
switch (event->GetState()) {
case SystemEventState::Event:
new (GetPointer(this->impl_storage)) impl::WaitableHolderOfEvent(&event->GetEvent());
break;
case SystemEventState::InterProcessEvent:
new (GetPointer(this->impl_storage)) impl::WaitableHolderOfInterProcessEvent(&event->GetInterProcessEvent());
break;
case SystemEventState::Uninitialized:
AMS_UNREACHABLE_DEFAULT_CASE();
}
/* Set user-data. */
this->user_data = 0;
}
WaitableHolder::WaitableHolder(InterruptEvent *event) {
/* Initialize appropriate holder. */
new (GetPointer(this->impl_storage)) impl::WaitableHolderOfInterruptEvent(event);
/* Set user-data. */
this->user_data = 0;
}
WaitableHolder::WaitableHolder(Thread *thread) {
/* Initialize appropriate holder. */
new (GetPointer(this->impl_storage)) impl::WaitableHolderOfThread(thread);
/* Set user-data. */
this->user_data = 0;
}
WaitableHolder::WaitableHolder(Semaphore *semaphore) {
/* Initialize appropriate holder. */
new (GetPointer(this->impl_storage)) impl::WaitableHolderOfSemaphore(semaphore);
/* Set user-data. */
this->user_data = 0;
}
WaitableHolder::WaitableHolder(MessageQueue *message_queue, MessageQueueWaitKind wait_kind) {
/* Initialize appropriate holder. */
switch (wait_kind) {
case MessageQueueWaitKind::ForNotFull:
new (GetPointer(this->impl_storage)) impl::WaitableHolderOfMessageQueueForNotFull(message_queue);
break;
case MessageQueueWaitKind::ForNotEmpty:
new (GetPointer(this->impl_storage)) impl::WaitableHolderOfMessageQueueForNotEmpty(message_queue);
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
/* Set user-data. */
this->user_data = 0;
}
WaitableHolder::~WaitableHolder() {
auto holder_base = reinterpret_cast<impl::WaitableHolderBase *>(GetPointer(this->impl_storage));
/* Don't allow destruction of a linked waitable holder. */
AMS_ASSERT(!holder_base->IsLinkedToManager());
holder_base->~WaitableHolderBase();
}
void WaitableHolder::UnlinkFromWaitableManager() {
auto holder_base = reinterpret_cast<impl::WaitableHolderBase *>(GetPointer(this->impl_storage));
/* Don't allow unlinking of an unlinked holder. */
AMS_ASSERT(holder_base->IsLinkedToManager());
holder_base->GetManager()->UnlinkWaitableHolder(*holder_base);
holder_base->SetManager(nullptr);
}
}

View file

@ -0,0 +1,88 @@
/*
* 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 "impl/os_waitable_holder_impl.hpp"
#include "impl/os_waitable_manager_impl.hpp"
namespace ams::os {
WaitableManager::WaitableManager() {
/* Initialize storage. */
new (GetPointer(this->impl_storage)) impl::WaitableManagerImpl();
}
WaitableManager::~WaitableManager() {
auto &impl = GetReference(this->impl_storage);
/* Don't allow destruction of a non-empty waitable holder. */
AMS_ASSERT(impl.IsEmpty());
impl.~WaitableManagerImpl();
}
/* Wait. */
WaitableHolder *WaitableManager::WaitAny() {
auto &impl = GetReference(this->impl_storage);
/* Don't allow waiting on empty list. */
AMS_ASSERT(!impl.IsEmpty());
return reinterpret_cast<WaitableHolder *>(impl.WaitAny());
}
WaitableHolder *WaitableManager::TryWaitAny() {
auto &impl = GetReference(this->impl_storage);
/* Don't allow waiting on empty list. */
AMS_ASSERT(!impl.IsEmpty());
return reinterpret_cast<WaitableHolder *>(impl.TryWaitAny());
}
WaitableHolder *WaitableManager::TimedWaitAny(u64 timeout) {
auto &impl = GetReference(this->impl_storage);
/* Don't allow waiting on empty list. */
AMS_ASSERT(!impl.IsEmpty());
return reinterpret_cast<WaitableHolder *>(impl.TimedWaitAny(timeout));
}
/* Link. */
void WaitableManager::LinkWaitableHolder(WaitableHolder *holder) {
auto &impl = GetReference(this->impl_storage);
auto holder_base = reinterpret_cast<impl::WaitableHolderBase *>(GetPointer(holder->impl_storage));
/* Don't allow double-linking a holder. */
AMS_ASSERT(!holder_base->IsLinkedToManager());
impl.LinkWaitableHolder(*holder_base);
holder_base->SetManager(&impl);
}
void WaitableManager::UnlinkAll() {
auto &impl = GetReference(this->impl_storage);
impl.UnlinkAll();
}
void WaitableManager::MoveAllFrom(WaitableManager *other) {
auto &dst_impl = GetReference(this->impl_storage);
auto &src_impl = GetReference(other->impl_storage);
dst_impl.MoveAllFrom(src_impl);
}
}

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 <dirent.h>
#include <stratosphere.hpp>
/* IPS Patching adapted from Luma3DS (https://github.com/AuroraWright/Luma3DS/blob/master/sysmodules/loader/source/patcher.c) */
namespace ams::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. */
AMS_ASSERT(offset <= protected_size);
u8 buffer[sizeof(Ips32TailMagic)];
while (true) {
AMS_ASSERT(fread(buffer, is_ips32 ? sizeof(Ips32TailMagic) : sizeof(IpsTailMagic), 1, f_ips) == 1);
if (IsIpsTail(is_ips32, buffer)) {
break;
}
/* Offset of patch. */
u32 patch_offset = GetIpsPatchOffset(is_ips32, buffer);
/* Size of patch. */
AMS_ASSERT(fread(buffer, 2, 1, f_ips) == 1);
u32 patch_size = GetIpsPatchSize(is_ips32, buffer);
/* Check for RLE encoding. */
if (patch_size == 0) {
/* Size of RLE. */
AMS_ASSERT(fread(buffer, 2, 1, f_ips) == 1);
u32 rle_size = (buffer[0] << 8) | (buffer[1]);
/* Value for RLE. */
AMS_ASSERT(fread(buffer, 1, 1, f_ips) == 1);
/* 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;
}
AMS_ASSERT(fread(mapped_module + patch_offset, read_size, 1, f_ips) == 1);
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,92 @@
/*
* 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 program_id) {
return serviceDispatchInOut(pminfoGetServiceSession(), 65000, program_id, *out_pid);
}
Result pminfoAtmosphereHasLaunchedProgram(bool *out, u64 program_id) {
u8 tmp;
Result rc = serviceDispatchInOut(pminfoGetServiceSession(), 65001, program_id, tmp);
if (R_SUCCEEDED(rc) && out) *out = tmp & 1;
return rc;
}
Result pminfoAtmosphereGetProcessInfo(NcmProgramLocation *loc_out, CfgOverrideStatus *status_out, u64 pid) {
struct {
NcmProgramLocation loc;
CfgOverrideStatus status;
} out;
Result rc = serviceDispatchInOut(pminfoGetServiceSession(), 65002, pid, out);
if (R_SUCCEEDED(rc)) {
if (loc_out) *loc_out = out.loc;
if (status_out) *status_out = out.status;
}
return rc;
}
Result pmdmntAtmosphereGetProcessInfo(Handle* handle_out, NcmProgramLocation *loc_out, CfgOverrideStatus *status_out, u64 pid) {
Handle tmp_handle;
struct {
NcmProgramLocation loc;
CfgOverrideStatus status;
} out;
Result rc = serviceDispatchInOut(pmdmntGetServiceSession(), 65000, pid, out,
.out_handle_attrs = { SfOutHandleAttr_HipcCopy },
.out_handles = &tmp_handle,
);
if (R_SUCCEEDED(rc)) {
if (handle_out) {
*handle_out = tmp_handle;
} else {
svcCloseHandle(tmp_handle);
}
if (loc_out) *loc_out = out.loc;
if (status_out) *status_out = out.status;
}
return rc;
}
Result pmdmntAtmosphereGetCurrentLimitInfo(u64 *out_cur, u64 *out_lim, u32 group, u32 resource) {
const struct {
u32 group;
u32 resource;
} in = { group, resource };
struct {
u64 cur;
u64 lim;
} out;
Result rc = serviceDispatchInOut(pmdmntGetServiceSession(), 65001, in, out);
if (R_SUCCEEDED(rc)) {
if (out_cur) *out_cur = out.cur;
if (out_lim) *out_lim = out.lim;
}
return rc;
}

View file

@ -0,0 +1,28 @@
/**
* @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
typedef struct {
u64 keys_held;
u64 flags;
} CfgOverrideStatus;
Result pminfoAtmosphereGetProcessId(u64 *out_pid, u64 program_id);
Result pminfoAtmosphereHasLaunchedProgram(bool *out, u64 program_id);
Result pminfoAtmosphereGetProcessInfo(NcmProgramLocation *loc_out, CfgOverrideStatus *status_out, u64 pid);
Result pmdmntAtmosphereGetProcessInfo(Handle *out, NcmProgramLocation *loc_out, CfgOverrideStatus *status_out, u64 pid);
Result pmdmntAtmosphereGetCurrentLimitInfo(u64 *out_cur, u64 *out_lim, u32 group, u32 resource);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,32 @@
/*
* 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>
namespace ams::pm::bm {
/* Boot Mode API. */
/* Both functions should be weakly linked, so that they can be overridden by ams::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,56 @@
/*
* 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 "pm_ams.h"
namespace ams::pm::dmnt {
/* Debug Monitor API. */
Result StartProcess(os::ProcessId process_id) {
return pmdmntStartProcess(static_cast<u64>(process_id));
}
Result GetProcessId(os::ProcessId *out_process_id, const ncm::ProgramId program_id) {
return pmdmntGetProcessId(reinterpret_cast<u64 *>(out_process_id), static_cast<u64>(program_id));
}
Result GetApplicationProcessId(os::ProcessId *out_process_id) {
return pmdmntGetApplicationProcessId(reinterpret_cast<u64 *>(out_process_id));
}
Result HookToCreateApplicationProcess(Handle *out_handle) {
Event evt;
R_TRY(pmdmntHookToCreateApplicationProcess(&evt));
*out_handle = evt.revent;
return ResultSuccess();
}
Result AtmosphereGetProcessInfo(Handle *out_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id) {
*out_handle = INVALID_HANDLE;
*out_loc = {};
*out_status = {};
static_assert(sizeof(*out_status) == sizeof(CfgOverrideStatus));
return pmdmntAtmosphereGetProcessInfo(out_handle, reinterpret_cast<NcmProgramLocation *>(out_loc), reinterpret_cast<CfgOverrideStatus *>(out_status), static_cast<u64>(process_id));
}
Result AtmosphereGetCurrentLimitInfo(u64 *out_current_value, u64 *out_limit_value, ResourceLimitGroup group, LimitableResource resource) {
*out_current_value = 0;
*out_limit_value = 0;
return pmdmntAtmosphereGetCurrentLimitInfo(out_current_value, out_limit_value, group, resource);
}
}

View file

@ -0,0 +1,93 @@
/*
* 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 "pm_ams.h"
namespace ams::pm::info {
namespace {
/* Global lock. */
os::Mutex g_info_lock;
/* TODO: Less memory-intensive storage? */
std::set<u64> g_cached_launched_programs;
}
/* Information API. */
Result GetProgramId(ncm::ProgramId *out_program_id, os::ProcessId process_id) {
std::scoped_lock lk(g_info_lock);
return pminfoGetProgramId(reinterpret_cast<u64 *>(out_program_id), static_cast<u64>(process_id));
}
Result GetProcessId(os::ProcessId *out_process_id, ncm::ProgramId program_id) {
std::scoped_lock lk(g_info_lock);
return pminfoAtmosphereGetProcessId(reinterpret_cast<u64 *>(out_process_id), static_cast<u64>(program_id));
}
Result GetProcessInfo(ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id) {
std::scoped_lock lk(g_info_lock);
*out_loc = {};
*out_status = {};
static_assert(sizeof(*out_status) == sizeof(CfgOverrideStatus));
return pminfoAtmosphereGetProcessInfo(reinterpret_cast<NcmProgramLocation *>(out_loc), reinterpret_cast<CfgOverrideStatus *>(out_status), static_cast<u64>(process_id));
}
Result WEAK HasLaunchedProgram(bool *out, ncm::ProgramId program_id) {
std::scoped_lock lk(g_info_lock);
if (g_cached_launched_programs.find(static_cast<u64>(program_id)) != g_cached_launched_programs.end()) {
*out = true;
return ResultSuccess();
}
bool has_launched = false;
R_TRY(pminfoAtmosphereHasLaunchedProgram(&has_launched, static_cast<u64>(program_id)));
if (has_launched) {
g_cached_launched_programs.insert(static_cast<u64>(program_id));
}
*out = has_launched;
return ResultSuccess();
}
bool HasLaunchedProgram(ncm::ProgramId program_id) {
bool has_launched = false;
R_ASSERT(HasLaunchedProgram(&has_launched, program_id));
return has_launched;
}
Result IsHblProcessId(bool *out, os::ProcessId process_id) {
ncm::ProgramLocation loc;
cfg::OverrideStatus override_status;
R_TRY(GetProcessInfo(&loc, &override_status, process_id));
*out = override_status.IsHbl();
return ResultSuccess();
}
Result IsHblProgramId(bool *out, ncm::ProgramId program_id) {
os::ProcessId process_id;
R_TRY(GetProcessId(&process_id, program_id));
return IsHblProcessId(out, process_id);
}
}

View file

@ -0,0 +1,27 @@
/*
* 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>
namespace ams::pm::shell {
/* Shell API. */
Result WEAK LaunchProgram(os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 launch_flags) {
static_assert(sizeof(ncm::ProgramLocation) == sizeof(NcmProgramLocation));
static_assert(alignof(ncm::ProgramLocation) == alignof(NcmProgramLocation));
return pmshellLaunchProgram(launch_flags, reinterpret_cast<const NcmProgramLocation *>(&loc), reinterpret_cast<u64 *>(out_process_id));
}
}

View file

@ -0,0 +1,37 @@
/*
* 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>
namespace ams::result {
extern bool CallFatalOnResultAssertion;
}
namespace ams::result::impl {
NORETURN WEAK void OnResultAssertion(Result result) {
/* Assert that we should call fatal on result assertion. */
/* If we shouldn't fatal, this will std::abort(); */
/* If we should, we'll continue onwards. */
AMS_ASSERT((ams::result::CallFatalOnResultAssertion));
/* TODO: ams::fatal:: */
fatalThrow(result.GetValue());
while (true) { /* ... */ }
}
}

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 <random>
#include <stratosphere.hpp>
namespace ams::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,64 @@
#pragma once
#include <switch/types.h>
#include <switch/result.h>
#include <switch/kernel/mutex.h>
#include <switch/sf/service.h>
#include <switch/services/sm.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct ServiceGuard {
Mutex mutex;
u32 refCount;
} ServiceGuard;
NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g)
{
mutexLock(&g->mutex);
return (g->refCount++) == 0;
}
NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void))
{
if (R_FAILED(rc)) {
cleanupFunc();
--g->refCount;
}
mutexUnlock(&g->mutex);
return rc;
}
NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
{
mutexLock(&g->mutex);
if (g->refCount && (--g->refCount) == 0)
cleanupFunc();
mutexUnlock(&g->mutex);
}
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
\
static ServiceGuard g_##name##Guard; \
NX_INLINE Result _##name##Initialize _paramdecl; \
static void _##name##Cleanup(void); \
\
Result name##Initialize _paramdecl \
{ \
Result rc = 0; \
if (serviceGuardBeginInit(&g_##name##Guard)) \
rc = _##name##Initialize _parampass; \
return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \
} \
\
void name##Exit(void) \
{ \
serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \
}
#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ())
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,35 @@
/*
* 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>
namespace ams::settings::fwdbg {
/* TODO: Implement when libnx wrapper is added. */
bool IsDebugModeEnabled();
size_t WEAK GetSettingsItemValueSize(const char *name, const char *key) {
u64 size = 0;
R_ASSERT(setsysGetSettingsItemValueSize(name, key, &size));
return size;
}
size_t WEAK GetSettingsItemValue(void *dst, size_t dst_size, const char *name, const char *key) {
u64 size = 0;
R_ASSERT(setsysGetSettingsItemValue(name, key, dst, dst_size, &size));
return size;
}
}

View file

@ -0,0 +1,158 @@
/*
* 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>
namespace ams::sf::cmif {
ServerDomainManager::Domain::~Domain() {
while (!this->entries.empty()) {
Entry *entry = &this->entries.front();
{
std::scoped_lock lk(this->manager->entry_owner_lock);
AMS_ASSERT(entry->owner == this);
entry->owner = nullptr;
}
entry->object.Reset();
this->entries.pop_front();
this->manager->entry_manager.FreeEntry(entry);
}
}
void ServerDomainManager::Domain::DestroySelf() {
ServerDomainManager *manager = this->manager;
this->~Domain();
manager->FreeDomain(this);
}
Result ServerDomainManager::Domain::ReserveIds(DomainObjectId *out_ids, size_t count) {
for (size_t i = 0; i < count; i++) {
Entry *entry = this->manager->entry_manager.AllocateEntry();
R_UNLESS(entry != nullptr, sf::cmif::ResultOutOfDomainEntries());
AMS_ASSERT(entry->owner == nullptr);
out_ids[i] = this->manager->entry_manager.GetId(entry);
}
return ResultSuccess();
}
void ServerDomainManager::Domain::ReserveSpecificIds(const DomainObjectId *ids, size_t count) {
this->manager->entry_manager.AllocateSpecificEntries(ids, count);
}
void ServerDomainManager::Domain::UnreserveIds(const DomainObjectId *ids, size_t count) {
for (size_t i = 0; i < count; i++) {
Entry *entry = this->manager->entry_manager.GetEntry(ids[i]);
AMS_ASSERT(entry != nullptr);
AMS_ASSERT(entry->owner == nullptr);
this->manager->entry_manager.FreeEntry(entry);
}
}
void ServerDomainManager::Domain::RegisterObject(DomainObjectId id, ServiceObjectHolder &&obj) {
Entry *entry = this->manager->entry_manager.GetEntry(id);
AMS_ASSERT(entry != nullptr);
{
std::scoped_lock lk(this->manager->entry_owner_lock);
AMS_ASSERT(entry->owner == nullptr);
entry->owner = this;
this->entries.push_back(*entry);
}
entry->object = std::move(obj);
}
ServiceObjectHolder ServerDomainManager::Domain::UnregisterObject(DomainObjectId id) {
ServiceObjectHolder obj;
Entry *entry = this->manager->entry_manager.GetEntry(id);
if (entry == nullptr) {
return ServiceObjectHolder();
}
{
std::scoped_lock lk(this->manager->entry_owner_lock);
if (entry->owner != this) {
return ServiceObjectHolder();
}
entry->owner = nullptr;
obj = std::move(entry->object);
this->entries.erase(this->entries.iterator_to(*entry));
}
this->manager->entry_manager.FreeEntry(entry);
return obj;
}
ServiceObjectHolder ServerDomainManager::Domain::GetObject(DomainObjectId id) {
Entry *entry = this->manager->entry_manager.GetEntry(id);
if (entry == nullptr) {
return ServiceObjectHolder();
}
{
std::scoped_lock lk(this->manager->entry_owner_lock);
if (entry->owner != this) {
return ServiceObjectHolder();
}
}
return entry->object.Clone();
}
ServerDomainManager::EntryManager::EntryManager(DomainEntryStorage *entry_storage, size_t entry_count) {
this->entries = reinterpret_cast<Entry *>(entry_storage);
this->num_entries = entry_count;
for (size_t i = 0; i < this->num_entries; i++) {
Entry *entry = new (this->entries + i) Entry();
this->free_list.push_back(*entry);
}
}
ServerDomainManager::EntryManager::~EntryManager() {
for (size_t i = 0; i < this->num_entries; i++) {
this->entries[i].~Entry();
}
}
ServerDomainManager::Entry *ServerDomainManager::EntryManager::AllocateEntry() {
std::scoped_lock lk(this->lock);
if (this->free_list.empty()) {
return nullptr;
}
Entry *e = &this->free_list.front();
this->free_list.pop_front();
return e;
}
void ServerDomainManager::EntryManager::FreeEntry(Entry *entry) {
std::scoped_lock lk(this->lock);
AMS_ASSERT(entry->owner == nullptr);
AMS_ASSERT(!entry->object);
this->free_list.push_front(*entry);
}
void ServerDomainManager::EntryManager::AllocateSpecificEntries(const DomainObjectId *ids, size_t count) {
std::scoped_lock lk(this->lock);
/* Allocate new IDs. */
for (size_t i = 0; i < count; i++) {
const auto id = ids[i];
Entry *entry = this->GetEntry(id);
if (id != InvalidDomainObjectId) {
AMS_ASSERT(entry != nullptr);
AMS_ASSERT(entry->owner == nullptr);
this->free_list.erase(this->free_list.iterator_to(*entry));
}
}
}
}

View file

@ -0,0 +1,220 @@
/*
* 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>
namespace ams::sf::cmif {
Result DomainServiceObjectDispatchTable::ProcessMessage(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const {
return this->ProcessMessageImpl(ctx, static_cast<DomainServiceObject *>(ctx.srv_obj)->GetServerDomain(), in_raw_data);
}
Result DomainServiceObjectDispatchTable::ProcessMessageForMitm(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const {
return this->ProcessMessageForMitmImpl(ctx, static_cast<DomainServiceObject *>(ctx.srv_obj)->GetServerDomain(), in_raw_data);
}
Result DomainServiceObjectDispatchTable::ProcessMessageImpl(ServiceDispatchContext &ctx, ServerDomainBase *domain, const cmif::PointerAndSize &in_raw_data) const {
const CmifDomainInHeader *in_header = reinterpret_cast<const CmifDomainInHeader *>(in_raw_data.GetPointer());
R_UNLESS(in_raw_data.GetSize() >= sizeof(*in_header), sf::cmif::ResultInvalidHeaderSize());
const cmif::PointerAndSize in_domain_raw_data = cmif::PointerAndSize(in_raw_data.GetAddress() + sizeof(*in_header), in_raw_data.GetSize() - sizeof(*in_header));
const DomainObjectId target_object_id = DomainObjectId{in_header->object_id};
switch (in_header->type) {
case CmifDomainRequestType_SendMessage:
{
auto target_object = domain->GetObject(target_object_id);
R_UNLESS(static_cast<bool>(target_object), sf::cmif::ResultTargetNotFound());
R_UNLESS(in_header->data_size + in_header->num_in_objects * sizeof(DomainObjectId) <= in_domain_raw_data.GetSize(), sf::cmif::ResultInvalidHeaderSize());
const cmif::PointerAndSize in_message_raw_data = cmif::PointerAndSize(in_domain_raw_data.GetAddress(), in_header->data_size);
DomainObjectId in_object_ids[8];
R_UNLESS(in_header->num_in_objects <= util::size(in_object_ids), sf::cmif::ResultInvalidNumInObjects());
std::memcpy(in_object_ids, reinterpret_cast<DomainObjectId *>(in_message_raw_data.GetAddress() + in_message_raw_data.GetSize()), sizeof(DomainObjectId) * in_header->num_in_objects);
DomainServiceObjectProcessor domain_processor(domain, in_object_ids, in_header->num_in_objects);
if (ctx.processor == nullptr) {
ctx.processor = &domain_processor;
} else {
ctx.processor->SetImplementationProcessor(&domain_processor);
}
ctx.srv_obj = target_object.GetServiceObjectUnsafe();
return target_object.ProcessMessage(ctx, in_message_raw_data);
}
case CmifDomainRequestType_Close:
/* TODO: N doesn't error check here. Should we? */
domain->UnregisterObject(target_object_id);
return ResultSuccess();
default:
return sf::cmif::ResultInvalidInHeader();
}
}
Result DomainServiceObjectDispatchTable::ProcessMessageForMitmImpl(ServiceDispatchContext &ctx, ServerDomainBase *domain, const cmif::PointerAndSize &in_raw_data) const {
const CmifDomainInHeader *in_header = reinterpret_cast<const CmifDomainInHeader *>(in_raw_data.GetPointer());
R_UNLESS(in_raw_data.GetSize() >= sizeof(*in_header), sf::cmif::ResultInvalidHeaderSize());
const cmif::PointerAndSize in_domain_raw_data = cmif::PointerAndSize(in_raw_data.GetAddress() + sizeof(*in_header), in_raw_data.GetSize() - sizeof(*in_header));
const DomainObjectId target_object_id = DomainObjectId{in_header->object_id};
switch (in_header->type) {
case CmifDomainRequestType_SendMessage:
{
auto target_object = domain->GetObject(target_object_id);
/* Mitm. If we don't have a target object, we should forward to let the server handle. */
if (!target_object) {
return ctx.session->ForwardRequest(ctx);
}
R_UNLESS(in_header->data_size + in_header->num_in_objects * sizeof(DomainObjectId) <= in_domain_raw_data.GetSize(), sf::cmif::ResultInvalidHeaderSize());
const cmif::PointerAndSize in_message_raw_data = cmif::PointerAndSize(in_domain_raw_data.GetAddress(), in_header->data_size);
DomainObjectId in_object_ids[8];
R_UNLESS(in_header->num_in_objects <= util::size(in_object_ids), sf::cmif::ResultInvalidNumInObjects());
std::memcpy(in_object_ids, reinterpret_cast<DomainObjectId *>(in_message_raw_data.GetAddress() + in_message_raw_data.GetSize()), sizeof(DomainObjectId) * in_header->num_in_objects);
DomainServiceObjectProcessor domain_processor(domain, in_object_ids, in_header->num_in_objects);
if (ctx.processor == nullptr) {
ctx.processor = &domain_processor;
} else {
ctx.processor->SetImplementationProcessor(&domain_processor);
}
ctx.srv_obj = target_object.GetServiceObjectUnsafe();
return target_object.ProcessMessage(ctx, in_message_raw_data);
}
case CmifDomainRequestType_Close:
{
auto target_object = domain->GetObject(target_object_id);
/* If the object is not in the domain, tell the server to close it. */
if (!target_object) {
return ctx.session->ForwardRequest(ctx);
}
/* If the object is in the domain, close our copy of it. Mitm objects are required to close their associated domain id, so this shouldn't cause desynch. */
domain->UnregisterObject(target_object_id);
return ResultSuccess();
}
default:
return sf::cmif::ResultInvalidInHeader();
}
}
Result DomainServiceObjectProcessor::PrepareForProcess(const ServiceDispatchContext &ctx, const ServerMessageRuntimeMetadata runtime_metadata) const {
/* Validate in object count. */
R_UNLESS(this->impl_metadata.GetInObjectCount() == this->GetInObjectCount(), sf::cmif::ResultInvalidNumInObjects());
/* Nintendo reserves domain object IDs here. We do this later, to support mitm semantics. */
/* Pass onwards. */
return this->impl_processor->PrepareForProcess(ctx, runtime_metadata);
}
Result DomainServiceObjectProcessor::GetInObjects(ServiceObjectHolder *in_objects) const {
for (size_t i = 0; i < this->GetInObjectCount(); i++) {
in_objects[i] = this->domain->GetObject(this->in_object_ids[i]);
}
return ResultSuccess();
}
HipcRequest DomainServiceObjectProcessor::PrepareForReply(const cmif::ServiceDispatchContext &ctx, PointerAndSize &out_raw_data, const ServerMessageRuntimeMetadata runtime_metadata) {
/* Call into impl processor, get request. */
PointerAndSize raw_data;
HipcRequest request = this->impl_processor->PrepareForReply(ctx, raw_data, runtime_metadata);
/* Write out header. */
constexpr size_t out_header_size = sizeof(CmifDomainOutHeader);
const size_t impl_out_data_total_size = this->GetImplOutDataTotalSize();
AMS_ASSERT(out_header_size + impl_out_data_total_size + sizeof(DomainObjectId) * this->GetOutObjectCount() <= raw_data.GetSize());
*reinterpret_cast<CmifDomainOutHeader *>(raw_data.GetPointer()) = CmifDomainOutHeader{ .num_out_objects = static_cast<u32>(this->GetOutObjectCount()), };
/* Set output raw data. */
out_raw_data = cmif::PointerAndSize(raw_data.GetAddress() + out_header_size, raw_data.GetSize() - out_header_size);
this->out_object_ids = reinterpret_cast<DomainObjectId *>(out_raw_data.GetAddress() + impl_out_data_total_size);
return request;
}
void DomainServiceObjectProcessor::PrepareForErrorReply(const cmif::ServiceDispatchContext &ctx, PointerAndSize &out_raw_data, const ServerMessageRuntimeMetadata runtime_metadata) {
/* Call into impl processor, get request. */
PointerAndSize raw_data;
this->impl_processor->PrepareForErrorReply(ctx, raw_data, runtime_metadata);
/* Write out header. */
constexpr size_t out_header_size = sizeof(CmifDomainOutHeader);
const size_t impl_out_headers_size = this->GetImplOutHeadersSize();
AMS_ASSERT(out_header_size + impl_out_headers_size <= raw_data.GetSize());
*reinterpret_cast<CmifDomainOutHeader *>(raw_data.GetPointer()) = CmifDomainOutHeader{ .num_out_objects = 0, };
/* Set output raw data. */
out_raw_data = cmif::PointerAndSize(raw_data.GetAddress() + out_header_size, raw_data.GetSize() - out_header_size);
/* Nintendo unreserves domain entries here, but we haven't reserved them yet. */
}
void DomainServiceObjectProcessor::SetOutObjects(const cmif::ServiceDispatchContext &ctx, const HipcRequest &response, ServiceObjectHolder *out_objects, DomainObjectId *selected_ids) {
const size_t num_out_objects = this->GetOutObjectCount();
/* Copy input object IDs from command impl (normally these are Invalid, in mitm they should be set). */
DomainObjectId object_ids[8];
bool is_reserved[8];
for (size_t i = 0; i < num_out_objects; i++) {
object_ids[i] = selected_ids[i];
is_reserved[i] = false;
}
/* Reserve object IDs as necessary. */
{
DomainObjectId reservations[8];
{
size_t num_unreserved_ids = 0;
DomainObjectId specific_ids[8];
size_t num_specific_ids = 0;
for (size_t i = 0; i < num_out_objects; i++) {
/* In the mitm case, we must not reserve IDs in use by other objects, so mitm objects will set this. */
if (object_ids[i] == InvalidDomainObjectId) {
num_unreserved_ids++;
} else {
specific_ids[num_specific_ids++] = object_ids[i];
}
}
/* TODO: Can we make this error non-fatal? It isn't for N, since they can reserve IDs earlier due to not having to worry about mitm. */
R_ASSERT(this->domain->ReserveIds(reservations, num_unreserved_ids));
this->domain->ReserveSpecificIds(specific_ids, num_specific_ids);
}
size_t reservation_index = 0;
for (size_t i = 0; i < num_out_objects; i++) {
if (object_ids[i] == InvalidDomainObjectId) {
object_ids[i] = reservations[reservation_index++];
is_reserved[i] = true;
}
}
}
/* Actually set out objects. */
for (size_t i = 0; i < num_out_objects; i++) {
if (!out_objects[i]) {
if (is_reserved[i]) {
this->domain->UnreserveIds(object_ids + i, 1);
}
object_ids[i] = InvalidDomainObjectId;
continue;
}
this->domain->RegisterObject(object_ids[i], std::move(out_objects[i]));
}
/* Set out object IDs in message. */
for (size_t i = 0; i < num_out_objects; i++) {
this->out_object_ids[i] = object_ids[i];
}
}
}

View file

@ -0,0 +1,107 @@
/*
* 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>
namespace ams::sf::cmif {
Result impl::ServiceDispatchTableBase::ProcessMessageImpl(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data, const ServiceCommandMeta *entries, const size_t entry_count) const {
/* Get versioning info. */
const auto hos_version = hos::GetVersion();
const u32 max_cmif_version = hos_version >= hos::Version_500 ? 1 : 0;
/* Parse the CMIF in header. */
const CmifInHeader *in_header = reinterpret_cast<const CmifInHeader *>(in_raw_data.GetPointer());
R_UNLESS(in_raw_data.GetSize() >= sizeof(*in_header), sf::cmif::ResultInvalidHeaderSize());
R_UNLESS(in_header->magic == CMIF_IN_HEADER_MAGIC && in_header->version <= max_cmif_version, sf::cmif::ResultInvalidInHeader());
const cmif::PointerAndSize in_message_raw_data = cmif::PointerAndSize(in_raw_data.GetAddress() + sizeof(*in_header), in_raw_data.GetSize() - sizeof(*in_header));
const u32 cmd_id = in_header->command_id;
/* Find a handler. */
decltype(ServiceCommandMeta::handler) cmd_handler = nullptr;
for (size_t i = 0; i < entry_count; i++) {
if (entries[i].Matches(cmd_id, hos_version)) {
cmd_handler = entries[i].GetHandler();
break;
}
}
R_UNLESS(cmd_handler != nullptr, sf::cmif::ResultUnknownCommandId());
/* Invoke handler. */
CmifOutHeader *out_header = nullptr;
Result command_result = cmd_handler(&out_header, ctx, in_message_raw_data);
/* Forward forwardable results, otherwise ensure we can send result to user. */
R_TRY_CATCH(command_result) {
R_CATCH_RETHROW(sf::impl::ResultRequestContextChanged)
R_CATCH_ALL() { AMS_ASSERT(out_header != nullptr); }
} R_END_TRY_CATCH;
/* Write output header to raw data. */
if (out_header != nullptr) {
*out_header = CmifOutHeader{CMIF_OUT_HEADER_MAGIC, 0, command_result.GetValue(), 0};
}
return ResultSuccess();
}
Result impl::ServiceDispatchTableBase::ProcessMessageForMitmImpl(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data, const ServiceCommandMeta *entries, const size_t entry_count) const {
/* Get versioning info. */
const auto hos_version = hos::GetVersion();
const u32 max_cmif_version = hos_version >= hos::Version_500 ? 1 : 0;
/* Parse the CMIF in header. */
const CmifInHeader *in_header = reinterpret_cast<const CmifInHeader *>(in_raw_data.GetPointer());
R_UNLESS(in_raw_data.GetSize() >= sizeof(*in_header), sf::cmif::ResultInvalidHeaderSize());
R_UNLESS(in_header->magic == CMIF_IN_HEADER_MAGIC && in_header->version <= max_cmif_version, sf::cmif::ResultInvalidInHeader());
const cmif::PointerAndSize in_message_raw_data = cmif::PointerAndSize(in_raw_data.GetAddress() + sizeof(*in_header), in_raw_data.GetSize() - sizeof(*in_header));
const u32 cmd_id = in_header->command_id;
/* Find a handler. */
decltype(ServiceCommandMeta::handler) cmd_handler = nullptr;
for (size_t i = 0; i < entry_count; i++) {
if (entries[i].Matches(cmd_id, hos_version)) {
cmd_handler = entries[i].GetHandler();
break;
}
}
/* If we didn't find a handler, forward the request. */
if (cmd_handler == nullptr) {
return ctx.session->ForwardRequest(ctx);
}
/* Invoke handler. */
CmifOutHeader *out_header = nullptr;
Result command_result = cmd_handler(&out_header, ctx, in_message_raw_data);
/* Forward forwardable results, otherwise ensure we can send result to user. */
R_TRY_CATCH(command_result) {
R_CATCH(sm::mitm::ResultShouldForwardToSession) {
return ctx.session->ForwardRequest(ctx);
}
R_CATCH_RETHROW(sf::impl::ResultRequestContextChanged)
R_CATCH_ALL() { AMS_ASSERT(out_header != nullptr); }
} R_END_TRY_CATCH;
/* Write output header to raw data. */
if (out_header != nullptr) {
*out_header = CmifOutHeader{CMIF_OUT_HEADER_MAGIC, 0, command_result.GetValue(), 0};
}
return ResultSuccess();
}
}

View file

@ -0,0 +1,26 @@
/*
* 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>
namespace ams::sf::cmif {
Result ServiceObjectHolder::ProcessMessage(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const {
const auto ProcessHandler = this->dispatch_meta->ProcessHandler;
const auto *DispatchTable = this->dispatch_meta->DispatchTable;
return (DispatchTable->*ProcessHandler)(ctx, in_raw_data);
}
}

View file

@ -0,0 +1,86 @@
/*
* 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>
namespace ams::sf::hipc {
namespace {
NX_INLINE Result ReceiveImpl(Handle session_handle, void *message_buf, size_t message_buf_size) {
s32 unused_index;
if (message_buf == armGetTls()) {
/* Consider: AMS_ASSERT(message_buf_size == TlsMessageBufferSize); */
return svcReplyAndReceive(&unused_index, &session_handle, 1, INVALID_HANDLE, U64_MAX);
} else {
return svcReplyAndReceiveWithUserBuffer(&unused_index, message_buf, message_buf_size, &session_handle, 1, INVALID_HANDLE, U64_MAX);
}
}
NX_INLINE Result ReplyImpl(Handle session_handle, void *message_buf, size_t message_buf_size) {
s32 unused_index;
if (message_buf == armGetTls()) {
/* Consider: AMS_ASSERT(message_buf_size == TlsMessageBufferSize); */
return svcReplyAndReceive(&unused_index, &session_handle, 0, session_handle, 0);
} else {
return svcReplyAndReceiveWithUserBuffer(&unused_index, message_buf, message_buf_size, &session_handle, 0, session_handle, 0);
}
}
}
Result Receive(ReceiveResult *out_recv_result, Handle session_handle, const cmif::PointerAndSize &message_buffer) {
R_TRY_CATCH(ReceiveImpl(session_handle, message_buffer.GetPointer(), message_buffer.GetSize())) {
R_CATCH(svc::ResultSessionClosed) {
*out_recv_result = ReceiveResult::Closed;
return ResultSuccess();
}
R_CATCH(svc::ResultReceiveListBroken) {
*out_recv_result = ReceiveResult::NeedsRetry;
return ResultSuccess();
}
} R_END_TRY_CATCH;
*out_recv_result = ReceiveResult::Success;
return ResultSuccess();
}
Result Receive(bool *out_closed, Handle session_handle, const cmif::PointerAndSize &message_buffer) {
R_TRY_CATCH(ReceiveImpl(session_handle, message_buffer.GetPointer(), message_buffer.GetSize())) {
R_CATCH(svc::ResultSessionClosed) {
*out_closed = true;
return ResultSuccess();
}
} R_END_TRY_CATCH;
*out_closed = false;
return ResultSuccess();
}
Result Reply(Handle session_handle, const cmif::PointerAndSize &message_buffer) {
R_TRY_CATCH(ReplyImpl(session_handle, message_buffer.GetPointer(), message_buffer.GetSize())) {
R_CONVERT(svc::ResultTimedOut, ResultSuccess())
R_CONVERT(svc::ResultSessionClosed, ResultSuccess())
} R_END_TRY_CATCH;
/* ReplyImpl should *always* return an error. */
AMS_ASSERT(false);
}
Result CreateSession(Handle *out_server_handle, Handle *out_client_handle) {
R_TRY_CATCH(svcCreateSession(out_server_handle, out_client_handle, 0, 0)) {
R_CONVERT(svc::ResultOutOfResource, sf::hipc::ResultOutOfSessions());
} R_END_TRY_CATCH;
return ResultSuccess();
}
}

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 "sf_hipc_mitm_query_api.hpp"
namespace ams::sf::hipc::impl {
namespace {
class MitmQueryService : public IServiceObject {
private:
enum class CommandId {
ShouldMitm = 65000,
};
private:
ServerManagerBase::MitmQueryFunction query_function;
public:
MitmQueryService(ServerManagerBase::MitmQueryFunction qf) : query_function(qf) { /* ... */ }
void ShouldMitm(sf::Out<bool> out, const sm::MitmProcessInfo &client_info) {
out.SetValue(this->query_function(client_info));
}
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(ShouldMitm),
};
};
/* Globals. */
os::Mutex g_query_server_lock;
bool g_constructed_server = false;
bool g_registered_any = false;
void QueryServerProcessThreadMain(void *query_server) {
reinterpret_cast<ServerManagerBase *>(query_server)->LoopProcess();
}
constexpr size_t QueryServerProcessThreadStackSize = 0x4000;
constexpr int QueryServerProcessThreadPriority = 27;
os::StaticThread<QueryServerProcessThreadStackSize> g_query_server_process_thread;
constexpr size_t MaxServers = 0;
TYPED_STORAGE(sf::hipc::ServerManager<MaxServers>) g_query_server_storage;
}
void RegisterMitmQueryHandle(Handle query_handle, ServerManagerBase::MitmQueryFunction query_func) {
std::scoped_lock lk(g_query_server_lock);
if (!g_constructed_server) {
new (GetPointer(g_query_server_storage)) sf::hipc::ServerManager<MaxServers>();
g_constructed_server = true;
}
R_ASSERT(GetPointer(g_query_server_storage)->RegisterSession(query_handle, cmif::ServiceObjectHolder(std::make_shared<MitmQueryService>(query_func))));
if (!g_registered_any) {
R_ASSERT(g_query_server_process_thread.Initialize(&QueryServerProcessThreadMain, GetPointer(g_query_server_storage), QueryServerProcessThreadPriority));
R_ASSERT(g_query_server_process_thread.Start());
g_registered_any = true;
}
}
}

View file

@ -0,0 +1,23 @@
/*
* 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 <stratosphere.hpp>
namespace ams::sf::hipc::impl {
void RegisterMitmQueryHandle(Handle query_handle, ServerManagerBase::MitmQueryFunction query_func);
}

View file

@ -0,0 +1,174 @@
/*
* 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>
namespace ams::sf::hipc {
namespace impl {
class HipcManager : public IServiceObject {
private:
enum class CommandId {
ConvertCurrentObjectToDomain = 0,
CopyFromCurrentDomain = 1,
CloneCurrentObject = 2,
QueryPointerBufferSize = 3,
CloneCurrentObjectEx = 4,
};
private:
ServerDomainSessionManager *manager;
ServerSession *session;
bool is_mitm_session;
private:
Result CloneCurrentObjectImpl(Handle *out_client_handle, ServerSessionManager *tagged_manager) {
/* Clone the object. */
cmif::ServiceObjectHolder &&clone = this->session->srv_obj_holder.Clone();
R_UNLESS(clone, sf::hipc::ResultDomainObjectNotFound());
/* Create new session handles. */
Handle server_handle;
R_ASSERT(hipc::CreateSession(&server_handle, out_client_handle));
/* Register with manager. */
if (!is_mitm_session) {
R_ASSERT(tagged_manager->RegisterSession(server_handle, std::move(clone)));
} else {
/* Clone the forward service. */
std::shared_ptr<::Service> new_forward_service = std::move(ServerSession::CreateForwardService());
R_ASSERT(serviceClone(this->session->forward_service.get(), new_forward_service.get()));
R_ASSERT(tagged_manager->RegisterMitmSession(server_handle, std::move(clone), std::move(new_forward_service)));
}
return ResultSuccess();
}
public:
explicit HipcManager(ServerDomainSessionManager *m, ServerSession *s) : manager(m), session(s), is_mitm_session(s->forward_service != nullptr) {
/* ... */
}
Result ConvertCurrentObjectToDomain(sf::Out<cmif::DomainObjectId> out) {
/* Allocate a domain. */
auto domain = this->manager->AllocateDomainServiceObject();
R_UNLESS(domain, sf::hipc::ResultOutOfDomains());
auto domain_guard = SCOPE_GUARD { cmif::ServerDomainManager::DestroyDomainServiceObject(static_cast<cmif::DomainServiceObject *>(domain)); };
cmif::DomainObjectId object_id = cmif::InvalidDomainObjectId;
cmif::ServiceObjectHolder new_holder;
if (this->is_mitm_session) {
/* If we're a mitm session, we need to convert the remote session to domain. */
AMS_ASSERT(session->forward_service->own_handle);
R_TRY(serviceConvertToDomain(session->forward_service.get()));
/* The object ID reservation cannot fail here, as that would cause desynchronization from target domain. */
object_id = cmif::DomainObjectId{session->forward_service->object_id};
domain->ReserveSpecificIds(&object_id, 1);
/* Create new object. */
cmif::MitmDomainServiceObject *domain_ptr = static_cast<cmif::MitmDomainServiceObject *>(domain);
new_holder = cmif::ServiceObjectHolder(std::move(std::shared_ptr<cmif::MitmDomainServiceObject>(domain_ptr, cmif::ServerDomainManager::DestroyDomainServiceObject)));
} else {
/* We're not a mitm session. Reserve a new object in the domain. */
R_TRY(domain->ReserveIds(&object_id, 1));
/* Create new object. */
cmif::DomainServiceObject *domain_ptr = static_cast<cmif::DomainServiceObject *>(domain);
new_holder = cmif::ServiceObjectHolder(std::move(std::shared_ptr<cmif::DomainServiceObject>(domain_ptr, cmif::ServerDomainManager::DestroyDomainServiceObject)));
}
AMS_ASSERT(object_id != cmif::InvalidDomainObjectId);
AMS_ASSERT(static_cast<bool>(new_holder));
/* We succeeded! */
domain_guard.Cancel();
domain->RegisterObject(object_id, std::move(session->srv_obj_holder));
session->srv_obj_holder = std::move(new_holder);
out.SetValue(object_id);
return ResultSuccess();
}
Result CopyFromCurrentDomain(sf::OutMoveHandle out, cmif::DomainObjectId object_id) {
/* Get domain. */
auto domain = this->session->srv_obj_holder.GetServiceObject<cmif::DomainServiceObject>();
R_UNLESS(domain != nullptr, sf::hipc::ResultTargetNotDomain());
/* Get domain object. */
auto &&object = domain->GetObject(object_id);
if (!object) {
R_UNLESS(this->is_mitm_session, sf::hipc::ResultDomainObjectNotFound());
return cmifCopyFromCurrentDomain(this->session->forward_service->session, object_id.value, out.GetHandlePointer());
}
if (!this->is_mitm_session || object_id.value != serviceGetObjectId(this->session->forward_service.get())) {
/* Create new session handles. */
Handle server_handle;
R_ASSERT(hipc::CreateSession(&server_handle, out.GetHandlePointer()));
/* Register. */
R_ASSERT(this->manager->RegisterSession(server_handle, std::move(object)));
} else {
/* Copy from the target domain. */
Handle new_forward_target;
R_TRY(cmifCopyFromCurrentDomain(this->session->forward_service->session, object_id.value, &new_forward_target));
/* Create new session handles. */
Handle server_handle;
R_ASSERT(hipc::CreateSession(&server_handle, out.GetHandlePointer()));
/* Register. */
std::shared_ptr<::Service> new_forward_service = std::move(ServerSession::CreateForwardService());
serviceCreate(new_forward_service.get(), new_forward_target);
R_ASSERT(this->manager->RegisterMitmSession(server_handle, std::move(object), std::move(new_forward_service)));
}
return ResultSuccess();
}
Result CloneCurrentObject(sf::OutMoveHandle out) {
return this->CloneCurrentObjectImpl(out.GetHandlePointer(), this->manager);
}
void QueryPointerBufferSize(sf::Out<u16> out) {
out.SetValue(this->session->pointer_buffer.GetSize());
}
Result CloneCurrentObjectEx(sf::OutMoveHandle out, u32 tag) {
return this->CloneCurrentObjectImpl(out.GetHandlePointer(), this->manager->GetSessionManagerByTag(tag));
}
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(ConvertCurrentObjectToDomain),
MAKE_SERVICE_COMMAND_META(CopyFromCurrentDomain),
MAKE_SERVICE_COMMAND_META(CloneCurrentObject),
MAKE_SERVICE_COMMAND_META(QueryPointerBufferSize),
MAKE_SERVICE_COMMAND_META(CloneCurrentObjectEx),
};
};
}
Result ServerDomainSessionManager::DispatchManagerRequest(ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message) {
/* Make a stack object, and pass a shared pointer to it to DispatchRequest. */
/* Note: This is safe, as no additional references to the hipc manager can ever be stored. */
/* The shared pointer to stack object is definitely gross, though. */
impl::HipcManager hipc_manager(this, session);
return this->DispatchRequest(cmif::ServiceObjectHolder(std::move(ServiceObjectTraits<impl::HipcManager>::SharedPointerHelper::GetEmptyDeleteSharedPointer(&hipc_manager))), session, in_message, out_message);
}
}

View file

@ -0,0 +1,225 @@
/*
* 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 "sf_hipc_mitm_query_api.hpp"
namespace ams::sf::hipc {
ServerManagerBase::ServerBase::~ServerBase() { /* Pure virtual destructor, to prevent linker errors. */ }
Result ServerManagerBase::InstallMitmServerImpl(Handle *out_port_handle, sm::ServiceName service_name, ServerManagerBase::MitmQueryFunction query_func) {
/* Install the Mitm. */
Handle query_handle;
R_TRY(sm::mitm::InstallMitm(out_port_handle, &query_handle, service_name));
/* Register the query handle. */
impl::RegisterMitmQueryHandle(query_handle, query_func);
return ResultSuccess();
}
void ServerManagerBase::RegisterSessionToWaitList(ServerSession *session) {
session->has_received = false;
/* Set user data tag. */
session->SetUserData(static_cast<uintptr_t>(UserDataTag::Session));
this->RegisterToWaitList(session);
}
void ServerManagerBase::RegisterToWaitList(os::WaitableHolder *holder) {
std::scoped_lock lk(this->waitlist_mutex);
this->waitlist.LinkWaitableHolder(holder);
this->notify_event.Signal();
}
void ServerManagerBase::ProcessWaitList() {
std::scoped_lock lk(this->waitlist_mutex);
this->waitable_manager.MoveAllFrom(&this->waitlist);
}
os::WaitableHolder *ServerManagerBase::WaitSignaled() {
std::scoped_lock lk(this->waitable_selection_mutex);
while (true) {
this->ProcessWaitList();
auto selected = this->waitable_manager.WaitAny();
if (selected == &this->request_stop_event_holder) {
return nullptr;
} else if (selected == &this->notify_event_holder) {
this->notify_event.Reset();
} else {
selected->UnlinkFromWaitableManager();
return selected;
}
}
}
void ServerManagerBase::ResumeProcessing() {
this->request_stop_event.Reset();
}
void ServerManagerBase::RequestStopProcessing() {
this->request_stop_event.Signal();
}
void ServerManagerBase::AddUserWaitableHolder(os::WaitableHolder *waitable) {
const auto user_data_tag = static_cast<UserDataTag>(waitable->GetUserData());
AMS_ASSERT(user_data_tag != UserDataTag::Server);
AMS_ASSERT(user_data_tag != UserDataTag::MitmServer);
AMS_ASSERT(user_data_tag != UserDataTag::Session);
this->RegisterToWaitList(waitable);
}
Result ServerManagerBase::ProcessForServer(os::WaitableHolder *holder) {
AMS_ASSERT(static_cast<UserDataTag>(holder->GetUserData()) == UserDataTag::Server);
ServerBase *server = static_cast<ServerBase *>(holder);
ON_SCOPE_EXIT { this->RegisterToWaitList(server); };
/* Create resources for new session. */
cmif::ServiceObjectHolder obj;
std::shared_ptr<::Service> fsrv;
server->CreateSessionObjectHolder(&obj, &fsrv);
/* Not a mitm server, so we must have no forward service. */
AMS_ASSERT(fsrv == nullptr);
/* Try to accept. */
return this->AcceptSession(server->port_handle, std::move(obj));
}
Result ServerManagerBase::ProcessForMitmServer(os::WaitableHolder *holder) {
AMS_ASSERT(static_cast<UserDataTag>(holder->GetUserData()) == UserDataTag::MitmServer);
ServerBase *server = static_cast<ServerBase *>(holder);
ON_SCOPE_EXIT { this->RegisterToWaitList(server); };
/* Create resources for new session. */
cmif::ServiceObjectHolder obj;
std::shared_ptr<::Service> fsrv;
server->CreateSessionObjectHolder(&obj, &fsrv);
/* Mitm server, so we must have forward service. */
AMS_ASSERT(fsrv != nullptr);
/* Try to accept. */
return this->AcceptMitmSession(server->port_handle, std::move(obj), std::move(fsrv));
}
Result ServerManagerBase::ProcessForSession(os::WaitableHolder *holder) {
AMS_ASSERT(static_cast<UserDataTag>(holder->GetUserData()) == UserDataTag::Session);
ServerSession *session = static_cast<ServerSession *>(holder);
cmif::PointerAndSize tls_message(armGetTls(), hipc::TlsMessageBufferSize);
const cmif::PointerAndSize &saved_message = session->saved_message;
AMS_ASSERT(tls_message.GetSize() == saved_message.GetSize());
if (!session->has_received) {
R_TRY(this->ReceiveRequest(session, tls_message));
session->has_received = true;
std::memcpy(saved_message.GetPointer(), tls_message.GetPointer(), tls_message.GetSize());
} else {
/* We were deferred and are re-receiving, so just memcpy. */
std::memcpy(tls_message.GetPointer(), saved_message.GetPointer(), tls_message.GetSize());
}
/* Treat a meta "Context Invalidated" message as a success. */
R_TRY_CATCH(this->ProcessRequest(session, tls_message)) {
R_CONVERT(sf::impl::ResultRequestInvalidated, ResultSuccess());
} R_END_TRY_CATCH;
return ResultSuccess();
}
void ServerManagerBase::ProcessDeferredSessions() {
/* Iterate over the list of deferred sessions, and see if we can't do anything. */
std::scoped_lock lk(this->deferred_session_mutex);
/* Undeferring a request may undefer another request. We'll continue looping until everything is stable. */
bool needs_undefer_all = true;
while (needs_undefer_all) {
needs_undefer_all = false;
auto it = this->deferred_session_list.begin();
while (it != this->deferred_session_list.end()) {
ServerSession *session = static_cast<ServerSession *>(&*it);
R_TRY_CATCH(this->ProcessForSession(session)) {
R_CATCH(sf::ResultRequestDeferred) {
/* Session is still deferred, so let's continue. */
it++;
continue;
}
R_CATCH(sf::impl::ResultRequestInvalidated) {
/* Session is no longer deferred! */
it = this->deferred_session_list.erase(it);
needs_undefer_all = true;
continue;
}
} R_END_TRY_CATCH_WITH_ASSERT;
/* We succeeded! Remove from deferred list. */
it = this->deferred_session_list.erase(it);
needs_undefer_all = true;
}
}
}
Result ServerManagerBase::Process(os::WaitableHolder *holder) {
switch (static_cast<UserDataTag>(holder->GetUserData())) {
case UserDataTag::Server:
return this->ProcessForServer(holder);
break;
case UserDataTag::MitmServer:
return this->ProcessForMitmServer(holder);
break;
case UserDataTag::Session:
/* Try to process for session. */
R_TRY_CATCH(this->ProcessForSession(holder)) {
R_CATCH(sf::ResultRequestDeferred) {
/* The session was deferred, so push it onto the deferred session list. */
std::scoped_lock lk(this->deferred_session_mutex);
this->deferred_session_list.push_back(*static_cast<ServerSession *>(holder));
return ResultSuccess();
}
} R_END_TRY_CATCH;
/* We successfully invoked a command...so let's see if anything can be undeferred. */
this->ProcessDeferredSessions();
return ResultSuccess();
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
bool ServerManagerBase::WaitAndProcessImpl() {
auto waitable = this->WaitSignaled();
if (!waitable) {
return false;
}
R_ASSERT(this->Process(waitable));
return true;
}
void ServerManagerBase::WaitAndProcess() {
this->WaitAndProcessImpl();
}
void ServerManagerBase::LoopProcess() {
while (this->WaitAndProcessImpl()) {
/* ... */
}
}
}

View file

@ -0,0 +1,303 @@
/*
* 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>
namespace ams::sf::hipc {
namespace {
constexpr inline void PreProcessCommandBufferForMitm(const cmif::ServiceDispatchContext &ctx, const cmif::PointerAndSize &pointer_buffer, uintptr_t cmd_buffer) {
/* TODO: Less gross method of editing command buffer? */
if (ctx.request.meta.send_pid) {
constexpr u64 MitmProcessIdTag = 0xFFFE000000000000ul;
constexpr u64 OldProcessIdMask = 0x0000FFFFFFFFFFFFul;
u64 *process_id = reinterpret_cast<u64 *>(cmd_buffer + sizeof(HipcHeader) + sizeof(HipcSpecialHeader));
*process_id = (MitmProcessIdTag) | (*process_id & OldProcessIdMask);
}
if (ctx.request.meta.num_recv_statics) {
/* TODO: Can we do this without gross bit-hackery? */
reinterpret_cast<HipcHeader *>(cmd_buffer)->recv_static_mode = 2;
const uintptr_t old_recv_list_entry = reinterpret_cast<uintptr_t>(ctx.request.data.recv_list);
const size_t old_recv_list_offset = old_recv_list_entry - util::AlignDown(old_recv_list_entry, TlsMessageBufferSize);
*reinterpret_cast<HipcRecvListEntry *>(cmd_buffer + old_recv_list_offset) = hipcMakeRecvStatic(pointer_buffer.GetPointer(), pointer_buffer.GetSize());
}
}
}
Result ServerSession::ForwardRequest(const cmif::ServiceDispatchContext &ctx) const {
AMS_ASSERT(this->IsMitmSession());
/* TODO: Support non-TLS messages? */
AMS_ASSERT(this->saved_message.GetPointer() != nullptr);
AMS_ASSERT(this->saved_message.GetSize() == TlsMessageBufferSize);
/* Copy saved TLS in. */
std::memcpy(armGetTls(), this->saved_message.GetPointer(), this->saved_message.GetSize());
/* Prepare buffer. */
PreProcessCommandBufferForMitm(ctx, this->pointer_buffer, reinterpret_cast<uintptr_t>(armGetTls()));
/* Dispatch forwards. */
R_TRY(svcSendSyncRequest(this->forward_service->session));
/* Parse, to ensure we catch any copy handles and close them. */
{
const auto response = hipcParseResponse(armGetTls());
if (response.num_copy_handles) {
ctx.handles_to_close->num_handles = response.num_copy_handles;
for (size_t i = 0; i < response.num_copy_handles; i++) {
ctx.handles_to_close->handles[i] = response.copy_handles[i];
}
}
}
return ResultSuccess();
}
void ServerSessionManager::DestroySession(ServerSession *session) {
/* Destroy object. */
session->~ServerSession();
/* Free object memory. */
this->FreeSession(session);
}
void ServerSessionManager::CloseSessionImpl(ServerSession *session) {
const Handle session_handle = session->session_handle;
this->DestroySession(session);
R_ASSERT(svcCloseHandle(session_handle));
}
Result ServerSessionManager::RegisterSessionImpl(ServerSession *session_memory, Handle session_handle, cmif::ServiceObjectHolder &&obj) {
/* Create session object. */
new (session_memory) ServerSession(session_handle, std::forward<cmif::ServiceObjectHolder>(obj));
/* Assign session resources. */
session_memory->pointer_buffer = this->GetSessionPointerBuffer(session_memory);
session_memory->saved_message = this->GetSessionSavedMessageBuffer(session_memory);
/* Register to wait list. */
this->RegisterSessionToWaitList(session_memory);
return ResultSuccess();
}
Result ServerSessionManager::AcceptSessionImpl(ServerSession *session_memory, Handle port_handle, cmif::ServiceObjectHolder &&obj) {
/* Create session handle. */
Handle session_handle;
R_TRY(svcAcceptSession(&session_handle, port_handle));
bool succeeded = false;
ON_SCOPE_EXIT {
if (!succeeded) {
R_ASSERT(svcCloseHandle(session_handle));
}
};
/* Register session. */
R_TRY(this->RegisterSessionImpl(session_memory, session_handle, std::forward<cmif::ServiceObjectHolder>(obj)));
succeeded = true;
return ResultSuccess();
}
Result ServerSessionManager::RegisterMitmSessionImpl(ServerSession *session_memory, Handle mitm_session_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv) {
/* Create session object. */
new (session_memory) ServerSession(mitm_session_handle, std::forward<cmif::ServiceObjectHolder>(obj), std::forward<std::shared_ptr<::Service>>(fsrv));
/* Assign session resources. */
session_memory->pointer_buffer = this->GetSessionPointerBuffer(session_memory);
session_memory->saved_message = this->GetSessionSavedMessageBuffer(session_memory);
/* Validate session pointer buffer. */
AMS_ASSERT(session_memory->pointer_buffer.GetSize() >= session_memory->forward_service->pointer_buffer_size);
session_memory->pointer_buffer = cmif::PointerAndSize(session_memory->pointer_buffer.GetAddress(), session_memory->forward_service->pointer_buffer_size);
/* Register to wait list. */
this->RegisterSessionToWaitList(session_memory);
return ResultSuccess();
}
Result ServerSessionManager::AcceptMitmSessionImpl(ServerSession *session_memory, Handle mitm_port_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv) {
/* Create session handle. */
Handle mitm_session_handle;
R_TRY(svcAcceptSession(&mitm_session_handle, mitm_port_handle));
bool succeeded = false;
ON_SCOPE_EXIT {
if (!succeeded) {
R_ASSERT(svcCloseHandle(mitm_session_handle));
}
};
/* Register session. */
R_TRY(this->RegisterMitmSessionImpl(session_memory, mitm_session_handle, std::forward<cmif::ServiceObjectHolder>(obj), std::forward<std::shared_ptr<::Service>>(fsrv)));
succeeded = true;
return ResultSuccess();
}
Result ServerSessionManager::RegisterSession(Handle session_handle, cmif::ServiceObjectHolder &&obj) {
/* We don't actually care about what happens to the session. It'll get linked. */
ServerSession *session_ptr = nullptr;
return this->RegisterSession(&session_ptr, session_handle, std::forward<cmif::ServiceObjectHolder>(obj));
}
Result ServerSessionManager::AcceptSession(Handle port_handle, cmif::ServiceObjectHolder &&obj) {
/* We don't actually care about what happens to the session. It'll get linked. */
ServerSession *session_ptr = nullptr;
return this->AcceptSession(&session_ptr, port_handle, std::forward<cmif::ServiceObjectHolder>(obj));
}
Result ServerSessionManager::RegisterMitmSession(Handle mitm_session_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv) {
/* We don't actually care about what happens to the session. It'll get linked. */
ServerSession *session_ptr = nullptr;
return this->RegisterMitmSession(&session_ptr, mitm_session_handle, std::forward<cmif::ServiceObjectHolder>(obj), std::forward<std::shared_ptr<::Service>>(fsrv));
}
Result ServerSessionManager::AcceptMitmSession(Handle mitm_port_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv) {
/* We don't actually care about what happens to the session. It'll get linked. */
ServerSession *session_ptr = nullptr;
return this->AcceptMitmSession(&session_ptr, mitm_port_handle, std::forward<cmif::ServiceObjectHolder>(obj), std::forward<std::shared_ptr<::Service>>(fsrv));
}
Result ServerSessionManager::ReceiveRequestImpl(ServerSession *session, const cmif::PointerAndSize &message) {
const cmif::PointerAndSize &pointer_buffer = session->pointer_buffer;
/* If the receive list is odd, we may need to receive repeatedly. */
while (true) {
if (pointer_buffer.GetPointer()) {
hipcMakeRequestInline(message.GetPointer(),
.type = CmifCommandType_Invalid,
.num_recv_statics = HIPC_AUTO_RECV_STATIC,
).recv_list[0] = hipcMakeRecvStatic(pointer_buffer.GetPointer(), pointer_buffer.GetSize());
} else {
hipcMakeRequestInline(message.GetPointer(),
.type = CmifCommandType_Invalid,
);
}
hipc::ReceiveResult recv_result;
R_TRY(hipc::Receive(&recv_result, session->session_handle, message));
switch (recv_result) {
case hipc::ReceiveResult::Success:
session->is_closed = false;
return ResultSuccess();
case hipc::ReceiveResult::Closed:
session->is_closed = true;
return ResultSuccess();
case hipc::ReceiveResult::NeedsRetry:
continue;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
}
namespace {
NX_CONSTEXPR u32 GetCmifCommandType(const cmif::PointerAndSize &message) {
HipcHeader hdr = {};
__builtin_memcpy(&hdr, message.GetPointer(), sizeof(hdr));
return hdr.type;
}
}
Result ServerSessionManager::ProcessRequest(ServerSession *session, const cmif::PointerAndSize &message) {
if (session->is_closed) {
this->CloseSessionImpl(session);
return ResultSuccess();
}
switch (GetCmifCommandType(message)) {
case CmifCommandType_Close:
{
this->CloseSessionImpl(session);
return ResultSuccess();
}
default:
{
R_TRY_CATCH(this->ProcessRequestImpl(session, message, message)) {
R_CATCH(sf::impl::ResultRequestContextChanged) {
/* A meta message changing the request context has been sent. */
return R_CURRENT_RESULT;
}
R_CATCH_ALL() {
/* All other results indicate something went very wrong. */
this->CloseSessionImpl(session);
return ResultSuccess();
}
} R_END_TRY_CATCH;
/* We succeeded, so we can process future messages on this session. */
this->RegisterSessionToWaitList(session);
return ResultSuccess();
}
}
}
Result ServerSessionManager::ProcessRequestImpl(ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message) {
/* TODO: Inline context support, retrieve from raw data + 0xC. */
const auto cmif_command_type = GetCmifCommandType(in_message);
switch (cmif_command_type) {
case CmifCommandType_Request:
case CmifCommandType_RequestWithContext:
return this->DispatchRequest(session->srv_obj_holder.Clone(), session, in_message, out_message);
case CmifCommandType_Control:
case CmifCommandType_ControlWithContext:
return this->DispatchManagerRequest(session, in_message, out_message);
default:
return sf::hipc::ResultUnknownCommandType();
}
}
Result ServerSessionManager::DispatchManagerRequest(ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message) {
/* This will get overridden by ... WithDomain class. */
return sf::ResultNotSupported();
}
Result ServerSessionManager::DispatchRequest(cmif::ServiceObjectHolder &&obj_holder, ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message) {
/* Create request context. */
cmif::HandlesToClose handles_to_close = {};
cmif::ServiceDispatchContext dispatch_ctx = {
.srv_obj = obj_holder.GetServiceObjectUnsafe(),
.manager = this,
.session = session,
.processor = nullptr, /* Filled in by template implementations. */
.handles_to_close = &handles_to_close,
.pointer_buffer = session->pointer_buffer,
.in_message_buffer = in_message,
.out_message_buffer = out_message,
.request = hipcParseRequest(in_message.GetPointer()),
};
/* Validate message sizes. */
const uintptr_t in_message_buffer_end = in_message.GetAddress() + in_message.GetSize();
const uintptr_t in_raw_addr = reinterpret_cast<uintptr_t>(dispatch_ctx.request.data.data_words);
const size_t in_raw_size = dispatch_ctx.request.meta.num_data_words * sizeof(u32);
/* Note: Nintendo does not validate this size before subtracting 0x10 from it. This is not exploitable. */
R_UNLESS(in_raw_size >= 0x10, sf::hipc::ResultInvalidRequestSize());
R_UNLESS(in_raw_addr + in_raw_size <= in_message_buffer_end, sf::hipc::ResultInvalidRequestSize());
const uintptr_t recv_list_end = reinterpret_cast<uintptr_t>(dispatch_ctx.request.data.recv_list + dispatch_ctx.request.meta.num_recv_statics);
R_UNLESS(recv_list_end <= in_message_buffer_end, sf::hipc::ResultInvalidRequestSize());
/* CMIF has 0x10 of padding in raw data, and requires 0x10 alignment. */
const cmif::PointerAndSize in_raw_data(util::AlignUp(in_raw_addr, 0x10), in_raw_size - 0x10);
/* Invoke command handler. */
R_TRY(obj_holder.ProcessMessage(dispatch_ctx, in_raw_data));
/* Reply. */
{
ON_SCOPE_EXIT {
for (size_t i = 0; i < handles_to_close.num_handles; i++) {
R_ASSERT(svcCloseHandle(handles_to_close.handles[i]));
}
};
R_TRY(hipc::Reply(session->session_handle, out_message));
}
return ResultSuccess();
}
}

View file

@ -0,0 +1,131 @@
/*
* 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/>.
*/
#define NX_SERVICE_ASSUME_NON_DOMAIN
#include "../service_guard.h"
#include "sm_ams.h"
static Result _smAtmosphereCmdHas(bool *out, SmServiceName name, u32 cmd_id) {
u8 tmp;
Result rc = serviceDispatchInOut(smGetServiceSession(), cmd_id, name, tmp);
if (R_SUCCEEDED(rc) && out) *out = tmp & 1;
return rc;
}
static Result _smAtmosphereCmdInServiceNameNoOut(SmServiceName name, Service *srv, u32 cmd_id) {
return serviceDispatchIn(srv, cmd_id, name);
}
Result smAtmosphereHasService(bool *out, SmServiceName name) {
return _smAtmosphereCmdHas(out, name, 65100);
}
Result smAtmosphereWaitService(SmServiceName name) {
return _smAtmosphereCmdInServiceNameNoOut(name, smGetServiceSession(), 65101);
}
Result smAtmosphereHasMitm(bool *out, SmServiceName name) {
return _smAtmosphereCmdHas(out, name, 65004);
}
Result smAtmosphereWaitMitm(SmServiceName name) {
return _smAtmosphereCmdInServiceNameNoOut(name, smGetServiceSession(), 65005);
}
static Service g_smAtmosphereMitmSrv;
NX_GENERATE_SERVICE_GUARD(smAtmosphereMitm);
Result _smAtmosphereMitmInitialize(void) {
return smAtmosphereOpenSession(&g_smAtmosphereMitmSrv);
}
void _smAtmosphereMitmCleanup(void) {
smAtmosphereCloseSession(&g_smAtmosphereMitmSrv);
}
Service* smAtmosphereMitmGetServiceSession(void) {
return &g_smAtmosphereMitmSrv;
}
Result smAtmosphereOpenSession(Service *out) {
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(out, sm_handle);
}
if (R_SUCCEEDED(rc)) {
const u64 pid_placeholder = 0;
rc = serviceDispatchIn(out, 0, pid_placeholder, .in_send_pid = true);
}
return rc;
}
void smAtmosphereCloseSession(Service *srv) {
serviceClose(srv);
}
Result smAtmosphereMitmInstall(Service *fwd_srv, Handle *handle_out, Handle *query_out, SmServiceName name) {
Handle tmp_handles[2];
Result rc = serviceDispatchIn(fwd_srv, 65000, name,
.out_handle_attrs = { SfOutHandleAttr_HipcMove, SfOutHandleAttr_HipcMove },
.out_handles = tmp_handles,
);
if (R_SUCCEEDED(rc)) {
*handle_out = tmp_handles[0];
*query_out = tmp_handles[1];
}
return rc;
}
Result smAtmosphereMitmUninstall(SmServiceName name) {
return _smAtmosphereCmdInServiceNameNoOut(name, smGetServiceSession(), 65001);
}
Result smAtmosphereMitmDeclareFuture(SmServiceName name) {
return _smAtmosphereCmdInServiceNameNoOut(name, smGetServiceSession(), 65006);
}
Result smAtmosphereMitmAcknowledgeSession(Service *srv_out, void *_out, SmServiceName name) {
struct {
u64 process_id;
u64 program_id;
u64 keys_held;
u64 flags;
} *out = _out;
_Static_assert(sizeof(*out) == 0x20, "sizeof(*out) == 0x20");
Handle tmp_handle;
Result rc = serviceDispatchInOut(&g_smAtmosphereMitmSrv, 65003, name, *out,
.out_handle_attrs = { SfOutHandleAttr_HipcMove },
.out_handles = &tmp_handle,
);
if (R_SUCCEEDED(rc)) {
serviceCreate(srv_out, tmp_handle);
}
return rc;
}

View file

@ -0,0 +1,35 @@
/**
* @file sm_ams.h
* @brief Service manager (sm) IPC wrapper for Atmosphere extensions.
* @author SciresM
* @copyright libnx Authors
*/
#pragma once
#include <switch/types.h>
#include <switch/kernel/event.h>
#include <switch/services/sm.h>
#ifdef __cplusplus
extern "C" {
#endif
Result smAtmosphereHasService(bool *out, SmServiceName name);
Result smAtmosphereWaitService(SmServiceName name);
Result smAtmosphereHasMitm(bool *out, SmServiceName name);
Result smAtmosphereWaitMitm(SmServiceName name);
Result smAtmosphereMitmInitialize(void);
void smAtmosphereMitmExit(void);
Service *smAtmosphereMitmGetServiceSession();
Result smAtmosphereOpenSession(Service *out);
void smAtmosphereCloseSession(Service *srv);
Result smAtmosphereMitmInstall(Service *fwd_srv, Handle *handle_out, Handle *query_out, SmServiceName name);
Result smAtmosphereMitmUninstall(SmServiceName name);
Result smAtmosphereMitmDeclareFuture(SmServiceName name);
Result smAtmosphereMitmAcknowledgeSession(Service *srv_out, void *info_out, SmServiceName name);
#ifdef __cplusplus
}
#endif

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 "sm_utils.hpp"
namespace ams::sm {
/* Ordinary SM API. */
Result GetService(Service *out, ServiceName name) {
return impl::DoWithUserSession([&]() {
return smGetServiceWrapper(out, impl::ConvertName(name));
});
}
Result RegisterService(Handle *out, ServiceName name, size_t max_sessions, bool is_light) {
return impl::DoWithUserSession([&]() {
return smRegisterService(out, impl::ConvertName(name), is_light, static_cast<int>(max_sessions));
});
}
Result UnregisterService(ServiceName name) {
return impl::DoWithUserSession([&]() {
return smUnregisterService(impl::ConvertName(name));
});
}
/* Atmosphere extensions. */
Result HasService(bool *out, ServiceName name) {
return impl::DoWithUserSession([&]() {
return smAtmosphereHasService(out, impl::ConvertName(name));
});
}
Result WaitService(ServiceName name) {
return impl::DoWithUserSession([&]() {
return smAtmosphereWaitService(impl::ConvertName(name));
});
}
namespace impl {
void DoWithSessionImpl(void (*Invoker)(void *), void *Function) {
impl::DoWithUserSession([&]() {
Invoker(Function);
return ResultSuccess();
});
}
}
}

View file

@ -0,0 +1,40 @@
/*
* 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"
#include "smm_ams.h"
namespace ams::sm::manager {
/* Manager API. */
Result RegisterProcess(os::ProcessId process_id, ncm::ProgramId program_id, cfg::OverrideStatus status, const void *acid, size_t acid_size, const void *aci, size_t aci_size) {
static_assert(sizeof(status) == sizeof(CfgOverrideStatus), "CfgOverrideStatus definition");
return smManagerAtmosphereRegisterProcess(static_cast<u64>(process_id), static_cast<u64>(program_id), reinterpret_cast<const CfgOverrideStatus *>(&status), acid, acid_size, aci, aci_size);
}
Result UnregisterProcess(os::ProcessId process_id) {
return smManagerUnregisterProcess(static_cast<u64>(process_id));
}
/* Atmosphere extensions. */
Result EndInitialDefers() {
return smManagerAtmosphereEndInitialDefers();
}
Result HasMitm(bool *out, ServiceName name) {
return smManagerAtmosphereHasMitm(out, impl::ConvertName(name));
}
}

View file

@ -0,0 +1,57 @@
/*
* 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 ams::sm::mitm {
/* Mitm API. */
Result InstallMitm(Handle *out_port, Handle *out_query, ServiceName name) {
return impl::DoWithPerThreadSession([&](Service *fwd) {
return smAtmosphereMitmInstall(fwd, out_port, out_query, impl::ConvertName(name));
});
}
Result UninstallMitm(ServiceName name) {
return impl::DoWithUserSession([&]() {
return smAtmosphereMitmUninstall(impl::ConvertName(name));
});
}
Result DeclareFutureMitm(ServiceName name) {
return impl::DoWithUserSession([&]() {
return smAtmosphereMitmDeclareFuture(impl::ConvertName(name));
});
}
Result AcknowledgeSession(Service *out_service, MitmProcessInfo *out_info, ServiceName name) {
return impl::DoWithMitmAcknowledgementSession([&]() {
return smAtmosphereMitmAcknowledgeSession(out_service, reinterpret_cast<void *>(out_info), impl::ConvertName(name));
});
}
Result HasMitm(bool *out, ServiceName name) {
return impl::DoWithUserSession([&]() {
return smAtmosphereHasMitm(out, impl::ConvertName(name));
});
}
Result WaitMitm(ServiceName name) {
return impl::DoWithUserSession([&]() {
return smAtmosphereWaitMitm(impl::ConvertName(name));
});
}
}

View file

@ -0,0 +1,42 @@
/*
* 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 ams::sm::impl {
namespace {
/* Globals. */
os::RecursiveMutex g_user_session_mutex;
os::RecursiveMutex g_mitm_ack_session_mutex;
os::RecursiveMutex g_per_thread_session_mutex;
}
/* Utilities. */
os::RecursiveMutex &GetUserSessionMutex() {
return g_user_session_mutex;
}
os::RecursiveMutex &GetMitmAcknowledgementSessionMutex() {
return g_mitm_ack_session_mutex;
}
os::RecursiveMutex &GetPerThreadSessionMutex() {
return g_per_thread_session_mutex;
}
}

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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "sm_ams.h"
namespace ams::sm::impl {
/* Utilities. */
os::RecursiveMutex &GetUserSessionMutex();
os::RecursiveMutex &GetMitmAcknowledgementSessionMutex();
os::RecursiveMutex &GetPerThreadSessionMutex();
template<typename F>
Result DoWithUserSession(F f) {
std::scoped_lock<os::RecursiveMutex &> lk(GetUserSessionMutex());
{
R_ASSERT(smInitialize());
ON_SCOPE_EXIT { smExit(); };
return f();
}
}
template<typename F>
Result DoWithMitmAcknowledgementSession(F f) {
std::scoped_lock<os::RecursiveMutex &> lk(GetMitmAcknowledgementSessionMutex());
{
R_ASSERT(smAtmosphereMitmInitialize());
ON_SCOPE_EXIT { smAtmosphereMitmExit(); };
return f();
}
}
template<typename F>
Result DoWithPerThreadSession(F f) {
Service srv;
{
std::scoped_lock<os::RecursiveMutex &> lk(GetPerThreadSessionMutex());
R_ASSERT(smAtmosphereOpenSession(&srv));
}
{
ON_SCOPE_EXIT { smAtmosphereCloseSession(&srv); };
return f(&srv);
}
}
NX_CONSTEXPR SmServiceName ConvertName(sm::ServiceName name) {
static_assert(sizeof(SmServiceName) == sizeof(sm::ServiceName));
SmServiceName ret = {};
__builtin_memcpy(&ret, &name, sizeof(sm::ServiceName));
return ret;
}
}

View file

@ -0,0 +1,49 @@
/*
* 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 "smm_ams.h"
Result smManagerAtmosphereEndInitialDefers(void) {
return serviceDispatch(smManagerGetServiceSession(), 65000);
}
Result smManagerAtmosphereRegisterProcess(u64 pid, u64 tid, const CfgOverrideStatus *status, const void *acid_sac, size_t acid_sac_size, const void *aci_sac, size_t aci_sac_size) {
const struct {
u64 pid;
u64 tid;
CfgOverrideStatus status;
} in = { pid, tid, *status };
return serviceDispatchIn(smManagerGetServiceSession(), 65002, in,
.buffer_attrs = {
SfBufferAttr_In | SfBufferAttr_HipcMapAlias,
SfBufferAttr_In | SfBufferAttr_HipcMapAlias,
},
.buffers = {
{ acid_sac, acid_sac_size },
{ aci_sac, aci_sac_size },
},
);
}
static Result _smManagerAtmosphereCmdHas(bool *out, SmServiceName name, u32 cmd_id) {
u8 tmp;
Result rc = serviceDispatchInOut(smManagerGetServiceSession(), cmd_id, name, tmp);
if (R_SUCCEEDED(rc) && out) *out = tmp & 1;
return rc;
}
Result smManagerAtmosphereHasMitm(bool *out, SmServiceName name) {
return _smManagerAtmosphereCmdHas(out, name, 65001);
}

View file

@ -0,0 +1,25 @@
/**
* @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
typedef struct {
u64 keys_held;
u64 flags;
} CfgOverrideStatus;
Result smManagerAtmosphereEndInitialDefers(void);
Result smManagerAtmosphereRegisterProcess(u64 pid, u64 tid, const CfgOverrideStatus *status, const void *acid_sac, size_t acid_sac_size, const void *aci_sac, size_t aci_sac_size);
Result smManagerAtmosphereHasMitm(bool *out, SmServiceName name);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,378 @@
/*
* 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>
namespace ams::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]);
}
/* Atmosphere functions. */
namespace {
enum class IramCopyDirection {
FromIram = 0,
ToIram = 1,
};
inline Result AtmosphereIramCopy(uintptr_t dram_address, uintptr_t iram_address, size_t size, IramCopyDirection direction) {
SecmonArgs args;
args.X[0] = static_cast<u64>(FunctionId::AtmosphereIramCopy);
args.X[1] = dram_address;
args.X[2] = iram_address;
args.X[3] = size;
args.X[4] = static_cast<u64>(direction);
svcCallSecureMonitor(&args);
return static_cast<Result>(args.X[0]);
}
}
Result AtmosphereCopyToIram(uintptr_t iram_dst, const void *dram_src, size_t size) {
return AtmosphereIramCopy(reinterpret_cast<uintptr_t>(dram_src), iram_dst, size, IramCopyDirection::ToIram);
}
Result AtmosphereCopyFromIram(void *dram_dst, uintptr_t iram_src, size_t size) {
return AtmosphereIramCopy(reinterpret_cast<uintptr_t>(dram_dst), iram_src, size, IramCopyDirection::FromIram);
}
Result AtmosphereReadWriteRegister(uint64_t address, uint32_t mask, uint32_t value, uint32_t *out_value) {
SecmonArgs args;
args.X[0] = static_cast<u64>(FunctionId::AtmosphereReadWriteRegister);
args.X[1] = address;
args.X[2] = mask;
args.X[3] = value;
svcCallSecureMonitor(&args);
*out_value = static_cast<uint32_t>(args.X[1]);
return static_cast<Result>(args.X[0]);
}
Result AtmosphereWriteAddress(void *dst, const void *src, size_t size) {
AMS_ASSERT(size <= sizeof(u64));
SecmonArgs args;
args.X[0] = static_cast<u64>(FunctionId::AtmosphereWriteAddress);
args.X[1] = reinterpret_cast<uintptr_t>(dst);
__builtin_memcpy(&args.X[1], src, size);
args.X[3] = size;
svcCallSecureMonitor(&args);
return static_cast<Result>(args.X[0]);
}
Result AtmosphereGetEmummcConfig(void *out_config, void *out_paths, u32 storage_id) {
const u64 paths = reinterpret_cast<u64>(out_paths);
AMS_ASSERT(util::IsAligned(paths, os::MemoryPageSize));
SecmonArgs args = {};
args.X[0] = static_cast<u64>(FunctionId::AtmosphereGetEmummcConfig);
args.X[1] = storage_id;
args.X[2] = paths;
svcCallSecureMonitor(&args);
std::memcpy(out_config, &args.X[1], sizeof(args) - sizeof(args.X[0]));
return static_cast<Result>(args.X[0]);
}
}

View file

@ -0,0 +1,75 @@
/*
* 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>
namespace ams::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;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
}

View file

@ -0,0 +1,530 @@
/*
* 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 ams::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);
NcmContentMetaType GetNcmContentMetaType(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) {
R_UNLESS(work_buffer_size >= BctSize + EksSize, ResultTooSmallWorkBuffer());
R_UNLESS(util::IsAligned(work_buffer, os::MemoryPageSize), ResultNotAlignedWorkBuffer());
R_UNLESS(util::IsAligned(work_buffer_size, 0x200), ResultNotAlignedWorkBuffer());
return ResultSuccess();
}
bool HasEks(BootImageUpdateType boot_image_update_type) {
switch (boot_image_update_type) {
case BootImageUpdateType::Erista:
return true;
case BootImageUpdateType::Mariko:
return false;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
bool HasAutoRcmPreserve(BootImageUpdateType boot_image_update_type) {
switch (boot_image_update_type) {
case BootImageUpdateType::Erista:
return true;
case BootImageUpdateType::Mariko:
return false;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
NcmContentMetaType GetNcmContentMetaType(BootModeType mode) {
switch (mode) {
case BootModeType::Normal:
return NcmContentMetaType_BootImagePackage;
case BootModeType::Safe:
return NcmContentMetaType_BootImagePackageSafe;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
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(ResultNeedsRepairBootImages) {
/* 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;
AMS_ASSERT(work_buffer_size >= sizeof(NcmContentMetaKey) * MaxContentMetas);
/* Open NAND System meta database, list contents. */
NcmContentMetaDatabase meta_db;
R_TRY(ncmOpenContentMetaDatabase(&meta_db, NcmStorageId_BuiltInSystem));
ON_SCOPE_EXIT { serviceClose(&meta_db.s); };
NcmContentMetaKey *records = reinterpret_cast<NcmContentMetaKey *>(work_buffer);
const auto content_meta_type = GetNcmContentMetaType(mode);
s32 written_entries;
s32 total_entries;
R_TRY(ncmContentMetaDatabaseList(&meta_db, &total_entries, &written_entries, records, MaxContentMetas * sizeof(*records), content_meta_type, 0, 0, UINT64_MAX, NcmContentInstallType_Full));
if (total_entries <= 0) {
return ResultBootImagePackageNotFound();
}
AMS_ASSERT(total_entries == written_entries);
/* Output is sorted, return the lowest valid exfat entry. */
if (total_entries > 1) {
for (size_t i = 0; i < size_t(total_entries); i++) {
u8 attr;
R_TRY(ncmContentMetaDatabaseGetAttributes(&meta_db, &records[i], &attr));
if (attr & NcmContentMetaAttribute_IncludesExFatDriver) {
*out_data_id = records[i].id;
return ResultSuccess();
}
}
}
/* If there's only one entry or no exfat entries, return that entry. */
*out_data_id = records[0].id;
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);
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
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, NcmStorageId_BuiltInSystem, GetBootImagePackageMountPath())) {
R_CONVERT(fs::ResultTargetNotFound, ResultBootImagePackageNotFound())
} R_END_TRY_CATCH;
ON_SCOPE_EXIT { R_ASSERT(romfsUnmount(GetBootImagePackageMountPath())); };
/* 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 ResultNeedsRepairBootImages();
}
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 ResultNeedsRepairBootImages();
}
/* 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 ResultNeedsRepairBootImages();
}
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 ResultNeedsRepairBootImages();
}
}
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, NcmStorageId_BuiltInSystem, GetBootImagePackageMountPath())) {
R_CONVERT(fs::ResultTargetNotFound, ResultBootImagePackageNotFound())
} R_END_TRY_CATCH;
ON_SCOPE_EXIT { R_ASSERT(romfsUnmount(GetBootImagePackageMountPath())); };
/* 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 ResultNeedsRepairBootImages();
}
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 ResultNeedsRepairBootImages();
}
/* 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 ResultNeedsRepairBootImages();
}
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 ResultNeedsRepairBootImages();
}
}
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);
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
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, NcmStorageId_BuiltInSystem, GetBootImagePackageMountPath())) {
R_CONVERT(fs::ResultTargetNotFound, ResultBootImagePackageNotFound())
} R_END_TRY_CATCH;
ON_SCOPE_EXIT { R_ASSERT(romfsUnmount(GetBootImagePackageMountPath())); };
{
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) && !exosphere::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, NcmStorageId_BuiltInSystem, GetBootImagePackageMountPath())) {
R_CONVERT(fs::ResultTargetNotFound, ResultBootImagePackageNotFound())
} R_END_TRY_CATCH;
ON_SCOPE_EXIT { R_ASSERT(romfsUnmount(GetBootImagePackageMountPath())); };
{
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) && !exosphere::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 ResultNeedsRepairBootImages();
}
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;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
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. */
sm::ScopedServiceHolder<ncmInitialize, ncmExit> ncm_holder;
R_ASSERT(ncm_holder.GetResult());
/* 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(ResultBootImagePackageNotFound) { /* 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(ResultBootImagePackageNotFound) { /* Nintendo considers failure to locate bip a success. TODO: don't do that? */ }
} R_END_TRY_CATCH;
}
return ResultSuccess();
}
}

View file

@ -0,0 +1,149 @@
/*
* 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 "updater_bis_management.hpp"
namespace ams::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) {
AMS_ASSERT((offset % SectorAlignment) == 0);
return fsStorageRead(&this->storage, offset, dst, size);
}
Result BisAccessor::Write(u64 offset, const void *src, size_t size) {
AMS_ASSERT((offset % SectorAlignment) == 0);
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) {
AMS_ASSERT((offset % SectorAlignment) == 0);
AMS_ASSERT((work_buffer_size % SectorAlignment) == 0);
FILE *bip_fp = fopen(bip_path, "rb");
if (bip_fp == NULL) {
return ResultInvalidBootImagePackage();
}
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();
}
}
AMS_ASSERT(written + read_size <= size);
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) {
AMS_ASSERT((offset % SectorAlignment) == 0);
AMS_ASSERT((work_buffer_size % SectorAlignment) == 0);
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) {
AMS_ASSERT((offset % SectorAlignment) == 0);
AMS_ASSERT((work_buffer_size % SectorAlignment) == 0);
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);
AMS_ASSERT(version <= BctVersionMax);
return static_cast<size_t>(version);
}
size_t Boot0Accessor::GetEksIndex(size_t bootloader_version) {
AMS_ASSERT(bootloader_version <= BctVersionMax);
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,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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::updater {
class BisAccessor {
public:
static constexpr size_t SectorAlignment = 0x200;
private:
FsStorage storage = {};
FsBisPartitionId partition_id;
bool active;
public:
BisAccessor(FsBisPartitionId 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(FsBisPartitionId id) : BisAccessor(id) { }
private:
constexpr const OffsetSizeType *FindEntry(EnumType which) {
const OffsetSizeType *entry = nullptr;
for (size_t i = 0; i < Meta::NumEntries; i++) {
if (Meta::Entries[i].which == which) {
entry = &Meta::Entries[i];
break;
}
}
AMS_ASSERT(entry != nullptr);
return entry;
}
public:
Result Read(size_t *out_size, void *dst, size_t size, EnumType which) {
const auto entry = FindEntry(which);
AMS_ASSERT(size >= entry->size);
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);
AMS_ASSERT(size <= entry->size);
AMS_ASSERT((size % BisAccessor::SectorAlignment) == 0);
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 FsBisPartitionId GetPackage2StorageId(Package2Type which) {
switch (which) {
case Package2Type::NormalMain:
return FsBisPartitionId_BootConfigAndPackage2Part1;
case Package2Type::NormalSub:
return FsBisPartitionId_BootConfigAndPackage2Part2;
case Package2Type::SafeMain:
return FsBisPartitionId_BootConfigAndPackage2Part3;
case Package2Type::SafeSub:
return FsBisPartitionId_BootConfigAndPackage2Part4;
case Package2Type::RepairMain:
return FsBisPartitionId_BootConfigAndPackage2Part5;
case Package2Type::RepairSub:
return FsBisPartitionId_BootConfigAndPackage2Part6;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
class Boot0Accessor : public PartitionAccessor<Boot0Meta> {
public:
static constexpr FsBisPartitionId PartitionId = FsBisPartitionId_BootPartition1Root;
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 FsBisPartitionId PartitionId = FsBisPartitionId_BootPartition2Root;
public:
Boot1Accessor() : PartitionAccessor<Boot1Meta>(PartitionId) { }
};
class Package2Accessor : public PartitionAccessor<Package2Meta> {
public:
Package2Accessor(Package2Type which) : PartitionAccessor<Package2Meta>(GetPackage2StorageId(which)) { }
};
}

View file

@ -0,0 +1,62 @@
/*
* 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 "updater_bis_save.hpp"
namespace ams::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) {
AMS_ASSERT(work_buffer_size >= SaveSize);
AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(work_buffer), os::MemoryPageSize));
AMS_ASSERT(util::IsAligned(work_buffer_size, 0x200));
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,42 @@
/*
* 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 "updater_bis_management.hpp"
namespace ams::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,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 "updater_files.hpp"
namespace ams::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 ResultInvalidBootImagePackage();
}
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 ResultInvalidBootImagePackage();
}
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,25 @@
/*
* 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 <stratosphere.hpp>
namespace ams::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,110 @@
/*
* 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 "updater_paths.hpp"
namespace ams::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) {
AMS_ASSERT(num_candidates > 0);
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));
}
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
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));
}
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
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));
}
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
}

View file

@ -0,0 +1,27 @@
/*
* 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 <stratosphere.hpp>
namespace ams::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 0x480
#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,41 @@
/*
* 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 "lz4.h"
namespace ams::util {
/* Compression utilities. */
int CompressLZ4(void *dst, size_t dst_size, const void *src, size_t src_size) {
/* Size checks. */
AMS_ASSERT(dst_size <= std::numeric_limits<int>::max());
AMS_ASSERT(src_size <= std::numeric_limits<int>::max());
/* 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. */
AMS_ASSERT(dst_size <= std::numeric_limits<int>::max());
AMS_ASSERT(src_size <= std::numeric_limits<int>::max());
/* 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,91 @@
/*
* 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 "ini.h"
namespace ams::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) {
s64 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, FsReadOption_None, &actually_read));
AMS_ASSERT(actually_read == try_read);
/* 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);
}
}