mirror of
https://github.com/Atmosphere-NX/Atmosphere-libs.git
synced 2025-06-30 23:12:13 +02:00
add map, patcher, ro to sts.
This commit is contained in:
parent
f575d06446
commit
1d81da1230
2
Makefile
2
Makefile
@ -16,7 +16,7 @@ include $(DEVKITPRO)/libnx/switch_rules
|
|||||||
# INCLUDES is a list of directories containing header files
|
# INCLUDES is a list of directories containing header files
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
TARGET := $(notdir $(CURDIR))
|
TARGET := $(notdir $(CURDIR))
|
||||||
SOURCES := source source/spl source/spl/smc source/updater
|
SOURCES := source source/spl source/spl/smc source/updater source/patcher source/map
|
||||||
DATA := data
|
DATA := data
|
||||||
INCLUDES := include
|
INCLUDES := include
|
||||||
|
|
||||||
|
21
include/stratosphere/map.hpp
Normal file
21
include/stratosphere/map.hpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#include "map/map_types.hpp"
|
||||||
|
#include "map/map_api.hpp"
|
30
include/stratosphere/map/map_api.hpp
Normal file
30
include/stratosphere/map/map_api.hpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#include "map_types.hpp"
|
||||||
|
|
||||||
|
namespace sts::map {
|
||||||
|
|
||||||
|
/* Public API. */
|
||||||
|
Result GetProcessAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h);
|
||||||
|
Result LocateMappableSpace(uintptr_t *out_address, size_t size);
|
||||||
|
Result MapCodeMemoryInProcess(MappedCodeMemory &out_mcm, Handle process_handle, uintptr_t base_address, size_t size);
|
||||||
|
bool CanAddGuardRegionsInProcess(Handle process_handle, uintptr_t address, size_t size);
|
||||||
|
|
||||||
|
}
|
116
include/stratosphere/map/map_types.hpp
Normal file
116
include/stratosphere/map/map_types.hpp
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include "../results.hpp"
|
||||||
|
|
||||||
|
namespace sts::map {
|
||||||
|
|
||||||
|
/* Types. */
|
||||||
|
struct AddressSpaceInfo {
|
||||||
|
uintptr_t heap_base;
|
||||||
|
size_t heap_size;
|
||||||
|
uintptr_t heap_end;
|
||||||
|
uintptr_t alias_base;
|
||||||
|
size_t alias_size;
|
||||||
|
uintptr_t alias_end;
|
||||||
|
uintptr_t aslr_base;
|
||||||
|
size_t aslr_size;
|
||||||
|
uintptr_t aslr_end;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AutoCloseMap {
|
||||||
|
private:
|
||||||
|
Handle process_handle;
|
||||||
|
Result result;
|
||||||
|
void *mapped_address;
|
||||||
|
uintptr_t base_address;
|
||||||
|
size_t size;
|
||||||
|
public:
|
||||||
|
AutoCloseMap(uintptr_t mp, Handle p_h, uintptr_t ba, size_t sz) : process_handle(p_h), mapped_address(reinterpret_cast<void *>(mp)), base_address(ba), size(sz) {
|
||||||
|
this->result = svcMapProcessMemory(this->mapped_address, this->process_handle, this->base_address, this->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
~AutoCloseMap() {
|
||||||
|
if (this->process_handle != INVALID_HANDLE && R_SUCCEEDED(this->result)) {
|
||||||
|
R_ASSERT(svcUnmapProcessMemory(this->mapped_address, this->process_handle, this->base_address, this->size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GetResult() const {
|
||||||
|
return this->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSuccess() const {
|
||||||
|
return R_SUCCEEDED(this->result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Invalidate() {
|
||||||
|
this->process_handle = INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MappedCodeMemory {
|
||||||
|
private:
|
||||||
|
Handle process_handle;
|
||||||
|
Result result;
|
||||||
|
uintptr_t dst_address;
|
||||||
|
uintptr_t src_address;
|
||||||
|
size_t size;
|
||||||
|
public:
|
||||||
|
MappedCodeMemory(Result init_res) : process_handle(INVALID_HANDLE), result(init_res), dst_address(0), src_address(0), size(0) {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
MappedCodeMemory(Handle p_h, uintptr_t dst, uintptr_t src, size_t sz) : process_handle(p_h), dst_address(dst), src_address(src), size(sz) {
|
||||||
|
this->result = svcMapProcessCodeMemory(this->process_handle, this->dst_address, this->src_address, this->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
~MappedCodeMemory() {
|
||||||
|
if (this->process_handle != INVALID_HANDLE && R_SUCCEEDED(this->result) && this->size > 0) {
|
||||||
|
R_ASSERT(svcUnmapProcessCodeMemory(this->process_handle, this->dst_address, this->src_address, this->size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t GetDstAddress() const {
|
||||||
|
return this->dst_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GetResult() const {
|
||||||
|
return this->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSuccess() const {
|
||||||
|
return R_SUCCEEDED(this->result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Invalidate() {
|
||||||
|
this->process_handle = INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
MappedCodeMemory &operator=(MappedCodeMemory &&o) {
|
||||||
|
this->process_handle = o.process_handle;
|
||||||
|
this->result = o.result;
|
||||||
|
this->dst_address = o.dst_address;
|
||||||
|
this->src_address = o.src_address;
|
||||||
|
this->size = o.size;
|
||||||
|
o.Invalidate();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
20
include/stratosphere/patcher.hpp
Normal file
20
include/stratosphere/patcher.hpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#include "patcher/patcher_api.hpp"
|
26
include/stratosphere/patcher/patcher_api.hpp
Normal file
26
include/stratosphere/patcher/patcher_api.hpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ro/ro_types.hpp"
|
||||||
|
|
||||||
|
namespace sts::patcher {
|
||||||
|
|
||||||
|
/* Helper for applying to code binaries. */
|
||||||
|
void LocateAndApplyIpsPatchesToModule(const char *patch_dir, size_t protected_size, size_t offset, const ro::ModuleId *module_id, u8 *mapped_module, size_t mapped_size);
|
||||||
|
|
||||||
|
}
|
20
include/stratosphere/ro.hpp
Normal file
20
include/stratosphere/ro.hpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#include "ro/ro_types.hpp"
|
152
include/stratosphere/ro/ro_types.hpp
Normal file
152
include/stratosphere/ro/ro_types.hpp
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
namespace sts::ro {
|
||||||
|
|
||||||
|
enum class ModuleType : u8 {
|
||||||
|
ForSelf = 0,
|
||||||
|
ForOthers = 1,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModuleId {
|
||||||
|
u8 build_id[0x20];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ModuleId) == sizeof(LoaderModuleInfo::build_id), "ModuleId definition!");
|
||||||
|
|
||||||
|
class NrrHeader {
|
||||||
|
public:
|
||||||
|
static constexpr u32 Magic = 0x3052524E;
|
||||||
|
private:
|
||||||
|
u32 magic;
|
||||||
|
u8 reserved_04[0xC];
|
||||||
|
u64 title_id_mask;
|
||||||
|
u64 title_id_pattern;
|
||||||
|
u8 reserved_20[0x10];
|
||||||
|
u8 modulus[0x100];
|
||||||
|
u8 fixed_key_signature[0x100];
|
||||||
|
u8 nrr_signature[0x100];
|
||||||
|
u64 title_id;
|
||||||
|
u32 size;
|
||||||
|
u8 type; /* 7.0.0+ */
|
||||||
|
u8 reserved_33D[3];
|
||||||
|
u32 hashes_offset;
|
||||||
|
u32 num_hashes;
|
||||||
|
u8 reserved_348[8];
|
||||||
|
public:
|
||||||
|
bool IsMagicValid() const {
|
||||||
|
return this->magic == Magic;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsTitleIdValid() const {
|
||||||
|
return (this->title_id & this->title_id_mask) == this->title_id_pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleType GetType() const {
|
||||||
|
const ModuleType type = static_cast<ModuleType>(this->type);
|
||||||
|
if (type >= ModuleType::Count) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GetTitleId() const {
|
||||||
|
return this->title_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetSize() const {
|
||||||
|
return this->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetNumHashes() const {
|
||||||
|
return this->num_hashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t GetHashes() const {
|
||||||
|
return reinterpret_cast<uintptr_t>(this) + this->hashes_offset;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NrrHeader) == 0x350, "NrrHeader definition!");
|
||||||
|
|
||||||
|
class NroHeader {
|
||||||
|
public:
|
||||||
|
static constexpr u32 Magic = 0x304F524E;
|
||||||
|
private:
|
||||||
|
u32 entrypoint_insn;
|
||||||
|
u32 mod_offset;
|
||||||
|
u8 reserved_08[0x8];
|
||||||
|
u32 magic;
|
||||||
|
u8 reserved_14[0x4];
|
||||||
|
u32 size;
|
||||||
|
u8 reserved_1C[0x4];
|
||||||
|
u32 text_offset;
|
||||||
|
u32 text_size;
|
||||||
|
u32 ro_offset;
|
||||||
|
u32 ro_size;
|
||||||
|
u32 rw_offset;
|
||||||
|
u32 rw_size;
|
||||||
|
u32 bss_size;
|
||||||
|
u8 reserved_3C[0x4];
|
||||||
|
ModuleId module_id;
|
||||||
|
u8 reserved_60[0x20];
|
||||||
|
public:
|
||||||
|
bool IsMagicValid() const {
|
||||||
|
return this->magic == Magic;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetSize() const {
|
||||||
|
return this->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetTextOffset() const {
|
||||||
|
return this->text_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetTextSize() const {
|
||||||
|
return this->text_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetRoOffset() const {
|
||||||
|
return this->ro_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetRoSize() const {
|
||||||
|
return this->ro_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetRwOffset() const {
|
||||||
|
return this->rw_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetRwSize() const {
|
||||||
|
return this->rw_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetBssSize() const {
|
||||||
|
return this->bss_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModuleId *GetModuleId() const {
|
||||||
|
return &this->module_id;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NroHeader) == 0x80, "NroHeader definition!");
|
||||||
|
|
||||||
|
}
|
@ -21,7 +21,9 @@
|
|||||||
namespace sts::spl {
|
namespace sts::spl {
|
||||||
|
|
||||||
HardwareType GetHardwareType();
|
HardwareType GetHardwareType();
|
||||||
bool IsRecoveryBoot();
|
bool IsDevelopmentHardware();
|
||||||
|
bool IsDevelopmentFunctionEnabled();
|
||||||
bool IsMariko();
|
bool IsMariko();
|
||||||
|
bool IsRecoveryBoot();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
243
source/map/map_api.cpp
Normal file
243
source/map/map_api.cpp
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include <stratosphere/map.hpp>
|
||||||
|
|
||||||
|
namespace sts::map {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/* Convenience defines. */
|
||||||
|
constexpr uintptr_t Deprecated64BitAslrBase = 0x08000000ul;
|
||||||
|
constexpr size_t Deprecated64BitAslrSize = 0x78000000ul;
|
||||||
|
constexpr uintptr_t Deprecated32BitAslrBase = 0x00200000ul;
|
||||||
|
constexpr size_t Deprecated32BitAslrSize = 0x3FE00000ul;
|
||||||
|
|
||||||
|
constexpr size_t GuardRegionSize = 0x4000;
|
||||||
|
constexpr size_t LocateRetryCount = 0x200;
|
||||||
|
|
||||||
|
/* Deprecated/Modern implementations. */
|
||||||
|
Result LocateMappableSpaceDeprecated(uintptr_t *out_address, size_t size) {
|
||||||
|
MemoryInfo mem_info = {};
|
||||||
|
u32 page_info = 0;
|
||||||
|
uintptr_t cur_base = 0;
|
||||||
|
|
||||||
|
AddressSpaceInfo address_space;
|
||||||
|
R_TRY(GetProcessAddressSpaceInfo(&address_space, CUR_PROCESS_HANDLE));
|
||||||
|
cur_base = address_space.aslr_base;
|
||||||
|
|
||||||
|
do {
|
||||||
|
R_TRY(svcQueryMemory(&mem_info, &page_info, cur_base));
|
||||||
|
|
||||||
|
if (mem_info.type == MemType_Unmapped && mem_info.addr - cur_base + mem_info.size >= size) {
|
||||||
|
*out_address = cur_base;
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uintptr_t mem_end = mem_info.addr + mem_info.size;
|
||||||
|
if (mem_info.type == MemType_Reserved || mem_end < cur_base || (mem_end >> 31)) {
|
||||||
|
return ResultKernelOutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_base = mem_end;
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LocateMappableSpaceModern(uintptr_t *out_address, size_t size) {
|
||||||
|
MemoryInfo mem_info = {};
|
||||||
|
u32 page_info = 0;
|
||||||
|
uintptr_t cur_base = 0, cur_end = 0;
|
||||||
|
|
||||||
|
AddressSpaceInfo address_space;
|
||||||
|
R_TRY(GetProcessAddressSpaceInfo(&address_space, CUR_PROCESS_HANDLE));
|
||||||
|
cur_base = address_space.aslr_base;
|
||||||
|
cur_end = cur_base + size;
|
||||||
|
|
||||||
|
if (cur_end <= cur_base) {
|
||||||
|
return ResultKernelOutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (address_space.heap_size && (address_space.heap_base <= cur_end - 1 && cur_base <= address_space.heap_end - 1)) {
|
||||||
|
/* If we overlap the heap region, go to the end of the heap region. */
|
||||||
|
if (cur_base == address_space.heap_end) {
|
||||||
|
return ResultKernelOutOfMemory;
|
||||||
|
}
|
||||||
|
cur_base = address_space.heap_end;
|
||||||
|
} else if (address_space.alias_size && (address_space.alias_base <= cur_end - 1 && cur_base <= address_space.alias_end - 1)) {
|
||||||
|
/* If we overlap the alias region, go to the end of the alias region. */
|
||||||
|
if (cur_base == address_space.alias_end) {
|
||||||
|
return ResultKernelOutOfMemory;
|
||||||
|
}
|
||||||
|
cur_base = address_space.alias_end;
|
||||||
|
} else {
|
||||||
|
R_ASSERT(svcQueryMemory(&mem_info, &page_info, cur_base));
|
||||||
|
if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= size) {
|
||||||
|
*out_address = cur_base;
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
if (mem_info.addr + mem_info.size <= cur_base) {
|
||||||
|
return ResultKernelOutOfMemory;
|
||||||
|
}
|
||||||
|
cur_base = mem_info.addr + mem_info.size;
|
||||||
|
if (cur_base >= address_space.aslr_end) {
|
||||||
|
return ResultKernelOutOfMemory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cur_end = cur_base + size;
|
||||||
|
if (cur_base + size <= cur_base) {
|
||||||
|
return ResultKernelOutOfMemory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result MapCodeMemoryInProcessDeprecated(MappedCodeMemory &out_mcm, Handle process_handle, uintptr_t base_address, size_t size) {
|
||||||
|
AddressSpaceInfo address_space;
|
||||||
|
R_TRY(GetProcessAddressSpaceInfo(&address_space, process_handle));
|
||||||
|
|
||||||
|
if (size > address_space.aslr_size) {
|
||||||
|
return ResultRoInsufficientAddressSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t try_address;
|
||||||
|
for (unsigned int i = 0; i < LocateRetryCount; i++) {
|
||||||
|
try_address = address_space.aslr_base + (StratosphereRandomUtils::GetRandomU64(static_cast<u64>(address_space.aslr_size - size) >> 12) << 12);
|
||||||
|
|
||||||
|
MappedCodeMemory tmp_mcm(process_handle, try_address, base_address, size);
|
||||||
|
R_TRY_CATCH(tmp_mcm.GetResult()) {
|
||||||
|
R_CATCH(ResultKernelInvalidMemoryState) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} R_END_TRY_CATCH;
|
||||||
|
|
||||||
|
if (!CanAddGuardRegionsInProcess(process_handle, try_address, size)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We're done searching. */
|
||||||
|
out_mcm = std::move(tmp_mcm);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultRoInsufficientAddressSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result MapCodeMemoryInProcessModern(MappedCodeMemory &out_mcm, Handle process_handle, uintptr_t base_address, size_t size) {
|
||||||
|
AddressSpaceInfo address_space;
|
||||||
|
R_TRY(GetProcessAddressSpaceInfo(&address_space, process_handle));
|
||||||
|
|
||||||
|
if (size > address_space.aslr_size) {
|
||||||
|
return ResultRoInsufficientAddressSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t try_address;
|
||||||
|
for (unsigned int i = 0; i < LocateRetryCount; i++) {
|
||||||
|
while (true) {
|
||||||
|
try_address = address_space.aslr_base + (StratosphereRandomUtils::GetRandomU64(static_cast<u64>(address_space.aslr_size - size) >> 12) << 12);
|
||||||
|
if (address_space.heap_size && (address_space.heap_base <= try_address + size - 1 && try_address <= address_space.heap_end - 1)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (address_space.alias_size && (address_space.alias_base <= try_address + size - 1 && try_address <= address_space.alias_end - 1)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
MappedCodeMemory tmp_mcm(process_handle, try_address, base_address, size);
|
||||||
|
R_TRY_CATCH(tmp_mcm.GetResult()) {
|
||||||
|
R_CATCH(ResultKernelInvalidMemoryState) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} R_END_TRY_CATCH;
|
||||||
|
|
||||||
|
if (!CanAddGuardRegionsInProcess(process_handle, try_address, size)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We're done searching. */
|
||||||
|
out_mcm = std::move(tmp_mcm);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultRoInsufficientAddressSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public API. */
|
||||||
|
Result GetProcessAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h) {
|
||||||
|
/* Clear output. */
|
||||||
|
std::memset(out, 0, sizeof(*out));
|
||||||
|
|
||||||
|
/* Retrieve info from kernel. */
|
||||||
|
R_TRY(svcGetInfo(&out->heap_base, InfoType_HeapRegionAddress, process_h, 0));
|
||||||
|
R_TRY(svcGetInfo(&out->heap_size, InfoType_HeapRegionSize, process_h, 0));
|
||||||
|
R_TRY(svcGetInfo(&out->alias_base, InfoType_AliasRegionAddress, process_h, 0));
|
||||||
|
R_TRY(svcGetInfo(&out->alias_size, InfoType_AliasRegionSize, process_h, 0));
|
||||||
|
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) {
|
||||||
|
R_TRY(svcGetInfo(&out->aslr_base, InfoType_AslrRegionAddress, process_h, 0));
|
||||||
|
R_TRY(svcGetInfo(&out->aslr_size, InfoType_AslrRegionSize, process_h, 0));
|
||||||
|
} else {
|
||||||
|
/* Auto-detect 32-bit vs 64-bit. */
|
||||||
|
if (out->heap_base < Deprecated64BitAslrBase || out->alias_base < Deprecated64BitAslrBase) {
|
||||||
|
out->aslr_base = Deprecated32BitAslrBase;
|
||||||
|
out->aslr_size = Deprecated32BitAslrSize;
|
||||||
|
} else {
|
||||||
|
out->aslr_base = Deprecated64BitAslrBase;
|
||||||
|
out->aslr_size = Deprecated64BitAslrSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out->heap_end = out->heap_base + out->heap_size;
|
||||||
|
out->alias_end = out->alias_base + out->alias_size;
|
||||||
|
out->aslr_end = out->aslr_base + out->aslr_size;
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LocateMappableSpace(uintptr_t *out_address, size_t size) {
|
||||||
|
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) {
|
||||||
|
return LocateMappableSpaceModern(out_address, size);
|
||||||
|
} else {
|
||||||
|
return LocateMappableSpaceDeprecated(out_address, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result MapCodeMemoryInProcess(MappedCodeMemory &out_mcm, Handle process_handle, uintptr_t base_address, size_t size) {
|
||||||
|
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) {
|
||||||
|
return MapCodeMemoryInProcessModern(out_mcm, process_handle, base_address, size);
|
||||||
|
} else {
|
||||||
|
return MapCodeMemoryInProcessDeprecated(out_mcm, process_handle, base_address, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanAddGuardRegionsInProcess(Handle process_handle, uintptr_t address, size_t size) {
|
||||||
|
MemoryInfo mem_info;
|
||||||
|
u32 page_info;
|
||||||
|
|
||||||
|
/* Nintendo doesn't validate SVC return values at all. */
|
||||||
|
/* TODO: Should we allow these to fail? */
|
||||||
|
R_ASSERT(svcQueryProcessMemory(&mem_info, &page_info, process_handle, address - 1));
|
||||||
|
if (mem_info.type == MemType_Unmapped && address - GuardRegionSize >= mem_info.addr) {
|
||||||
|
R_ASSERT(svcQueryProcessMemory(&mem_info, &page_info, process_handle, address + size));
|
||||||
|
return mem_info.type == MemType_Unmapped && address + size + GuardRegionSize <= mem_info.addr + mem_info.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
256
source/patcher/patcher_api.cpp
Normal file
256
source/patcher/patcher_api.cpp
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include <stratosphere/patcher.hpp>
|
||||||
|
|
||||||
|
/* IPS Patching adapted from Luma3DS (https://github.com/AuroraWright/Luma3DS/blob/master/sysmodules/loader/source/patcher.c) */
|
||||||
|
|
||||||
|
namespace sts::patcher {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/* Convenience definitions. */
|
||||||
|
constexpr const char IpsHeadMagic[5] = {'P', 'A', 'T', 'C', 'H'};
|
||||||
|
constexpr const char IpsTailMagic[3] = {'E', 'O', 'F'};
|
||||||
|
constexpr const char Ips32HeadMagic[5] = {'I', 'P', 'S', '3', '2'};
|
||||||
|
constexpr const char Ips32TailMagic[4] = {'E', 'E', 'O', 'F'};
|
||||||
|
constexpr const char *IpsFileExtension = ".ips";
|
||||||
|
constexpr size_t IpsFileExtensionLength = std::strlen(IpsFileExtension);
|
||||||
|
constexpr size_t ModuleIpsPatchLength = 2 * sizeof(ro::ModuleId) + IpsFileExtensionLength;
|
||||||
|
|
||||||
|
/* Helpers. */
|
||||||
|
inline u8 ConvertHexNybble(const char nybble) {
|
||||||
|
if ('0' <= nybble && nybble <= '9') {
|
||||||
|
return nybble - '0';
|
||||||
|
} else if ('a' <= nybble && nybble <= 'f') {
|
||||||
|
return nybble - 'a' + 0xa;
|
||||||
|
} else {
|
||||||
|
return nybble - 'A' + 0xA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParseModuleIdFromPath(ro::ModuleId *out_module_id, const char *name, size_t name_len, size_t extension_len) {
|
||||||
|
/* Validate name is hex module id. */
|
||||||
|
for (unsigned int i = 0; i < name_len - extension_len; i++) {
|
||||||
|
if (std::isxdigit(name[i]) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read module id from name. */
|
||||||
|
std::memset(out_module_id, 0, sizeof(*out_module_id));
|
||||||
|
for (unsigned int name_ofs = 0, id_ofs = 0; name_ofs < name_len - extension_len && id_ofs < sizeof(*out_module_id); id_ofs++) {
|
||||||
|
out_module_id->build_id[id_ofs] |= ConvertHexNybble(name[name_ofs++]) << 4;
|
||||||
|
out_module_id->build_id[id_ofs] |= ConvertHexNybble(name[name_ofs++]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MatchesModuleId(const char *name, size_t name_len, size_t extension_len, const ro::ModuleId *module_id) {
|
||||||
|
/* Get module id. */
|
||||||
|
ro::ModuleId module_id_from_name;
|
||||||
|
if (!ParseModuleIdFromPath(&module_id_from_name, name, name_len, extension_len)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::memcmp(&module_id_from_name, module_id, sizeof(*module_id)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsIpsTail(bool is_ips32, u8 *buffer) {
|
||||||
|
if (is_ips32) {
|
||||||
|
return std::memcmp(buffer, Ips32TailMagic, sizeof(Ips32TailMagic));
|
||||||
|
} else {
|
||||||
|
return std::memcmp(buffer, IpsTailMagic, sizeof(IpsTailMagic));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u32 GetIpsPatchOffset(bool is_ips32, u8 *buffer) {
|
||||||
|
if (is_ips32) {
|
||||||
|
return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | (buffer[3]);
|
||||||
|
} else {
|
||||||
|
return (buffer[0] << 16) | (buffer[1] << 8) | (buffer[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u32 GetIpsPatchSize(bool is_ips32, u8 *buffer) {
|
||||||
|
return (buffer[0] << 8) | (buffer[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyIpsPatch(u8 *mapped_module, size_t mapped_size, size_t protected_size, size_t offset, bool is_ips32, FILE *f_ips) {
|
||||||
|
/* Validate offset/protected size. */
|
||||||
|
if (offset > protected_size) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 buffer[sizeof(Ips32TailMagic)];
|
||||||
|
while (true) {
|
||||||
|
if (fread(buffer, is_ips32 ? sizeof(Ips32TailMagic) : sizeof(IpsTailMagic), 1, f_ips) != 1) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsIpsTail(is_ips32, buffer)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Offset of patch. */
|
||||||
|
u32 patch_offset = GetIpsPatchOffset(is_ips32, buffer);
|
||||||
|
|
||||||
|
/* Size of patch. */
|
||||||
|
if (fread(buffer, 2, 1, f_ips) != 1) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
u32 patch_size = GetIpsPatchSize(is_ips32, buffer);
|
||||||
|
|
||||||
|
/* Check for RLE encoding. */
|
||||||
|
if (patch_size == 0) {
|
||||||
|
/* Size of RLE. */
|
||||||
|
if (fread(buffer, 2, 1, f_ips) != 1) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 rle_size = (buffer[0] << 8) | (buffer[1]);
|
||||||
|
|
||||||
|
/* Value for RLE. */
|
||||||
|
if (fread(buffer, 1, 1, f_ips) != 1) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure we don't write to protected region. */
|
||||||
|
if (patch_offset < protected_size) {
|
||||||
|
if (patch_offset + rle_size > protected_size) {
|
||||||
|
const u32 diff = protected_size - patch_offset;
|
||||||
|
patch_offset += diff;
|
||||||
|
rle_size -= diff;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust offset, if relevant. */
|
||||||
|
patch_offset -= offset;
|
||||||
|
|
||||||
|
/* Apply patch. */
|
||||||
|
if (patch_offset + rle_size > mapped_size) {
|
||||||
|
rle_size = mapped_size - patch_offset;
|
||||||
|
}
|
||||||
|
std::memset(mapped_module + patch_offset, buffer[0], rle_size);
|
||||||
|
} else {
|
||||||
|
/* Ensure we don't write to protected region. */
|
||||||
|
if (patch_offset < protected_size) {
|
||||||
|
if (patch_offset + patch_size > protected_size) {
|
||||||
|
const u32 diff = protected_size - patch_offset;
|
||||||
|
patch_offset += diff;
|
||||||
|
patch_size -= diff;
|
||||||
|
fseek(f_ips, diff, SEEK_CUR);
|
||||||
|
} else {
|
||||||
|
fseek(f_ips, patch_size, SEEK_CUR);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust offset, if relevant. */
|
||||||
|
patch_offset -= offset;
|
||||||
|
|
||||||
|
/* Apply patch. */
|
||||||
|
u32 read_size = patch_size;
|
||||||
|
if (patch_offset + read_size > mapped_size) {
|
||||||
|
read_size = mapped_size - patch_offset;
|
||||||
|
}
|
||||||
|
if (fread(mapped_module + patch_offset, read_size, 1, f_ips) != 1) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
if (patch_size > read_size) {
|
||||||
|
fseek(f_ips, patch_size - read_size, SEEK_CUR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocateAndApplyIpsPatchesToModule(const char *patch_dir_name, size_t protected_size, size_t offset, const ro::ModuleId *module_id, u8 *mapped_module, size_t mapped_size) {
|
||||||
|
/* Inspect all patches from /atmosphere/<patch_dir>/<*>/<*>.ips */
|
||||||
|
char path[FS_MAX_PATH+1] = {0};
|
||||||
|
std::snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/%s", patch_dir_name);
|
||||||
|
|
||||||
|
DIR *patches_dir = opendir(path);
|
||||||
|
struct dirent *pdir_ent;
|
||||||
|
if (patches_dir != NULL) {
|
||||||
|
/* Iterate over the patches directory to find patch subdirectories. */
|
||||||
|
while ((pdir_ent = readdir(patches_dir)) != NULL) {
|
||||||
|
if (std::strcmp(pdir_ent->d_name, ".") == 0 || std::strcmp(pdir_ent->d_name, "..") == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/%s/%s", patch_dir_name, pdir_ent->d_name);
|
||||||
|
DIR *patch_dir = opendir(path);
|
||||||
|
struct dirent *ent;
|
||||||
|
if (patch_dir != NULL) {
|
||||||
|
/* Iterate over the patch subdirectory to find .ips patches. */
|
||||||
|
while ((ent = readdir(patch_dir)) != NULL) {
|
||||||
|
if (std::strcmp(ent->d_name, ".") == 0 || std::strcmp(ent->d_name, "..") == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t name_len = strlen(ent->d_name);
|
||||||
|
if (!(IpsFileExtensionLength < name_len && name_len <= ModuleIpsPatchLength)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((name_len & 1) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std::strcmp(ent->d_name + name_len - IpsFileExtensionLength, IpsFileExtension) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!MatchesModuleId(ent->d_name, name_len, IpsFileExtensionLength, module_id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/%s/%s/%s", patch_dir_name, pdir_ent->d_name, ent->d_name);
|
||||||
|
FILE *f_ips = fopen(path, "rb");
|
||||||
|
if (f_ips == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ON_SCOPE_EXIT { fclose(f_ips); };
|
||||||
|
|
||||||
|
u8 header[5];
|
||||||
|
if (fread(header, 5, 1, f_ips) == 1) {
|
||||||
|
if (std::memcmp(header, IpsHeadMagic, 5) == 0) {
|
||||||
|
ApplyIpsPatch(mapped_module, mapped_size, protected_size, offset, false, f_ips);
|
||||||
|
} else if (std::memcmp(header, Ips32HeadMagic, 5) == 0) {
|
||||||
|
ApplyIpsPatch(mapped_module, mapped_size, protected_size, offset, true, f_ips);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(f_ips);
|
||||||
|
}
|
||||||
|
closedir(patch_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(patches_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -25,6 +25,18 @@ namespace sts::spl {
|
|||||||
return static_cast<HardwareType>(out_val);
|
return static_cast<HardwareType>(out_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
bool IsRecoveryBoot() {
|
||||||
u64 val = 0;
|
u64 val = 0;
|
||||||
R_ASSERT(splGetConfig(SplConfigItem_IsRecoveryBoot, &val));
|
R_ASSERT(splGetConfig(SplConfigItem_IsRecoveryBoot, &val));
|
||||||
|
Loading…
Reference in New Issue
Block a user