From 1d81da1230728993922126f756f6499b24be3eda Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Mon, 24 Jun 2019 02:05:08 -0700 Subject: [PATCH] add map, patcher, ro to sts. --- Makefile | 2 +- include/stratosphere/map.hpp | 21 ++ include/stratosphere/map/map_api.hpp | 30 +++ include/stratosphere/map/map_types.hpp | 116 +++++++++ include/stratosphere/patcher.hpp | 20 ++ include/stratosphere/patcher/patcher_api.hpp | 26 ++ include/stratosphere/ro.hpp | 20 ++ include/stratosphere/ro/ro_types.hpp | 152 +++++++++++ include/stratosphere/spl/spl_api.hpp | 4 +- source/map/map_api.cpp | 243 ++++++++++++++++++ source/patcher/patcher_api.cpp | 256 +++++++++++++++++++ source/spl/spl_api.cpp | 12 + 12 files changed, 900 insertions(+), 2 deletions(-) create mode 100644 include/stratosphere/map.hpp create mode 100644 include/stratosphere/map/map_api.hpp create mode 100644 include/stratosphere/map/map_types.hpp create mode 100644 include/stratosphere/patcher.hpp create mode 100644 include/stratosphere/patcher/patcher_api.hpp create mode 100644 include/stratosphere/ro.hpp create mode 100644 include/stratosphere/ro/ro_types.hpp create mode 100644 source/map/map_api.cpp create mode 100644 source/patcher/patcher_api.cpp 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));