diff --git a/Makefile b/Makefile
index 656c07eb..6bf7befb 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ include $(DEVKITPRO)/libnx/switch_rules
# INCLUDES is a list of directories containing header files
#---------------------------------------------------------------------------------
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
INCLUDES := include
diff --git a/include/stratosphere/map.hpp b/include/stratosphere/map.hpp
new file mode 100644
index 00000000..0d9d8ba9
--- /dev/null
+++ b/include/stratosphere/map.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+
+#include "map/map_types.hpp"
+#include "map/map_api.hpp"
diff --git a/include/stratosphere/map/map_api.hpp b/include/stratosphere/map/map_api.hpp
new file mode 100644
index 00000000..75065447
--- /dev/null
+++ b/include/stratosphere/map/map_api.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+
+#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);
+
+}
diff --git a/include/stratosphere/map/map_types.hpp b/include/stratosphere/map/map_types.hpp
new file mode 100644
index 00000000..f874696b
--- /dev/null
+++ b/include/stratosphere/map/map_types.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#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(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;
+ }
+ };
+
+}
diff --git a/include/stratosphere/patcher.hpp b/include/stratosphere/patcher.hpp
new file mode 100644
index 00000000..0e11d4c4
--- /dev/null
+++ b/include/stratosphere/patcher.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+
+#include "patcher/patcher_api.hpp"
diff --git a/include/stratosphere/patcher/patcher_api.hpp b/include/stratosphere/patcher/patcher_api.hpp
new file mode 100644
index 00000000..5be0f589
--- /dev/null
+++ b/include/stratosphere/patcher/patcher_api.hpp
@@ -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 .
+ */
+
+#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);
+
+}
diff --git a/include/stratosphere/ro.hpp b/include/stratosphere/ro.hpp
new file mode 100644
index 00000000..42239e95
--- /dev/null
+++ b/include/stratosphere/ro.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+
+#include "ro/ro_types.hpp"
diff --git a/include/stratosphere/ro/ro_types.hpp b/include/stratosphere/ro/ro_types.hpp
new file mode 100644
index 00000000..0a6a0189
--- /dev/null
+++ b/include/stratosphere/ro/ro_types.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+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(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(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!");
+
+}
diff --git a/include/stratosphere/spl/spl_api.hpp b/include/stratosphere/spl/spl_api.hpp
index 62291f0d..d24a69c5 100644
--- a/include/stratosphere/spl/spl_api.hpp
+++ b/include/stratosphere/spl/spl_api.hpp
@@ -21,7 +21,9 @@
namespace sts::spl {
HardwareType GetHardwareType();
- bool IsRecoveryBoot();
+ bool IsDevelopmentHardware();
+ bool IsDevelopmentFunctionEnabled();
bool IsMariko();
+ bool IsRecoveryBoot();
}
diff --git a/source/map/map_api.cpp b/source/map/map_api.cpp
new file mode 100644
index 00000000..0281f940
--- /dev/null
+++ b/source/map/map_api.cpp
@@ -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 .
+ */
+
+#include
+#include
+#include
+
+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(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(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;
+ }
+
+}
diff --git a/source/patcher/patcher_api.cpp b/source/patcher/patcher_api.cpp
new file mode 100644
index 00000000..083d265f
--- /dev/null
+++ b/source/patcher/patcher_api.cpp
@@ -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 .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+/* 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//<*>/<*>.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);
+ }
+ }
+
+}
diff --git a/source/spl/spl_api.cpp b/source/spl/spl_api.cpp
index 9d0cb604..f3aea6a7 100644
--- a/source/spl/spl_api.cpp
+++ b/source/spl/spl_api.cpp
@@ -25,6 +25,18 @@ namespace sts::spl {
return static_cast(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() {
u64 val = 0;
R_ASSERT(splGetConfig(SplConfigItem_IsRecoveryBoot, &val));