mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-06-02 23:59:49 -04:00
git subrepo clone https://github.com/Atmosphere-NX/Atmosphere-libs libraries
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:
parent
28717bfd27
commit
0105455086
294 changed files with 29915 additions and 0 deletions
46
libraries/libstratosphere/source/ams/ams_bpc.c
Normal file
46
libraries/libstratosphere/source/ams/ams_bpc.c
Normal 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 } },
|
||||
);
|
||||
}
|
34
libraries/libstratosphere/source/ams/ams_bpc.h
Normal file
34
libraries/libstratosphere/source/ams/ams_bpc.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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
|
137
libraries/libstratosphere/source/ams/ams_emummc_api.cpp
Normal file
137
libraries/libstratosphere/source/ams/ams_emummc_api.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
176
libraries/libstratosphere/source/ams/ams_environment.cpp
Normal file
176
libraries/libstratosphere/source/ams/ams_environment.cpp
Normal 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();
|
||||
}
|
75
libraries/libstratosphere/source/ams/ams_exosphere_api.cpp
Normal file
75
libraries/libstratosphere/source/ams/ams_exosphere_api.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
349
libraries/libstratosphere/source/boot2/boot2_api.cpp
Normal file
349
libraries/libstratosphere/source/boot2/boot2_api.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
72
libraries/libstratosphere/source/cfg/cfg_flags.cpp
Normal file
72
libraries/libstratosphere/source/cfg/cfg_flags.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
308
libraries/libstratosphere/source/cfg/cfg_override.cpp
Normal file
308
libraries/libstratosphere/source/cfg/cfg_override.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
97
libraries/libstratosphere/source/cfg/cfg_sd_card.cpp
Normal file
97
libraries/libstratosphere/source/cfg/cfg_sd_card.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
55
libraries/libstratosphere/source/dd/dd_io_mappings.cpp
Normal file
55
libraries/libstratosphere/source/dd/dd_io_mappings.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
167
libraries/libstratosphere/source/dmnt/dmntcht.c
Normal file
167
libraries/libstratosphere/source/dmnt/dmntcht.c
Normal 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);
|
||||
}
|
93
libraries/libstratosphere/source/dmnt/dmntcht.h
Normal file
93
libraries/libstratosphere/source/dmnt/dmntcht.h
Normal 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
|
93
libraries/libstratosphere/source/fs/fs_file_storage.cpp
Normal file
93
libraries/libstratosphere/source/fs/fs_file_storage.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
248
libraries/libstratosphere/source/fs/fs_path_tool.cpp
Normal file
248
libraries/libstratosphere/source/fs/fs_path_tool.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
47
libraries/libstratosphere/source/fs/fs_path_utils.cpp
Normal file
47
libraries/libstratosphere/source/fs/fs_path_utils.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
118
libraries/libstratosphere/source/fssystem/fssystem_utility.cpp
Normal file
118
libraries/libstratosphere/source/fssystem/fssystem_utility.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
66
libraries/libstratosphere/source/hid/hid_api.cpp
Normal file
66
libraries/libstratosphere/source/hid/hid_api.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
147
libraries/libstratosphere/source/hos/hos_version_api.cpp
Normal file
147
libraries/libstratosphere/source/hos/hos_version_api.cpp
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <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));
|
||||
}
|
||||
|
||||
}
|
168
libraries/libstratosphere/source/kvdb/kvdb_archive.cpp
Normal file
168
libraries/libstratosphere/source/kvdb/kvdb_archive.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
48
libraries/libstratosphere/source/ldr/ldr_ams.c
Normal file
48
libraries/libstratosphere/source/ldr/ldr_ams.c
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <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);
|
||||
}
|
27
libraries/libstratosphere/source/ldr/ldr_ams.h
Normal file
27
libraries/libstratosphere/source/ldr/ldr_ams.h
Normal 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
|
54
libraries/libstratosphere/source/ldr/ldr_pm_api.cpp
Normal file
54
libraries/libstratosphere/source/ldr/ldr_pm_api.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
236
libraries/libstratosphere/source/map/map_api.cpp
Normal file
236
libraries/libstratosphere/source/map/map_api.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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 {
|
||||
/* ... */
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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>;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
105
libraries/libstratosphere/source/os/os_event.cpp
Normal file
105
libraries/libstratosphere/source/os/os_event.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
111
libraries/libstratosphere/source/os/os_interrupt_event.cpp
Normal file
111
libraries/libstratosphere/source/os/os_interrupt_event.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
247
libraries/libstratosphere/source/os/os_message_queue.cpp
Normal file
247
libraries/libstratosphere/source/os/os_message_queue.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
44
libraries/libstratosphere/source/os/os_process_handle.cpp
Normal file
44
libraries/libstratosphere/source/os/os_process_handle.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
85
libraries/libstratosphere/source/os/os_semaphore.cpp
Normal file
85
libraries/libstratosphere/source/os/os_semaphore.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
188
libraries/libstratosphere/source/os/os_system_event.cpp
Normal file
188
libraries/libstratosphere/source/os/os_system_event.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
116
libraries/libstratosphere/source/os/os_waitable_holder.cpp
Normal file
116
libraries/libstratosphere/source/os/os_waitable_holder.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
88
libraries/libstratosphere/source/os/os_waitable_manager.cpp
Normal file
88
libraries/libstratosphere/source/os/os_waitable_manager.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
235
libraries/libstratosphere/source/patcher/patcher_api.cpp
Normal file
235
libraries/libstratosphere/source/patcher/patcher_api.cpp
Normal file
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
92
libraries/libstratosphere/source/pm/pm_ams.c
Normal file
92
libraries/libstratosphere/source/pm/pm_ams.c
Normal 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;
|
||||
}
|
28
libraries/libstratosphere/source/pm/pm_ams.h
Normal file
28
libraries/libstratosphere/source/pm/pm_ams.h
Normal 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
|
32
libraries/libstratosphere/source/pm/pm_boot_mode_api.cpp
Normal file
32
libraries/libstratosphere/source/pm/pm_boot_mode_api.cpp
Normal 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());
|
||||
}
|
||||
|
||||
}
|
56
libraries/libstratosphere/source/pm/pm_dmnt_api.cpp
Normal file
56
libraries/libstratosphere/source/pm/pm_dmnt_api.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
93
libraries/libstratosphere/source/pm/pm_info_api.cpp
Normal file
93
libraries/libstratosphere/source/pm/pm_info_api.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
27
libraries/libstratosphere/source/pm/pm_shell_api.cpp
Normal file
27
libraries/libstratosphere/source/pm/pm_shell_api.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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) { /* ... */ }
|
||||
}
|
||||
|
||||
}
|
129
libraries/libstratosphere/source/rnd/rnd_api.cpp
Normal file
129
libraries/libstratosphere/source/rnd/rnd_api.cpp
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <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);
|
||||
}
|
||||
|
||||
}
|
64
libraries/libstratosphere/source/service_guard.h
Normal file
64
libraries/libstratosphere/source/service_guard.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
86
libraries/libstratosphere/source/sf/hipc/sf_hipc_api.cpp
Normal file
86
libraries/libstratosphere/source/sf/hipc/sf_hipc_api.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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()) {
|
||||
/* ... */
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
131
libraries/libstratosphere/source/sm/sm_ams.c
Normal file
131
libraries/libstratosphere/source/sm/sm_ams.c
Normal 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;
|
||||
}
|
35
libraries/libstratosphere/source/sm/sm_ams.h
Normal file
35
libraries/libstratosphere/source/sm/sm_ams.h
Normal 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
|
63
libraries/libstratosphere/source/sm/sm_api.cpp
Normal file
63
libraries/libstratosphere/source/sm/sm_api.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
40
libraries/libstratosphere/source/sm/sm_manager_api.cpp
Normal file
40
libraries/libstratosphere/source/sm/sm_manager_api.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
57
libraries/libstratosphere/source/sm/sm_mitm_api.cpp
Normal file
57
libraries/libstratosphere/source/sm/sm_mitm_api.cpp
Normal 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));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
42
libraries/libstratosphere/source/sm/sm_utils.cpp
Normal file
42
libraries/libstratosphere/source/sm/sm_utils.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
70
libraries/libstratosphere/source/sm/sm_utils.hpp
Normal file
70
libraries/libstratosphere/source/sm/sm_utils.hpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
}
|
49
libraries/libstratosphere/source/sm/smm_ams.c
Normal file
49
libraries/libstratosphere/source/sm/smm_ams.c
Normal 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);
|
||||
}
|
25
libraries/libstratosphere/source/sm/smm_ams.h
Normal file
25
libraries/libstratosphere/source/sm/smm_ams.h
Normal 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
|
378
libraries/libstratosphere/source/spl/smc/spl_smc.cpp
Normal file
378
libraries/libstratosphere/source/spl/smc/spl_smc.cpp
Normal 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]);
|
||||
}
|
||||
|
||||
}
|
75
libraries/libstratosphere/source/spl/spl_api.cpp
Normal file
75
libraries/libstratosphere/source/spl/spl_api.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
530
libraries/libstratosphere/source/updater/updater_api.cpp
Normal file
530
libraries/libstratosphere/source/updater/updater_api.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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)) { }
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
68
libraries/libstratosphere/source/updater/updater_files.cpp
Normal file
68
libraries/libstratosphere/source/updater/updater_files.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "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();
|
||||
}
|
||||
|
||||
}
|
25
libraries/libstratosphere/source/updater/updater_files.hpp
Normal file
25
libraries/libstratosphere/source/updater/updater_files.hpp
Normal 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);
|
||||
|
||||
}
|
110
libraries/libstratosphere/source/updater/updater_paths.cpp
Normal file
110
libraries/libstratosphere/source/updater/updater_paths.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
27
libraries/libstratosphere/source/updater/updater_paths.hpp
Normal file
27
libraries/libstratosphere/source/updater/updater_paths.hpp
Normal 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);
|
||||
|
||||
}
|
269
libraries/libstratosphere/source/util/ini.c
Normal file
269
libraries/libstratosphere/source/util/ini.c
Normal file
|
@ -0,0 +1,269 @@
|
|||
/* inih -- simple .INI file parser
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ini.h"
|
||||
|
||||
#if !INI_USE_STACK
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#define MAX_SECTION 72
|
||||
#define MAX_NAME 72
|
||||
|
||||
/* Used by ini_parse_string() to keep track of string parsing state. */
|
||||
typedef struct {
|
||||
const char* ptr;
|
||||
size_t num_left;
|
||||
} ini_parse_string_ctx;
|
||||
|
||||
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||
static char* rstrip(char* s)
|
||||
{
|
||||
char* p = s + strlen(s);
|
||||
while (p > s && isspace((unsigned char)(*--p)))
|
||||
*p = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Return pointer to first non-whitespace char in given string. */
|
||||
static char* lskip(const char* s)
|
||||
{
|
||||
while (*s && isspace((unsigned char)(*s)))
|
||||
s++;
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Return pointer to first char (of chars) or inline comment in given string,
|
||||
or pointer to null at end of string if neither found. Inline comment must
|
||||
be prefixed by a whitespace character to register as a comment. */
|
||||
static char* find_chars_or_comment(const char* s, const char* chars)
|
||||
{
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
int was_space = 0;
|
||||
while (*s && (!chars || !strchr(chars, *s)) &&
|
||||
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
|
||||
was_space = isspace((unsigned char)(*s));
|
||||
s++;
|
||||
}
|
||||
#else
|
||||
while (*s && (!chars || !strchr(chars, *s))) {
|
||||
s++;
|
||||
}
|
||||
#endif
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
||||
static char* strncpy0(char* dest, const char* src, size_t size)
|
||||
{
|
||||
strncpy(dest, src, size - 1);
|
||||
dest[size - 1] = '\0';
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||
void* user)
|
||||
{
|
||||
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||
#if INI_USE_STACK
|
||||
char line[INI_MAX_LINE];
|
||||
int max_line = INI_MAX_LINE;
|
||||
#else
|
||||
char* line;
|
||||
int max_line = INI_INITIAL_ALLOC;
|
||||
#endif
|
||||
#if INI_ALLOW_REALLOC
|
||||
char* new_line;
|
||||
int offset;
|
||||
#endif
|
||||
char section[MAX_SECTION] = "";
|
||||
char prev_name[MAX_NAME] = "";
|
||||
|
||||
char* start;
|
||||
char* end;
|
||||
char* name;
|
||||
char* value;
|
||||
int lineno = 0;
|
||||
int error = 0;
|
||||
|
||||
#if !INI_USE_STACK
|
||||
line = (char*)malloc(INI_INITIAL_ALLOC);
|
||||
if (!line) {
|
||||
return -2;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if INI_HANDLER_LINENO
|
||||
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
|
||||
#else
|
||||
#define HANDLER(u, s, n, v) handler(u, s, n, v)
|
||||
#endif
|
||||
|
||||
/* Scan through stream line by line */
|
||||
while (reader(line, max_line, stream) != NULL) {
|
||||
#if INI_ALLOW_REALLOC
|
||||
offset = strlen(line);
|
||||
while (offset == max_line - 1 && line[offset - 1] != '\n') {
|
||||
max_line *= 2;
|
||||
if (max_line > INI_MAX_LINE)
|
||||
max_line = INI_MAX_LINE;
|
||||
new_line = realloc(line, max_line);
|
||||
if (!new_line) {
|
||||
free(line);
|
||||
return -2;
|
||||
}
|
||||
line = new_line;
|
||||
if (reader(line + offset, max_line - offset, stream) == NULL)
|
||||
break;
|
||||
if (max_line >= INI_MAX_LINE)
|
||||
break;
|
||||
offset += strlen(line + offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
lineno++;
|
||||
|
||||
start = line;
|
||||
#if INI_ALLOW_BOM
|
||||
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
|
||||
(unsigned char)start[1] == 0xBB &&
|
||||
(unsigned char)start[2] == 0xBF) {
|
||||
start += 3;
|
||||
}
|
||||
#endif
|
||||
start = lskip(rstrip(start));
|
||||
|
||||
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
|
||||
/* Start-of-line comment */
|
||||
}
|
||||
#if INI_ALLOW_MULTILINE
|
||||
else if (*prev_name && *start && start > line) {
|
||||
/* Non-blank line with leading whitespace, treat as continuation
|
||||
of previous name's value (as per Python configparser). */
|
||||
if (!HANDLER(user, section, prev_name, start) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
#endif
|
||||
else if (*start == '[') {
|
||||
/* A "[section]" line */
|
||||
end = find_chars_or_comment(start + 1, "]");
|
||||
if (*end == ']') {
|
||||
*end = '\0';
|
||||
strncpy0(section, start + 1, sizeof(section));
|
||||
*prev_name = '\0';
|
||||
}
|
||||
else if (!error) {
|
||||
/* No ']' found on section line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
else if (*start) {
|
||||
/* Not a comment, must be a name[=:]value pair */
|
||||
end = find_chars_or_comment(start, "=:");
|
||||
if (*end == '=' || *end == ':') {
|
||||
*end = '\0';
|
||||
name = rstrip(start);
|
||||
value = end + 1;
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
end = find_chars_or_comment(value, NULL);
|
||||
if (*end)
|
||||
*end = '\0';
|
||||
#endif
|
||||
value = lskip(value);
|
||||
rstrip(value);
|
||||
|
||||
/* Valid name[=:]value pair found, call handler */
|
||||
strncpy0(prev_name, name, sizeof(prev_name));
|
||||
if (!HANDLER(user, section, name, value) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
else if (!error) {
|
||||
/* No '=' or ':' found on name[=:]value line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
|
||||
#if INI_STOP_ON_FIRST_ERROR
|
||||
if (error)
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !INI_USE_STACK
|
||||
free(line);
|
||||
#endif
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_file(FILE* file, ini_handler handler, void* user)
|
||||
{
|
||||
return ini_parse_stream((ini_reader)fgets, file, handler, user);
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse(const char* filename, ini_handler handler, void* user)
|
||||
{
|
||||
FILE* file;
|
||||
int error;
|
||||
|
||||
file = fopen(filename, "r");
|
||||
if (!file)
|
||||
return -1;
|
||||
error = ini_parse_file(file, handler, user);
|
||||
fclose(file);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* An ini_reader function to read the next line from a string buffer. This
|
||||
is the fgets() equivalent used by ini_parse_string(). */
|
||||
static char* ini_reader_string(char* str, int num, void* stream) {
|
||||
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
|
||||
const char* ctx_ptr = ctx->ptr;
|
||||
size_t ctx_num_left = ctx->num_left;
|
||||
char* strp = str;
|
||||
char c;
|
||||
|
||||
if (ctx_num_left == 0 || num < 2)
|
||||
return NULL;
|
||||
|
||||
while (num > 1 && ctx_num_left != 0) {
|
||||
c = *ctx_ptr++;
|
||||
ctx_num_left--;
|
||||
*strp++ = c;
|
||||
if (c == '\n')
|
||||
break;
|
||||
num--;
|
||||
}
|
||||
|
||||
*strp = '\0';
|
||||
ctx->ptr = ctx_ptr;
|
||||
ctx->num_left = ctx_num_left;
|
||||
return str;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_string(const char* string, ini_handler handler, void* user) {
|
||||
ini_parse_string_ctx ctx;
|
||||
|
||||
ctx.ptr = string;
|
||||
ctx.num_left = strlen(string);
|
||||
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
|
||||
user);
|
||||
}
|
130
libraries/libstratosphere/source/util/ini.h
Normal file
130
libraries/libstratosphere/source/util/ini.h
Normal file
|
@ -0,0 +1,130 @@
|
|||
/* inih -- simple .INI file parser
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __INI_H__
|
||||
#define __INI_H__
|
||||
|
||||
/* Make this header file easier to include in C++ code */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* Nonzero if ini_handler callback should accept lineno parameter. */
|
||||
#ifndef INI_HANDLER_LINENO
|
||||
#define INI_HANDLER_LINENO 0
|
||||
#endif
|
||||
|
||||
/* Typedef for prototype of handler function. */
|
||||
#if INI_HANDLER_LINENO
|
||||
typedef int (*ini_handler)(void* user, const char* section,
|
||||
const char* name, const char* value,
|
||||
int lineno);
|
||||
#else
|
||||
typedef int (*ini_handler)(void* user, const char* section,
|
||||
const char* name, const char* value);
|
||||
#endif
|
||||
|
||||
/* Typedef for prototype of fgets-style reader function. */
|
||||
typedef char* (*ini_reader)(char* str, int num, void* stream);
|
||||
|
||||
/* Parse given INI-style file. May have [section]s, name=value pairs
|
||||
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
||||
is "" if name=value pair parsed before any section heading. name:value
|
||||
pairs are also supported as a concession to Python's configparser.
|
||||
|
||||
For each name=value pair parsed, call handler function with given user
|
||||
pointer as well as section, name, and value (data only valid for duration
|
||||
of handler call). Handler should return nonzero on success, zero on error.
|
||||
|
||||
Returns 0 on success, line number of first error on parse error (doesn't
|
||||
stop on first error), -1 on file open error, or -2 on memory allocation
|
||||
error (only when INI_USE_STACK is zero).
|
||||
*/
|
||||
int ini_parse(const char* filename, ini_handler handler, void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
||||
close the file when it's finished -- the caller must do that. */
|
||||
int ini_parse_file(FILE* file, ini_handler handler, void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
|
||||
filename. Used for implementing custom or string-based I/O (see also
|
||||
ini_parse_string). */
|
||||
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||
void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
|
||||
instead of a file. Useful for parsing INI data from a network socket or
|
||||
already in memory. */
|
||||
int ini_parse_string(const char* string, ini_handler handler, void* user);
|
||||
|
||||
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
||||
configparser. If allowed, ini_parse() will call the handler with the same
|
||||
name for each subsequent line parsed. */
|
||||
#ifndef INI_ALLOW_MULTILINE
|
||||
#define INI_ALLOW_MULTILINE 1
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
||||
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
|
||||
#ifndef INI_ALLOW_BOM
|
||||
#define INI_ALLOW_BOM 1
|
||||
#endif
|
||||
|
||||
/* Chars that begin a start-of-line comment. Per Python configparser, allow
|
||||
both ; and # comments at the start of a line by default. */
|
||||
#ifndef INI_START_COMMENT_PREFIXES
|
||||
#define INI_START_COMMENT_PREFIXES ";#"
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow inline comments (with valid inline comment characters
|
||||
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
|
||||
Python 3.2+ configparser behaviour. */
|
||||
#ifndef INI_ALLOW_INLINE_COMMENTS
|
||||
#define INI_ALLOW_INLINE_COMMENTS 1
|
||||
#endif
|
||||
#ifndef INI_INLINE_COMMENT_PREFIXES
|
||||
#define INI_INLINE_COMMENT_PREFIXES ";"
|
||||
#endif
|
||||
|
||||
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
|
||||
#ifndef INI_USE_STACK
|
||||
#define INI_USE_STACK 1
|
||||
#endif
|
||||
|
||||
/* Maximum line length for any line in INI file (stack or heap). Note that
|
||||
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
|
||||
#ifndef INI_MAX_LINE
|
||||
#define INI_MAX_LINE 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__ */
|
1857
libraries/libstratosphere/source/util/lz4.c
Normal file
1857
libraries/libstratosphere/source/util/lz4.c
Normal file
File diff suppressed because it is too large
Load diff
569
libraries/libstratosphere/source/util/lz4.h
Normal file
569
libraries/libstratosphere/source/util/lz4.h
Normal file
|
@ -0,0 +1,569 @@
|
|||
/*
|
||||
* LZ4 - Fast LZ compression algorithm
|
||||
* Header File
|
||||
* Copyright (C) 2011-2017, Yann Collet.
|
||||
|
||||
BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact the author at :
|
||||
- LZ4 homepage : http://www.lz4.org
|
||||
- LZ4 source repository : https://github.com/lz4/lz4
|
||||
*/
|
||||
#if defined (__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef LZ4_H_2983827168210
|
||||
#define LZ4_H_2983827168210
|
||||
|
||||
/* --- Dependency --- */
|
||||
#include <stddef.h> /* size_t */
|
||||
|
||||
|
||||
/**
|
||||
Introduction
|
||||
|
||||
LZ4 is lossless compression algorithm, providing compression speed at 400 MB/s per core,
|
||||
scalable with multi-cores CPU. It features an extremely fast decoder, with speed in
|
||||
multiple GB/s per core, typically reaching RAM speed limits on multi-core systems.
|
||||
|
||||
The LZ4 compression library provides in-memory compression and decompression functions.
|
||||
Compression can be done in:
|
||||
- a single step (described as Simple Functions)
|
||||
- a single step, reusing a context (described in Advanced Functions)
|
||||
- unbounded multiple steps (described as Streaming compression)
|
||||
|
||||
lz4.h provides block compression functions. It gives full buffer control to user.
|
||||
Decompressing an lz4-compressed block also requires metadata (such as compressed size).
|
||||
Each application is free to encode such metadata in whichever way it wants.
|
||||
|
||||
An additional format, called LZ4 frame specification (doc/lz4_Frame_format.md),
|
||||
take care of encoding standard metadata alongside LZ4-compressed blocks.
|
||||
If your application requires interoperability, it's recommended to use it.
|
||||
A library is provided to take care of it, see lz4frame.h.
|
||||
*/
|
||||
|
||||
/*^***************************************************************
|
||||
* Export parameters
|
||||
*****************************************************************/
|
||||
/*
|
||||
* LZ4_DLL_EXPORT :
|
||||
* Enable exporting of functions when building a Windows DLL
|
||||
* LZ4LIB_VISIBILITY :
|
||||
* Control library symbols visibility.
|
||||
*/
|
||||
#ifndef LZ4LIB_VISIBILITY
|
||||
# if defined(__GNUC__) && (__GNUC__ >= 4)
|
||||
# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default")))
|
||||
# else
|
||||
# define LZ4LIB_VISIBILITY
|
||||
# endif
|
||||
#endif
|
||||
#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1)
|
||||
# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY
|
||||
#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1)
|
||||
# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
|
||||
#else
|
||||
# define LZ4LIB_API LZ4LIB_VISIBILITY
|
||||
#endif
|
||||
|
||||
/*------ Version ------*/
|
||||
#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */
|
||||
#define LZ4_VERSION_MINOR 8 /* for new (non-breaking) interface capabilities */
|
||||
#define LZ4_VERSION_RELEASE 2 /* for tweaks, bug-fixes, or development */
|
||||
|
||||
#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE)
|
||||
|
||||
#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE
|
||||
#define LZ4_QUOTE(str) #str
|
||||
#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str)
|
||||
#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION)
|
||||
|
||||
LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */
|
||||
LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; unseful to check dll version */
|
||||
|
||||
|
||||
/*-************************************
|
||||
* Tuning parameter
|
||||
**************************************/
|
||||
/*!
|
||||
* LZ4_MEMORY_USAGE :
|
||||
* Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.)
|
||||
* Increasing memory usage improves compression ratio
|
||||
* Reduced memory usage may improve speed, thanks to cache effect
|
||||
* Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache
|
||||
*/
|
||||
#ifndef LZ4_MEMORY_USAGE
|
||||
# define LZ4_MEMORY_USAGE 14
|
||||
#endif
|
||||
|
||||
/*-************************************
|
||||
* Simple Functions
|
||||
**************************************/
|
||||
/*! LZ4_compress_default() :
|
||||
Compresses 'srcSize' bytes from buffer 'src'
|
||||
into already allocated 'dst' buffer of size 'dstCapacity'.
|
||||
Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize).
|
||||
It also runs faster, so it's a recommended setting.
|
||||
If the function cannot compress 'src' into a more limited 'dst' budget,
|
||||
compression stops *immediately*, and the function result is zero.
|
||||
Note : as a consequence, 'dst' content is not valid.
|
||||
Note 2 : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer).
|
||||
srcSize : max supported value is LZ4_MAX_INPUT_SIZE.
|
||||
dstCapacity : size of buffer 'dst' (which must be already allocated)
|
||||
return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity)
|
||||
or 0 if compression fails */
|
||||
LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity);
|
||||
|
||||
/*! LZ4_decompress_safe() :
|
||||
compressedSize : is the exact complete size of the compressed block.
|
||||
dstCapacity : is the size of destination buffer, which must be already allocated.
|
||||
return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity)
|
||||
If destination buffer is not large enough, decoding will stop and output an error code (negative value).
|
||||
If the source stream is detected malformed, the function will stop decoding and return a negative result.
|
||||
This function is protected against malicious data packets.
|
||||
*/
|
||||
LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity);
|
||||
|
||||
|
||||
/*-************************************
|
||||
* Advanced Functions
|
||||
**************************************/
|
||||
#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */
|
||||
#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16)
|
||||
|
||||
/*!
|
||||
LZ4_compressBound() :
|
||||
Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible)
|
||||
This function is primarily useful for memory allocation purposes (destination buffer size).
|
||||
Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example).
|
||||
Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize)
|
||||
inputSize : max supported value is LZ4_MAX_INPUT_SIZE
|
||||
return : maximum output size in a "worst case" scenario
|
||||
or 0, if input size is incorrect (too large or negative)
|
||||
*/
|
||||
LZ4LIB_API int LZ4_compressBound(int inputSize);
|
||||
|
||||
/*!
|
||||
LZ4_compress_fast() :
|
||||
Same as LZ4_compress_default(), but allows selection of "acceleration" factor.
|
||||
The larger the acceleration value, the faster the algorithm, but also the lesser the compression.
|
||||
It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed.
|
||||
An acceleration value of "1" is the same as regular LZ4_compress_default()
|
||||
Values <= 0 will be replaced by ACCELERATION_DEFAULT (currently == 1, see lz4.c).
|
||||
*/
|
||||
LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
|
||||
|
||||
|
||||
/*!
|
||||
LZ4_compress_fast_extState() :
|
||||
Same compression function, just using an externally allocated memory space to store compression state.
|
||||
Use LZ4_sizeofState() to know how much memory must be allocated,
|
||||
and allocate it on 8-bytes boundaries (using malloc() typically).
|
||||
Then, provide it as 'void* state' to compression function.
|
||||
*/
|
||||
LZ4LIB_API int LZ4_sizeofState(void);
|
||||
LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
|
||||
|
||||
|
||||
/*!
|
||||
LZ4_compress_destSize() :
|
||||
Reverse the logic : compresses as much data as possible from 'src' buffer
|
||||
into already allocated buffer 'dst' of size 'targetDestSize'.
|
||||
This function either compresses the entire 'src' content into 'dst' if it's large enough,
|
||||
or fill 'dst' buffer completely with as much data as possible from 'src'.
|
||||
*srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'.
|
||||
New value is necessarily <= old value.
|
||||
return : Nb bytes written into 'dst' (necessarily <= targetDestSize)
|
||||
or 0 if compression fails
|
||||
*/
|
||||
LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize);
|
||||
|
||||
|
||||
/*!
|
||||
LZ4_decompress_fast() : **unsafe!**
|
||||
This function is a bit faster than LZ4_decompress_safe(),
|
||||
but doesn't provide any security guarantee.
|
||||
originalSize : is the uncompressed size to regenerate
|
||||
Destination buffer must be already allocated, and its size must be >= 'originalSize' bytes.
|
||||
return : number of bytes read from source buffer (== compressed size).
|
||||
If the source stream is detected malformed, the function stops decoding and return a negative result.
|
||||
note : This function respects memory boundaries for *properly formed* compressed data.
|
||||
However, it does not provide any protection against malicious input.
|
||||
It also doesn't know 'src' size, and implies it's >= compressed size.
|
||||
Use this function in trusted environment **only**.
|
||||
*/
|
||||
LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize);
|
||||
|
||||
/*!
|
||||
LZ4_decompress_safe_partial() :
|
||||
This function decompress a compressed block of size 'srcSize' at position 'src'
|
||||
into destination buffer 'dst' of size 'dstCapacity'.
|
||||
The function will decompress a minimum of 'targetOutputSize' bytes, and stop after that.
|
||||
However, it's not accurate, and may write more than 'targetOutputSize' (but always <= dstCapacity).
|
||||
@return : the number of bytes decoded in the destination buffer (necessarily <= dstCapacity)
|
||||
Note : this number can also be < targetOutputSize, if compressed block contains less data.
|
||||
Therefore, always control how many bytes were decoded.
|
||||
If source stream is detected malformed, function returns a negative result.
|
||||
This function is protected against malicious data packets.
|
||||
*/
|
||||
LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity);
|
||||
|
||||
|
||||
/*-*********************************************
|
||||
* Streaming Compression Functions
|
||||
***********************************************/
|
||||
typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */
|
||||
|
||||
/*! LZ4_createStream() and LZ4_freeStream() :
|
||||
* LZ4_createStream() will allocate and initialize an `LZ4_stream_t` structure.
|
||||
* LZ4_freeStream() releases its memory.
|
||||
*/
|
||||
LZ4LIB_API LZ4_stream_t* LZ4_createStream(void);
|
||||
LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr);
|
||||
|
||||
/*! LZ4_resetStream() :
|
||||
* An LZ4_stream_t structure can be allocated once and re-used multiple times.
|
||||
* Use this function to start compressing a new stream.
|
||||
*/
|
||||
LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr);
|
||||
|
||||
/*! LZ4_loadDict() :
|
||||
* Use this function to load a static dictionary into LZ4_stream_t.
|
||||
* Any previous data will be forgotten, only 'dictionary' will remain in memory.
|
||||
* Loading a size of 0 is allowed, and is the same as reset.
|
||||
* @return : dictionary size, in bytes (necessarily <= 64 KB)
|
||||
*/
|
||||
LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize);
|
||||
|
||||
/*! LZ4_compress_fast_continue() :
|
||||
* Compress 'src' content using data from previously compressed blocks, for better compression ratio.
|
||||
* 'dst' buffer must be already allocated.
|
||||
* If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster.
|
||||
*
|
||||
* Important : The previous 64KB of compressed data is assumed to remain present and unmodified in memory!
|
||||
*
|
||||
* Special 1 : When input is a double-buffer, they can have any size, including < 64 KB.
|
||||
* Make sure that buffers are separated by at least one byte.
|
||||
* This way, each block only depends on previous block.
|
||||
* Special 2 : If input buffer is a ring-buffer, it can have any size, including < 64 KB.
|
||||
*
|
||||
* @return : size of compressed block
|
||||
* or 0 if there is an error (typically, cannot fit into 'dst').
|
||||
* After an error, the stream status is invalid, it can only be reset or freed.
|
||||
*/
|
||||
LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
|
||||
|
||||
/*! LZ4_saveDict() :
|
||||
* If last 64KB data cannot be guaranteed to remain available at its current memory location,
|
||||
* save it into a safer place (char* safeBuffer).
|
||||
* This is schematically equivalent to a memcpy() followed by LZ4_loadDict(),
|
||||
* but is much faster, because LZ4_saveDict() doesn't need to rebuild tables.
|
||||
* @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error.
|
||||
*/
|
||||
LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize);
|
||||
|
||||
|
||||
/*-**********************************************
|
||||
* Streaming Decompression Functions
|
||||
* Bufferless synchronous API
|
||||
************************************************/
|
||||
typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* incomplete type (defined later) */
|
||||
|
||||
/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() :
|
||||
* creation / destruction of streaming decompression tracking structure.
|
||||
* A tracking structure can be re-used multiple times sequentially. */
|
||||
LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void);
|
||||
LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream);
|
||||
|
||||
/*! LZ4_setStreamDecode() :
|
||||
* An LZ4_streamDecode_t structure can be allocated once and re-used multiple times.
|
||||
* Use this function to start decompression of a new stream of blocks.
|
||||
* A dictionary can optionnally be set. Use NULL or size 0 for a reset order.
|
||||
* @return : 1 if OK, 0 if error
|
||||
*/
|
||||
LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize);
|
||||
|
||||
/*! LZ4_decompress_*_continue() :
|
||||
* These decoding functions allow decompression of consecutive blocks in "streaming" mode.
|
||||
* A block is an unsplittable entity, it must be presented entirely to a decompression function.
|
||||
* Decompression functions only accept one block at a time.
|
||||
* The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded.
|
||||
* If less than 64KB of data has been decoded all the data must be present.
|
||||
*
|
||||
* Special : if application sets a ring buffer for decompression, it must respect one of the following conditions :
|
||||
* - Exactly same size as encoding buffer, with same update rule (block boundaries at same positions)
|
||||
* In which case, the decoding & encoding ring buffer can have any size, including very small ones ( < 64 KB).
|
||||
* - Larger than encoding buffer, by a minimum of maxBlockSize more bytes.
|
||||
* maxBlockSize is implementation dependent. It's the maximum size of any single block.
|
||||
* In which case, encoding and decoding buffers do not need to be synchronized,
|
||||
* and encoding ring buffer can have any size, including small ones ( < 64 KB).
|
||||
* - _At least_ 64 KB + 8 bytes + maxBlockSize.
|
||||
* In which case, encoding and decoding buffers do not need to be synchronized,
|
||||
* and encoding ring buffer can have any size, including larger than decoding buffer.
|
||||
* Whenever these conditions are not possible, save the last 64KB of decoded data into a safe buffer,
|
||||
* and indicate where it is saved using LZ4_setStreamDecode() before decompressing next block.
|
||||
*/
|
||||
LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity);
|
||||
LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize);
|
||||
|
||||
|
||||
/*! LZ4_decompress_*_usingDict() :
|
||||
* These decoding functions work the same as
|
||||
* a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue()
|
||||
* They are stand-alone, and don't need an LZ4_streamDecode_t structure.
|
||||
*/
|
||||
LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize);
|
||||
LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize);
|
||||
|
||||
|
||||
/*^**********************************************
|
||||
* !!!!!! STATIC LINKING ONLY !!!!!!
|
||||
***********************************************/
|
||||
|
||||
/*-************************************
|
||||
* Unstable declarations
|
||||
**************************************
|
||||
* Declarations in this section should be considered unstable.
|
||||
* Use at your own peril, etc., etc.
|
||||
* They may be removed in the future.
|
||||
* Their signatures may change.
|
||||
**************************************/
|
||||
|
||||
#ifdef LZ4_STATIC_LINKING_ONLY
|
||||
|
||||
/*! LZ4_resetStream_fast() :
|
||||
* When an LZ4_stream_t is known to be in a internally coherent state,
|
||||
* it can often be prepared for a new compression with almost no work, only
|
||||
* sometimes falling back to the full, expensive reset that is always required
|
||||
* when the stream is in an indeterminate state (i.e., the reset performed by
|
||||
* LZ4_resetStream()).
|
||||
*
|
||||
* LZ4_streams are guaranteed to be in a valid state when:
|
||||
* - returned from LZ4_createStream()
|
||||
* - reset by LZ4_resetStream()
|
||||
* - memset(stream, 0, sizeof(LZ4_stream_t))
|
||||
* - the stream was in a valid state and was reset by LZ4_resetStream_fast()
|
||||
* - the stream was in a valid state and was then used in any compression call
|
||||
* that returned success
|
||||
* - the stream was in an indeterminate state and was used in a compression
|
||||
* call that fully reset the state (LZ4_compress_fast_extState()) and that
|
||||
* returned success
|
||||
*/
|
||||
LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr);
|
||||
|
||||
/*! LZ4_compress_fast_extState_fastReset() :
|
||||
* A variant of LZ4_compress_fast_extState().
|
||||
*
|
||||
* Using this variant avoids an expensive initialization step. It is only safe
|
||||
* to call if the state buffer is known to be correctly initialized already
|
||||
* (see above comment on LZ4_resetStream_fast() for a definition of "correctly
|
||||
* initialized"). From a high level, the difference is that this function
|
||||
* initializes the provided state with a call to LZ4_resetStream_fast() while
|
||||
* LZ4_compress_fast_extState() starts with a call to LZ4_resetStream().
|
||||
*/
|
||||
LZ4LIB_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
|
||||
|
||||
/*! LZ4_attach_dictionary() :
|
||||
* This is an experimental API that allows for the efficient use of a
|
||||
* static dictionary many times.
|
||||
*
|
||||
* Rather than re-loading the dictionary buffer into a working context before
|
||||
* each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a
|
||||
* working LZ4_stream_t, this function introduces a no-copy setup mechanism,
|
||||
* in which the working stream references the dictionary stream in-place.
|
||||
*
|
||||
* Several assumptions are made about the state of the dictionary stream.
|
||||
* Currently, only streams which have been prepared by LZ4_loadDict() should
|
||||
* be expected to work.
|
||||
*
|
||||
* Alternatively, the provided dictionary stream pointer may be NULL, in which
|
||||
* case any existing dictionary stream is unset.
|
||||
*
|
||||
* If a dictionary is provided, it replaces any pre-existing stream history.
|
||||
* The dictionary contents are the only history that can be referenced and
|
||||
* logically immediately precede the data compressed in the first subsequent
|
||||
* compression call.
|
||||
*
|
||||
* The dictionary will only remain attached to the working stream through the
|
||||
* first compression call, at the end of which it is cleared. The dictionary
|
||||
* stream (and source buffer) must remain in-place / accessible / unchanged
|
||||
* through the completion of the first compression call on the stream.
|
||||
*/
|
||||
LZ4LIB_API void LZ4_attach_dictionary(LZ4_stream_t *working_stream, const LZ4_stream_t *dictionary_stream);
|
||||
|
||||
#endif
|
||||
|
||||
/*-************************************
|
||||
* Private definitions
|
||||
**************************************
|
||||
* Do not use these definitions.
|
||||
* They are exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`.
|
||||
* Using these definitions will expose code to API and/or ABI break in future versions of the library.
|
||||
**************************************/
|
||||
#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2)
|
||||
#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE)
|
||||
#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */
|
||||
|
||||
#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
|
||||
struct LZ4_stream_t_internal {
|
||||
uint32_t hashTable[LZ4_HASH_SIZE_U32];
|
||||
uint32_t currentOffset;
|
||||
uint16_t initCheck;
|
||||
uint16_t tableType;
|
||||
const uint8_t* dictionary;
|
||||
const LZ4_stream_t_internal* dictCtx;
|
||||
uint32_t dictSize;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const uint8_t* externalDict;
|
||||
size_t extDictSize;
|
||||
const uint8_t* prefixEnd;
|
||||
size_t prefixSize;
|
||||
} LZ4_streamDecode_t_internal;
|
||||
|
||||
#else
|
||||
|
||||
typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
|
||||
struct LZ4_stream_t_internal {
|
||||
unsigned int hashTable[LZ4_HASH_SIZE_U32];
|
||||
unsigned int currentOffset;
|
||||
unsigned short initCheck;
|
||||
unsigned short tableType;
|
||||
const unsigned char* dictionary;
|
||||
const LZ4_stream_t_internal* dictCtx;
|
||||
unsigned int dictSize;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const unsigned char* externalDict;
|
||||
size_t extDictSize;
|
||||
const unsigned char* prefixEnd;
|
||||
size_t prefixSize;
|
||||
} LZ4_streamDecode_t_internal;
|
||||
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* LZ4_stream_t :
|
||||
* information structure to track an LZ4 stream.
|
||||
* init this structure before first use.
|
||||
* note : only use in association with static linking !
|
||||
* this definition is not API/ABI safe,
|
||||
* it may change in a future version !
|
||||
*/
|
||||
#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4)
|
||||
#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long))
|
||||
union LZ4_stream_u {
|
||||
unsigned long long table[LZ4_STREAMSIZE_U64];
|
||||
LZ4_stream_t_internal internal_donotuse;
|
||||
} ; /* previously typedef'd to LZ4_stream_t */
|
||||
|
||||
|
||||
/*!
|
||||
* LZ4_streamDecode_t :
|
||||
* information structure to track an LZ4 stream during decompression.
|
||||
* init this structure using LZ4_setStreamDecode (or memset()) before first use
|
||||
* note : only use in association with static linking !
|
||||
* this definition is not API/ABI safe,
|
||||
* and may change in a future version !
|
||||
*/
|
||||
#define LZ4_STREAMDECODESIZE_U64 4
|
||||
#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long))
|
||||
union LZ4_streamDecode_u {
|
||||
unsigned long long table[LZ4_STREAMDECODESIZE_U64];
|
||||
LZ4_streamDecode_t_internal internal_donotuse;
|
||||
} ; /* previously typedef'd to LZ4_streamDecode_t */
|
||||
|
||||
|
||||
/*-************************************
|
||||
* Obsolete Functions
|
||||
**************************************/
|
||||
|
||||
/*! Deprecation warnings
|
||||
Should deprecation warnings be a problem,
|
||||
it is generally possible to disable them,
|
||||
typically with -Wno-deprecated-declarations for gcc
|
||||
or _CRT_SECURE_NO_WARNINGS in Visual.
|
||||
Otherwise, it's also possible to define LZ4_DISABLE_DEPRECATE_WARNINGS */
|
||||
#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS
|
||||
# define LZ4_DEPRECATED(message) /* disable deprecation warnings */
|
||||
#else
|
||||
# define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
|
||||
# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */
|
||||
# define LZ4_DEPRECATED(message) [[deprecated(message)]]
|
||||
# elif (LZ4_GCC_VERSION >= 405) || defined(__clang__)
|
||||
# define LZ4_DEPRECATED(message) __attribute__((deprecated(message)))
|
||||
# elif (LZ4_GCC_VERSION >= 301)
|
||||
# define LZ4_DEPRECATED(message) __attribute__((deprecated))
|
||||
# elif defined(_MSC_VER)
|
||||
# define LZ4_DEPRECATED(message) __declspec(deprecated(message))
|
||||
# else
|
||||
# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler")
|
||||
# define LZ4_DEPRECATED(message)
|
||||
# endif
|
||||
#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */
|
||||
|
||||
/* Obsolete compression functions */
|
||||
LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* source, char* dest, int sourceSize);
|
||||
LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* source, char* dest, int sourceSize, int maxOutputSize);
|
||||
LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize);
|
||||
LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize);
|
||||
LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize);
|
||||
LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize);
|
||||
|
||||
/* Obsolete decompression functions */
|
||||
LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize);
|
||||
LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize);
|
||||
|
||||
/* Obsolete streaming functions; degraded functionality; do not use!
|
||||
*
|
||||
* In order to perform streaming compression, these functions depended on data
|
||||
* that is no longer tracked in the state. They have been preserved as well as
|
||||
* possible: using them will still produce a correct output. However, they don't
|
||||
* actually retain any history between compression calls. The compression ratio
|
||||
* achieved will therefore be no better than compressing each chunk
|
||||
* independently.
|
||||
*/
|
||||
LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer);
|
||||
LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void);
|
||||
LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer);
|
||||
LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state);
|
||||
|
||||
/* Obsolete streaming decoding functions */
|
||||
LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize);
|
||||
LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize);
|
||||
|
||||
#endif /* LZ4_H_2983827168210 */
|
||||
|
||||
|
||||
#if defined (__cplusplus)
|
||||
}
|
||||
#endif
|
41
libraries/libstratosphere/source/util/util_compression.cpp
Normal file
41
libraries/libstratosphere/source/util/util_compression.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
91
libraries/libstratosphere/source/util/util_ini.cpp
Normal file
91
libraries/libstratosphere/source/util/util_ini.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue