diff --git a/README.md b/README.md index 49a2285b..860acdb1 100644 --- a/README.md +++ b/README.md @@ -1,117 +1,44 @@ -# Switch OC Suite -[![License: GPL v2](https://img.shields.io/badge/License-GPL_v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) -[![Downloads](https://img.shields.io/github/downloads/hanai3Bi/Switch-OC-Suite/total)](https://github.com/hanai3Bi/Switch-OC-Suite/releases) - - - -*If Switch OC Suite is so good, then why isn't there a Switch OC Suite 2?* - -This project is very dangerous and can possibly damage your console. If you decide to use it, USE AT YOUR OWN RISK. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND. - -Overclocking Suite for Nintendo Switch consoles running Atmosphere CFW. - -[Project Homepage](https://souldbminersmwc.github.io/Switch-OC-Suite-2) - -**DISCLAIMER: USE AT YOUR OWN RISK!** - -- Overclocking in general will shorten the lifespan of some hardware components. **YOU ARE RESPONSIBLE for any problem or potential damage** if unsafe frequencies are ENABLED in sys-clk-OC. Issues like asking for bypassing limit will BE IGNORED OR CLOSED WITHOUT REPLY. - -- Due to HorizonOS design, instabilities from unsafe RAM clocks may cause filesystem corruption. **Always make backup before enabling DRAM OC.** - -## Features - -- Erista variant (HAC-001) - - CPU / GPU Overclock (Safe: 1785 / 921 MHz) - - Unsafe - - Due to the limit of board power draw or power IC - - Unlockable frequencies up to 2091 / 998 MHz - - See [README for sys-clk-OC](https://github.com/hanai3Bi/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md) - - - DRAM Overclock (Safe: 1862.4 MHz) - -- Mariko variant (HAC-001-01, HDH-001, HEG-001) - - CPU / GPU Overclock (Safe: 1963 / 998 MHz) - - Unsafe - - Due to the limit of board power draw or power IC - - Unlockable frequencies up to 2295 / 1267 MHz - - See [README for sys-clk-OC](https://github.com/hanai3Bi/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md) - - - DRAM Overclock (Safe: 1996.8 MHz) - -- Modded sys-clk - - Global Profile - - Designated a dummy title id `0xA111111111111111`. - - Priority: "Temp overrides" > "Application profile" > "Global profile" > "System default". - - Real Voltage readings (CPU/GPU/RAM) - - Bug Fixes - -- **[System Settings (Optional)](https://github.com/hanai3Bi/Switch-OC-Suite/blob/master/system_settings.md)** +
+![alt text](assets/logo.png "logo") + + +![alt text](https://img.shields.io/badge/GPL--2.0-red?style=for-the-badge "logo") ![alt text](https://img.shields.io/badge/Nintendo_Switch-E60012?style=for-the-badge&logo=nintendo-switch&logoColor=white "logo") [![alt text](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.com/invite/S3eX47dHsB) ![alt text](https://img.shields.io/badge/VSCode-0078D4?style=for-the-badge&logo=visual%20studio%20code&logoColor=white) ![alt text](https://img.shields.io/badge/C%2B%2B-00599C?style=for-the-badge&logo=c%2B%2B&logoColor=white) + +
+ +##### DISCLAIMER: THIS TOOL CAN BE DANGEROUS IF MISUSED. PROCEED WITH CAUTION +* Due to the design of Horizon OS, overclocking RAM can cause **NAND DAMAGE**. Ensure to have a NAND Backup + +A open source overclocking tool for Nintendo Switch consoles running Atmosphere custom firmware + + +## Features: +CPU overclock up to 2601MHz on Mariko units, 2091MHz on Erista units +GPU up to 1382MHz on Mariko units, 1075MHz on Erista units +RAM up to 3200MHz on Mariko units, 2250MHz on Erista units +Over/undervolting +Configurator +Works with most homebrew + +*Higher (potentially dangerous) frequencies are unlockable* ## Installation +Grab latest hoc.kip from releases tab +If using hekate, edit hekate_ipl.ini to include "kip1=atmosphere/kips/*". No need for editing if using fusee +Download latest Horizon OC sysmodule from releases tab +Extract sysmodule into root of SD card -1. Download latest [release](https://github.com/hanai3Bi/Switch-OC-Suite/releases). +Ensure you are using latest Atmosphere, otherwise the console will not boot -2. Grab `x.x.x_loader.kip` for your Atmosphere version, rename it to `loader.kip` and place it in `/atmosphere/kips/`. +## Building +Set up a development enviorment ready to compile Atmosphere +Git clone Atmosphere, and move the cloned folder into build/ +Insert Source/stratosphere folder into build/ +Run build.sh -3. (optional) You can customize via [online loader configurator](https://hanai3Bi.github.io/Switch-OC-Suite/#config): -
- - | Defaults | Mariko | Erista | - | ---------- | ------------- | ------------- | - | CPU OC | 2295 MHz Max | 2091 MHz Max | - | CPU Volt | 1235 mV | 1235 mV | - | GPU OC | 1267 MHz Max | 998 Mhz max | - | RAM OC | 1996 MHz | 1862 MHz | - | RAM Volt | Disabled | Disabled | - | RAM Timing | Auto-Adjusted | Auto-Adjusted | - | CPU UV | Disabled | N/A | - | GPU UV | Disabled | N/A | - -
- -4. Hekate-ipl bootloader (fss0) Only (Not required for AMS fusee) - - At boot entry section in `bootloader/hekate_ipl.ini`, Add `kip1=atmosphere/kips/loader.kip` to any line that works. - -5. Install [sys-clk-oc](https://github.com/hanai3Bi/Switch-OC-Suite/releases/latest/download/sys-clk-oc.zip) - - official [sys-clk](https://github.com/retronx-team/sys-clk/releases) (2.0.0+) is compatible, but not recommended (no bugfixes or additional features). - -6. (optional) Copy SdOut.zip for useful utilities. - -## Updating via AIO - -1. Download and copy custom_packs.json to /config/aio-switch-updater/custom_packs.json - -2. Launch AIO Switch Updater and go to Custom Downloads tab - -3. Select Switch-OC-Suite and press Contiune - - -## Build - -
- -1. Copy Switch-OC-Suite files into Atmosphere folder. - -2. Before compiling, run `patch.py` in `Atmosphere/stratosphere/loader/source/` to insert oc module into loader sysmodule. - -3. Compile Atmosphere loader with devkitpro. - -4. When compilation is done, uncompress the kip to make it work with configurator: `hactool -t kip1 loader.kip --uncompress=loader.kip` - -
- - -## Acknowledgement - -- CTCaer for [Hekate-ipl](https://github.com/CTCaer/hekate) bootloader, RE and hardware research -- [devkitPro](https://devkitpro.org/) for All-In-One homebrew toolchains -- masagrator for [ReverseNX-RT](https://github.com/masagrator/ReverseNX-RT) and info on BatteryChargeInfoFields in psm module -- Nvidia for [Tegra X1 Technical Reference Manual](https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual) -- RetroNX team for [sys-clk](https://github.com/retronx-team/sys-clk) -- SciresM and Reswitched Team for the state-of-the-art [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere) CFW of Switch -- Switchbrew [wiki](http://switchbrew.org/wiki/) for Switch in-depth info -- Switchroot for their [modified L4T kernel and device tree](https://gitlab.com/switchroot/kernel) -- ZatchyCatGames for RE and original OC loader patches for Atmosphere -- KazushiMe for [Switch-OC-Suite](https://github.com/KazushiMe/Switch-OC-Suite) -- lineon for research and help +## Credits +meha for Switch-Oc-Suite +sys-clk team for sys-clk +b0rd2auth for Ultrahand sys-clk fork +Lightos and Sammybigio2010 for early testing \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/ldr_meta.cpp b/Source/Atmosphere/stratosphere/loader/source/ldr_meta.cpp new file mode 100644 index 00000000..f0f8fb7d --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/ldr_meta.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (c) 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 "ldr_capabilities.hpp" +#include "ldr_content_management.hpp" +#include "ldr_development_manager.hpp" +#include "ldr_meta.hpp" + +namespace ams::ldr { + + namespace { + + /* Convenience definitions. */ + constexpr size_t MetaCacheBufferSize = 0x8000; + constexpr inline const char AtmosphereMetaPath[] = ENCODE_ATMOSPHERE_CODE_PATH("/main.npdm"); + constexpr inline const char SdOrBaseMetaPath[] = ENCODE_SD_OR_CODE_PATH("/main.npdm"); + constexpr inline const char BaseMetaPath[] = ENCODE_CODE_PATH("/main.npdm"); + + /* Types. */ + struct MetaCache { + Meta meta; + u8 buffer[MetaCacheBufferSize]; + }; + + /* Global storage. */ + ncm::ProgramId g_cached_program_id; + cfg::OverrideStatus g_cached_override_status; + MetaCache g_meta_cache; + MetaCache g_original_meta_cache; + + /* Helpers. */ + Result ValidateSubregion(size_t allowed_start, size_t allowed_end, size_t start, size_t size, size_t min_size = 0) { + R_UNLESS(size >= min_size, ldr::ResultInvalidMeta()); + R_UNLESS(allowed_start <= start, ldr::ResultInvalidMeta()); + R_UNLESS(start <= allowed_end, ldr::ResultInvalidMeta()); + R_UNLESS(start + size <= allowed_end, ldr::ResultInvalidMeta()); + R_SUCCEED(); + } + + Result ValidateNpdm(const Npdm *npdm, size_t size) { + /* Validate magic. */ + R_UNLESS(npdm->magic == Npdm::Magic, ldr::ResultInvalidMeta()); + + /* Validate flags. */ + constexpr u32 InvalidMetaFlagMask = 0x80000000; + R_UNLESS(!(npdm->flags & InvalidMetaFlagMask), ldr::ResultInvalidMeta()); + + /* Validate Acid extents. */ + R_TRY(ValidateSubregion(sizeof(Npdm), size, npdm->acid_offset, npdm->acid_size, sizeof(Acid))); + + /* Validate Aci extends. */ + R_TRY(ValidateSubregion(sizeof(Npdm), size, npdm->aci_offset, npdm->aci_size, sizeof(Aci))); + + R_SUCCEED(); + } + + Result ValidateAcid(const Acid *acid, size_t size) { + /* Validate magic. */ + R_UNLESS(acid->magic == Acid::Magic, ldr::ResultInvalidMeta()); + + /* Validate that the acid is for production if not development. */ + if (!IsDevelopmentForAcidProductionCheck()) { + R_UNLESS((acid->flags & Acid::AcidFlag_Production) != 0, ldr::ResultInvalidMeta()); + } + + /* Validate that the acid version is correct. */ + constexpr u8 SupportedSdkMajorVersion = ams::svc::ConvertToSdkMajorVersion(ams::svc::SupportedKernelMajorVersion); + if (acid->unknown_209 < SupportedSdkMajorVersion) { + R_UNLESS(acid->version == 0, ldr::ResultInvalidMeta()); + R_UNLESS(acid->unknown_209 == 0, ldr::ResultInvalidMeta()); + } + + /* Validate Fac, Sac, Kac. */ + R_TRY(ValidateSubregion(sizeof(Acid), size, acid->fac_offset, acid->fac_size)); + R_TRY(ValidateSubregion(sizeof(Acid), size, acid->sac_offset, acid->sac_size)); + R_TRY(ValidateSubregion(sizeof(Acid), size, acid->kac_offset, acid->kac_size)); + + R_SUCCEED(); + } + + Result ValidateAci(const Aci *aci, size_t size) { + /* Validate magic. */ + R_UNLESS(aci->magic == Aci::Magic, ldr::ResultInvalidMeta()); + + /* Validate Fah, Sac, Kac. */ + R_TRY(ValidateSubregion(sizeof(Aci), size, aci->fah_offset, aci->fah_size)); + R_TRY(ValidateSubregion(sizeof(Aci), size, aci->sac_offset, aci->sac_size)); + R_TRY(ValidateSubregion(sizeof(Aci), size, aci->kac_offset, aci->kac_size)); + + R_SUCCEED(); + } + + const u8 *GetAcidSignatureModulus(ncm::ContentMetaPlatform platform, u8 key_generation, bool unk_unused) { + return fssystem::GetAcidSignatureKeyModulus(platform, !IsDevelopmentForAcidSignatureCheck(), key_generation, unk_unused); + } + + size_t GetAcidSignatureModulusSize(ncm::ContentMetaPlatform platform, bool unk_unused) { + return fssystem::GetAcidSignatureKeyModulusSize(platform, unk_unused); + } + + Result ValidateAcidSignature(Meta *meta, ncm::ContentMetaPlatform platform, bool unk_unused) { + R_SUCCEED(); + /* Loader did not check signatures prior to 10.0.0. */ + if (hos::GetVersion() < hos::Version_10_0_0) { + meta->check_verification_data = false; + R_SUCCEED(); + } + + /* Get the signature key generation. */ + const auto signature_key_generation = meta->npdm->signature_key_generation; + R_UNLESS(fssystem::IsValidSignatureKeyGeneration(platform, signature_key_generation), ldr::ResultInvalidMeta()); + + /* Verify the signature. */ + const u8 *sig = meta->acid->signature; + const size_t sig_size = sizeof(meta->acid->signature); + const u8 *mod = GetAcidSignatureModulus(platform, signature_key_generation, unk_unused); + const size_t mod_size = GetAcidSignatureModulusSize(platform, unk_unused); + const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent(); + const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize; + const u8 *msg = meta->acid->modulus; + const size_t msg_size = meta->acid->size; + const bool is_signature_valid = crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + R_UNLESS(is_signature_valid || !IsEnabledProgramVerification(), ldr::ResultInvalidAcidSignature()); + + meta->check_verification_data = is_signature_valid; + R_SUCCEED(); + } + + Result LoadMetaFromFile(fs::FileHandle file, MetaCache *cache) { + /* Reset cache. */ + cache->meta = {}; + + /* Read from file. */ + s64 npdm_size = 0; + { + /* Get file size. */ + R_TRY(fs::GetFileSize(std::addressof(npdm_size), file)); + + /* Read data into cache buffer. */ + R_UNLESS(npdm_size <= static_cast(MetaCacheBufferSize), ldr::ResultMetaOverflow()); + R_TRY(fs::ReadFile(file, 0, cache->buffer, npdm_size)); + } + + /* Ensure size is big enough. */ + R_UNLESS(npdm_size >= static_cast(sizeof(Npdm)), ldr::ResultInvalidMeta()); + + /* Validate the meta. */ + { + Meta *meta = std::addressof(cache->meta); + + Npdm *npdm = reinterpret_cast(cache->buffer); + R_TRY(ValidateNpdm(npdm, npdm_size)); + + Acid *acid = reinterpret_cast(cache->buffer + npdm->acid_offset); + Aci *aci = reinterpret_cast(cache->buffer + npdm->aci_offset); + R_TRY(ValidateAcid(acid, npdm->acid_size)); + R_TRY(ValidateAci(aci, npdm->aci_size)); + + /* Set Meta members. */ + meta->npdm = npdm; + meta->acid = acid; + meta->aci = aci; + + meta->acid_fac = reinterpret_cast(acid) + acid->fac_offset; + meta->acid_sac = reinterpret_cast(acid) + acid->sac_offset; + meta->acid_kac = reinterpret_cast(acid) + acid->kac_offset; + + meta->aci_fah = reinterpret_cast(aci) + aci->fah_offset; + meta->aci_sac = reinterpret_cast(aci) + aci->sac_offset; + meta->aci_kac = reinterpret_cast(aci) + aci->kac_offset; + + meta->modulus = acid->modulus; + } + + R_SUCCEED(); + } + + } + + /* API. */ + Result LoadMeta(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform, bool unk_unused) { + /* Set the cached program id back to zero. */ + g_cached_program_id = {}; + + /* Try to load meta from file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), AtmosphereMetaPath, fs::OpenMode_Read)); + { + ON_SCOPE_EXIT { fs::CloseFile(file); }; + R_TRY(LoadMetaFromFile(file, std::addressof(g_meta_cache))); + } + + /* Patch meta. Start by setting all program ids to the current program id. */ + Meta *meta = std::addressof(g_meta_cache.meta); + meta->acid->program_id_min = loc.program_id; + meta->acid->program_id_max = loc.program_id; + meta->aci->program_id = loc.program_id; + + /* For HBL, we need to copy some information from the base meta. */ + if (status.IsHbl()) { + if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), SdOrBaseMetaPath, fs::OpenMode_Read))) { + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + + if (R_SUCCEEDED(LoadMetaFromFile(file, std::addressof(g_original_meta_cache)))) { + Meta *o_meta = std::addressof(g_original_meta_cache.meta); + + /* Fix pool partition. */ + if (hos::GetVersion() >= hos::Version_5_0_0) { + meta->acid->flags = (meta->acid->flags & 0xFFFFFFC3) | (o_meta->acid->flags & 0x0000003C); + } + + /* Fix flags. */ + const u16 program_info_flags = MakeProgramInfoFlag(static_cast(o_meta->aci_kac), o_meta->aci->kac_size / sizeof(util::BitPack32)); + UpdateProgramInfoFlag(program_info_flags, static_cast(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32)); + UpdateProgramInfoFlag(program_info_flags, static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)); + } + } + + /* Perform address space override. */ + if (status.HasOverrideAddressSpace()) { + /* Clear the existing address space. */ + meta->npdm->flags &= ~Npdm::MetaFlag_AddressSpaceTypeMask; + + /* Set the new address space flag. */ + switch (status.GetOverrideAddressSpaceFlags()) { + case cfg::impl::OverrideStatusFlag_AddressSpace32Bit: meta->npdm->flags |= (Npdm::AddressSpaceType_32Bit) << Npdm::MetaFlag_AddressSpaceTypeShift; break; + case cfg::impl::OverrideStatusFlag_AddressSpace64BitDeprecated: meta->npdm->flags |= (Npdm::AddressSpaceType_64BitDeprecated) << Npdm::MetaFlag_AddressSpaceTypeShift; break; + case cfg::impl::OverrideStatusFlag_AddressSpace32BitWithoutAlias: meta->npdm->flags |= (Npdm::AddressSpaceType_32BitWithoutAlias) << Npdm::MetaFlag_AddressSpaceTypeShift; break; + case cfg::impl::OverrideStatusFlag_AddressSpace64Bit: meta->npdm->flags |= (Npdm::AddressSpaceType_64Bit) << Npdm::MetaFlag_AddressSpaceTypeShift; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + /* When hbl is applet, adjust main thread priority. */ + if ((MakeProgramInfoFlag(static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet) { + constexpr auto HblMainThreadPriorityApplication = 44; + constexpr auto HblMainThreadPriorityApplet = 40; + if (meta->npdm->main_thread_priority == HblMainThreadPriorityApplication) { + meta->npdm->main_thread_priority = HblMainThreadPriorityApplet; + } + } + + /* Fix the debug capabilities, to prevent needing a hbl recompilation. */ + FixDebugCapabilityForHbl(static_cast(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32)); + FixDebugCapabilityForHbl(static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)); + } else if (hos::GetVersion() >= hos::Version_10_0_0) { + /* If storage id is none, there is no base code filesystem, and thus it is impossible for us to validate. */ + /* However, if we're an application, we are guaranteed a base code filesystem. */ + if (static_cast(loc.storage_id) != ncm::StorageId::None || ncm::IsApplicationId(loc.program_id)) { + R_TRY(fs::OpenFile(std::addressof(file), BaseMetaPath, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + R_TRY(LoadMetaFromFile(file, std::addressof(g_original_meta_cache))); + R_TRY(ValidateAcidSignature(std::addressof(g_original_meta_cache.meta), platform, unk_unused)); + meta->modulus = g_original_meta_cache.meta.modulus; + meta->check_verification_data = g_original_meta_cache.meta.check_verification_data; + } + } + + /* Pre-process the capabilities. */ + /* This is used to e.g. avoid passing memory region descriptor to older kernels. */ + PreProcessCapability(static_cast(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32)); + PreProcessCapability(static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)); + + /* Set output. */ + g_cached_program_id = loc.program_id; + g_cached_override_status = status; + *out_meta = *meta; + + R_SUCCEED(); + } + + Result LoadMetaFromCache(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform) { + if (g_cached_program_id != loc.program_id || g_cached_override_status != status) { + R_RETURN(LoadMeta(out_meta, loc, status, platform, false)); + } + *out_meta = g_meta_cache.meta; + R_SUCCEED(); + } + + void InvalidateMetaCache() { + /* Set the cached program id back to zero. */ + g_cached_program_id = {}; + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp b/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp new file mode 100644 index 00000000..56df8c2d --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp @@ -0,0 +1,780 @@ +/* + * Copyright (c) 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 "ldr_capabilities.hpp" +#include "ldr_content_management.hpp" +#include "ldr_development_manager.hpp" +#include "ldr_launch_record.hpp" +#include "ldr_meta.hpp" +#include "ldr_patcher.hpp" +#include "ldr_process_creation.hpp" +#include "ldr_ro_manager.hpp" +#include "ldr_ro_manager.hpp" +#include "oc/oc_loader.hpp" + +namespace ams::ldr { + + namespace { + + /* Convenience defines. */ + constexpr size_t SystemResourceSizeMax = 0x1FE00000; + + /* Types. */ + enum NsoIndex { + Nso_Rtld = 0, + Nso_Main = 1, + Nso_Compat0 = 2, + Nso_Compat1 = 3, + Nso_Compat2 = 4, + Nso_Compat3 = 5, + Nso_Compat4 = 6, + Nso_Compat5 = 7, + Nso_Compat6 = 8, + Nso_Compat7 = 9, + Nso_Compat8 = 10, + Nso_Compat9 = 11, + Nso_SubSdk0 = 12, + Nso_SubSdk1 = 13, + Nso_SubSdk2 = 14, + Nso_SubSdk3 = 15, + Nso_SubSdk4 = 16, + Nso_SubSdk5 = 17, + Nso_SubSdk6 = 18, + Nso_SubSdk7 = 19, + Nso_SubSdk8 = 20, + Nso_SubSdk9 = 21, + Nso_Sdk = 22, + Nso_Count, + }; + + constexpr inline const char *NsoPaths[Nso_Count] = { + ENCODE_ATMOSPHERE_CODE_PATH("/rtld"), + ENCODE_ATMOSPHERE_CODE_PATH("/main"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat0"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat1"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat2"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat3"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat4"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat5"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat6"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat7"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat8"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat9"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk0"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk1"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk2"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk3"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk4"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk5"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk6"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk7"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk8"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk9"), + ENCODE_ATMOSPHERE_CODE_PATH("/sdk"), + }; + + constexpr const char *GetNsoPath(size_t idx) { + AMS_ABORT_UNLESS(idx < Nso_Count); + return NsoPaths[idx]; + } + + struct ProcessInfo { + os::NativeHandle process_handle; + uintptr_t args_address; + size_t args_size; + uintptr_t nso_address[Nso_Count]; + size_t nso_size[Nso_Count]; + }; + + /* Global NSO header cache. */ + bool g_has_nso[Nso_Count]; + NsoHeader g_nso_headers[Nso_Count]; + + /* Pcv/Ptm check cache */ + bool g_is_pcv; + bool g_is_ptm; + + Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) { + /* No version verification is done before 8.1.0. */ + R_SUCCEED_IF(hos::GetVersion() < hos::Version_8_1_0); + + /* No verification is done if development. */ + R_SUCCEED_IF(IsDevelopmentForAntiDowngradeCheck()); + + /* TODO: Anti-downgrade checking does not make very much sense for us. Should we do anything? */ + AMS_UNUSED(program_id, version); + + R_SUCCEED(); + } + + /* Helpers. */ + Result GetProgramInfoFromMeta(ProgramInfo *out, const Meta *meta) { + /* Copy basic info. */ + out->main_thread_priority = meta->npdm->main_thread_priority; + out->default_cpu_id = meta->npdm->default_cpu_id; + out->main_thread_stack_size = meta->npdm->main_thread_stack_size; + out->program_id = meta->aci->program_id; + + /* Copy access controls. */ + size_t offset = 0; +#define COPY_ACCESS_CONTROL(source, which) \ + ({ \ + const size_t size = meta->source->which##_size; \ + R_UNLESS(offset + size <= sizeof(out->ac_buffer), ldr::ResultInternalError()); \ + out->source##_##which##_size = size; \ + std::memcpy(out->ac_buffer + offset, meta->source##_##which, size); \ + offset += size; \ + }) + + /* Copy all access controls to buffer. */ + COPY_ACCESS_CONTROL(acid, sac); + COPY_ACCESS_CONTROL(aci, sac); + COPY_ACCESS_CONTROL(acid, fac); + COPY_ACCESS_CONTROL(aci, fah); +#undef COPY_ACCESS_CONTROL + + /* Copy flags. */ + out->flags = MakeProgramInfoFlag(static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)); + R_SUCCEED(); + } + + bool IsApplet(const Meta *meta) { + return (MakeProgramInfoFlag(static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet; + } + + bool IsApplication(const Meta *meta) { + return (MakeProgramInfoFlag(static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application; + } + + Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) { + return static_cast((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift); + } + + Acid::PoolPartition GetPoolPartition(const Meta *meta) { + return static_cast((meta->acid->flags & Acid::AcidFlag_PoolPartitionMask) >> Acid::AcidFlag_PoolPartitionShift); + } + + Result LoadAutoLoadHeaders(NsoHeader *nso_headers, bool *has_nso) { + /* Clear NSOs. */ + std::memset(nso_headers, 0, sizeof(*nso_headers) * Nso_Count); + std::memset(has_nso, 0, sizeof(*has_nso) * Nso_Count); + + for (size_t i = 0; i < Nso_Count; i++) { + fs::FileHandle file; + if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read))) { + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Read NSO header. */ + size_t read_size; + R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, nso_headers + i, sizeof(*nso_headers))); + R_UNLESS(read_size == sizeof(*nso_headers), ldr::ResultInvalidNso()); + + has_nso[i] = true; + } + } + + R_SUCCEED(); + } + + Result CheckAutoLoad(const NsoHeader *nso_headers, const bool *has_nso) { + /* We must always have a main. */ + R_UNLESS(has_nso[Nso_Main], ldr::ResultInvalidNso()); + + /* If we don't have an RTLD, we must only have a main. */ + if (!has_nso[Nso_Rtld]) { + for (size_t i = Nso_Main + 1; i < Nso_Count; i++) { + R_UNLESS(!has_nso[i], ldr::ResultInvalidNso()); + } + } + + /* All NSOs must have zero text offset. */ + for (size_t i = 0; i < Nso_Count; i++) { + R_UNLESS(nso_headers[i].text_dst_offset == 0, ldr::ResultInvalidNso()); + } + + R_SUCCEED(); + } + + constexpr const ncm::ProgramId UnqualifiedApprovalProgramIds[] = { + { 0x010003F003A34000 }, /* Pokemon: Let's Go, Pikachu! */ + { 0x0100152000022000 }, /* Mario Kart 8 Deluxe */ + { 0x0100165003504000 }, /* Nintendo Labo Toy-Con 04: VR Kit */ + { 0x0100187003A36000 }, /* Pokemon: Let's Go, Eevee! */ + { 0x01002E5008C56000 }, /* Pokemon Sword [Live Tournament] */ + { 0x01002FF008C24000 }, /* Ring Fit Adventure */ + { 0x010049900F546001 }, /* Super Mario 3D All-Stars: Super Mario 64 */ + { 0x010057D00ECE4000 }, /* Nintendo Switch Online (Nintendo 64) [for Japan] */ + { 0x01006F8002326000 }, /* Animal Crossing: New Horizons */ + { 0x01006FB00F50E000 }, /* [???] */ + { 0x010070300F50C000 }, /* [???] */ + { 0x010075100E8EC000 }, /* 马力欧卡丁车8 豪华版 [Mario Kart 8 Deluxe for China] */ + { 0x01008DB008C2C000 }, /* Pokemon Shield */ + { 0x01009AD008C4C000 }, /* Pokemon: Let's Go, Pikachu! [Kiosk] */ + { 0x0100A66003384000 }, /* Hulu */ + { 0x0100ABF008968000 }, /* Pokemon Sword */ + { 0x0100C9A00ECE6000 }, /* Nintendo Switch Online (Nintendo 64) [for America] */ + { 0x0100ED100BA3A000 }, /* Mario Kart Live: Home Circuit */ + { 0x0100F38011CFE000 }, /* Animal Crossing: New Horizons Island Transfer Tool */ + { 0x0100F6B011028000 }, /* 健身环大冒险 [Ring Fit Adventure for China] */ + }; + + /* Check that the unqualified approval programs are sorted. */ + static_assert([]() -> bool { + for (size_t i = 0; i < util::size(UnqualifiedApprovalProgramIds) - 1; ++i) { + if (UnqualifiedApprovalProgramIds[i].value >= UnqualifiedApprovalProgramIds[i + 1].value) { + return false; + } + } + + return true; + }()); + + bool IsUnqualifiedApprovalProgramId(ncm::ProgramId program_id) { + /* Check if the program id is one with unqualified approval. */ + return std::binary_search(std::begin(UnqualifiedApprovalProgramIds), std::end(UnqualifiedApprovalProgramIds), program_id); + } + + bool IsUnqualifiedApproval(const Meta *meta) { + /* If the meta has unqualified approval flag, it's unqualified approval. */ + if (meta->acid->flags & ldr::Acid::AcidFlag_UnqualifiedApproval) { + return true; + } + + /* If the unqualified approval flag is not set, the program must be an application. */ + if (!IsApplication(meta)) { + return false; + } + + /* The program id must be a force unqualified approval program id. */ + return IsUnqualifiedApprovalProgramId(meta->acid->program_id_min) && meta->acid->program_id_min == meta->acid->program_id_max; + } + + Result ValidateMeta(const Meta *meta, const ncm::ProgramLocation &loc, const fs::CodeVerificationData &code_verification_data) { + /* Validate version. */ + R_TRY(ValidateProgramVersion(loc.program_id, meta->npdm->version)); + + /* Validate program id. */ + R_UNLESS(meta->aci->program_id >= meta->acid->program_id_min, ldr::ResultInvalidProgramId()); + R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId()); + + /* Validate the kernel capabilities. */ + R_TRY(TestCapability(static_cast(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32), static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32))); + +/* Check if NCA is PCV or PTM */ +g_is_pcv = meta->aci->program_id == ncm::SystemProgramId::Pcv; +g_is_ptm = meta->aci->program_id == ncm::SystemProgramId::Ptm; + + + + /* If we have data to validate, validate it. */ + if (meta->check_verification_data) { + const u8 *sig = code_verification_data.signature; + const size_t sig_size = sizeof(code_verification_data.signature); + const u8 *mod = static_cast(meta->modulus); + const size_t mod_size = crypto::Rsa2048PssSha256Verifier::ModulusSize; + const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent(); + const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize; + const u8 *hsh = code_verification_data.target_hash; + const size_t hsh_size = sizeof(code_verification_data.target_hash); + const bool is_signature_valid = crypto::VerifyRsa2048PssSha256WithHash(sig, sig_size, mod, mod_size, exp, exp_size, hsh, hsh_size); + + /* If the signature check fails, we need to check if this is allowable. */ + if (!is_signature_valid) { + /* We have to enforce signature checks on prod and when we have a signature to check on dev. */ + R_UNLESS(IsDevelopmentForAcidProductionCheck(), ldr::ResultInvalidNcaSignature()); + R_UNLESS(!code_verification_data.has_data, ldr::ResultInvalidNcaSignature()); + + /* There was no signature to check on dev. Check if this is acceptable. */ + R_UNLESS(IsUnqualifiedApproval(meta), ldr::ResultInvalidNcaSignature()); + } + } + + /* All good. */ + R_SUCCEED(); + } + + Result GetCreateProcessFlags(u32 *out, const Meta *meta, const u32 ldr_flags) { + const u8 meta_flags = meta->npdm->flags; + + u32 flags = 0; + + /* Set Is64Bit. */ + if (meta_flags & Npdm::MetaFlag_Is64Bit) { + flags |= svc::CreateProcessFlag_Is64Bit; + } + + /* Set AddressSpaceType. */ + switch (GetAddressSpaceType(meta)) { + case Npdm::AddressSpaceType_32Bit: + flags |= svc::CreateProcessFlag_AddressSpace32Bit; + break; + case Npdm::AddressSpaceType_64BitDeprecated: + flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated; + break; + case Npdm::AddressSpaceType_32BitWithoutAlias: + flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias; + break; + case Npdm::AddressSpaceType_64Bit: + flags |= svc::CreateProcessFlag_AddressSpace64Bit; + break; + default: + R_THROW(ldr::ResultInvalidMeta()); + } + + /* Set Enable Debug. */ + if (ldr_flags & CreateProcessFlag_EnableDebug) { + flags |= svc::CreateProcessFlag_EnableDebug; + } + + /* Set Enable ASLR. */ + if (!(ldr_flags & CreateProcessFlag_DisableAslr)) { + flags |= svc::CreateProcessFlag_EnableAslr; + } + + /* Set Is Application. */ + if (IsApplication(meta)) { + flags |= svc::CreateProcessFlag_IsApplication; + + /* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */ + if (hos::GetVersion() >= hos::Version_7_0_0) { + if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) { + flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation; + } + } + } + + /* 5.0.0+ Set Pool Partition. */ + if (hos::GetVersion() >= hos::Version_5_0_0) { + /* TODO: Nintendo no longer accepts Applet when pool partition == application. Would this break hbl/anything else in the hb ecosystem? */ + /* TODO: Nintendo uses a helper bool MakeSvcPoolPartitionFlag(u32 *out, Acid::PoolPartition partition); */ + switch (GetPoolPartition(meta)) { + case Acid::PoolPartition_Application: + if (IsApplet(meta)) { + flags |= svc::CreateProcessFlag_PoolPartitionApplet; + } else { + flags |= svc::CreateProcessFlag_PoolPartitionApplication; + } + break; + case Acid::PoolPartition_Applet: + flags |= svc::CreateProcessFlag_PoolPartitionApplet; + break; + case Acid::PoolPartition_System: + flags |= svc::CreateProcessFlag_PoolPartitionSystem; + break; + case Acid::PoolPartition_SystemNonSecure: + flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure; + break; + default: + R_THROW(ldr::ResultInvalidMeta()); + } + } else if (hos::GetVersion() >= hos::Version_4_0_0) { + /* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */ + if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) { + flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory; + } + } + + /* 11.0.0+/meso Set Disable DAS merge. */ + if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) { + flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge; + } + + /* 18.0.0+/meso Set Alias region extra size. */ + if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) { + flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize; + } + + *out = flags; + R_SUCCEED(); + } + + Result GetCreateProcessParameter(svc::CreateProcessParameter *out, const Meta *meta, u32 flags, os::NativeHandle resource_limit) { + /* Clear output. */ + std::memset(out, 0, sizeof(*out)); + + /* Set name, version, program id, resource limit handle. */ + std::memcpy(out->name, meta->npdm->program_name, sizeof(out->name) - 1); + out->version = meta->npdm->version; + out->program_id = meta->aci->program_id.value; + out->reslimit = resource_limit; + + /* Set flags. */ + R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags)); + + /* 3.0.0+ System Resource Size. */ + if (hos::GetVersion() >= hos::Version_3_0_0) { + /* Validate size is aligned. */ + R_UNLESS(util::IsAligned(meta->npdm->system_resource_size, os::MemoryBlockUnitSize), ldr::ResultInvalidSize()); + + /* Validate system resource usage. */ + if (meta->npdm->system_resource_size) { + /* Process must be 64-bit. */ + R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta()); + + /* Process must be application or applet. */ + R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta()); + + /* Size must be less than or equal to max. */ + R_UNLESS(meta->npdm->system_resource_size <= SystemResourceSizeMax, ldr::ResultInvalidMeta()); + } + out->system_resource_num_pages = meta->npdm->system_resource_size >> 12; + } + + R_SUCCEED(); + } + + u64 GenerateSecureRandom(u64 max) { + /* Generate a cryptographically random number. */ + u64 rand; + crypto::GenerateCryptographicallyRandomBytes(std::addressof(rand), sizeof(rand)); + + /* Coerce into range. */ + return rand % (max + 1); + } + + Result DecideAddressSpaceLayout(ProcessInfo *out, svc::CreateProcessParameter *out_param, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument) { + /* Clear output. */ + out->args_address = 0; + out->args_size = 0; + std::memset(out->nso_address, 0, sizeof(out->nso_address)); + std::memset(out->nso_size, 0, sizeof(out->nso_size)); + + size_t total_size = 0; + bool argument_allocated = false; + + /* Calculate base offsets. */ + for (size_t i = 0; i < Nso_Count; i++) { + if (has_nso[i]) { + out->nso_address[i] = total_size; + const size_t text_end = nso_headers[i].text_dst_offset + nso_headers[i].text_size; + const size_t ro_end = nso_headers[i].ro_dst_offset + nso_headers[i].ro_size; + const size_t rw_end = nso_headers[i].rw_dst_offset + nso_headers[i].rw_size + nso_headers[i].bss_size; + out->nso_size[i] = text_end; + out->nso_size[i] = std::max(out->nso_size[i], ro_end); + out->nso_size[i] = std::max(out->nso_size[i], rw_end); + out->nso_size[i] = util::AlignUp(out->nso_size[i], os::MemoryPageSize); + + total_size += out->nso_size[i]; + + if (!argument_allocated && argument != nullptr) { + out->args_address = total_size; + out->args_size = util::AlignUp(2 * sizeof(u32) + argument->argument_size * 2 + ArgumentStore::ArgumentBufferSize, os::MemoryPageSize); + total_size += out->args_size; + argument_allocated = true; + } + } + } + + /* Calculate ASLR. */ + uintptr_t aslr_start = 0; + size_t aslr_size = 0; + if (hos::GetVersion() >= hos::Version_2_0_0) { + switch (out_param->flags & svc::CreateProcessFlag_AddressSpaceMask) { + case svc::CreateProcessFlag_AddressSpace32Bit: + case svc::CreateProcessFlag_AddressSpace32BitWithoutAlias: + aslr_start = svc::AddressSmallMap32Start; + aslr_size = svc::AddressSmallMap32Size; + break; + case svc::CreateProcessFlag_AddressSpace64BitDeprecated: + aslr_start = svc::AddressSmallMap36Start; + aslr_size = svc::AddressSmallMap36Size; + break; + case svc::CreateProcessFlag_AddressSpace64Bit: + aslr_start = svc::AddressMap39Start; + aslr_size = svc::AddressMap39Size; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } else { + /* On 1.0.0, only 2 address space types existed. */ + if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) { + aslr_start = svc::AddressSmallMap36Start; + aslr_size = svc::AddressSmallMap36Size; + } else { + aslr_start = svc::AddressSmallMap32Start; + aslr_size = svc::AddressSmallMap32Size; + } + } + R_UNLESS(total_size <= aslr_size, svc::ResultOutOfMemory()); + + /* Set Create Process output. */ + uintptr_t aslr_slide = 0; + size_t free_size = (aslr_size - total_size); + if (out_param->flags & svc::CreateProcessFlag_EnableAslr) { + aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize; + } + + /* Set out. */ + aslr_start += aslr_slide; + for (size_t i = 0; i < Nso_Count; i++) { + if (has_nso[i]) { + out->nso_address[i] += aslr_start; + } + } + if (out->args_address) { + out->args_address += aslr_start; + } + + out_param->code_address = aslr_start; + out_param->code_num_pages = total_size >> 12; + + R_SUCCEED(); + } + + Result LoadAutoLoadModuleSegment(fs::FileHandle file, const NsoHeader::SegmentInfo *segment, size_t file_size, const u8 *file_hash, bool is_compressed, bool check_hash, uintptr_t map_base, uintptr_t map_end) { + /* Select read size based on compression. */ + if (!is_compressed) { + file_size = segment->size; + } + + /* Validate size. */ + R_UNLESS(file_size <= segment->size, ldr::ResultInvalidNso()); + R_UNLESS(segment->size <= std::numeric_limits::max(), ldr::ResultInvalidNso()); + + /* Load data from file. */ + uintptr_t load_address = is_compressed ? map_end - file_size : map_base; + size_t read_size; + R_TRY(fs::ReadFile(std::addressof(read_size), file, segment->file_offset, reinterpret_cast(load_address), file_size)); + R_UNLESS(read_size == file_size, ldr::ResultInvalidNso()); + + /* Uncompress if necessary. */ + if (is_compressed) { + bool decompressed = (util::DecompressLZ4(reinterpret_cast(map_base), segment->size, reinterpret_cast(load_address), file_size) == static_cast(segment->size)); + R_UNLESS(decompressed, ldr::ResultInvalidNso()); + } + + /* Check hash if necessary. */ + if (check_hash) { + u8 hash[crypto::Sha256Generator::HashSize]; + crypto::GenerateSha256(hash, sizeof(hash), reinterpret_cast(map_base), segment->size); + + R_UNLESS(std::memcmp(hash, file_hash, sizeof(hash)) == 0, ldr::ResultInvalidNso()); + } + + R_SUCCEED(); + } + + Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, bool prevent_code_reads) { + /* Map and read data from file. */ + { + /* Map the process memory. */ + void *mapped_memory = nullptr; + R_TRY(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, nso_address, nso_size, GenerateSecureRandom)); + ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, process_handle, nso_address, nso_size); }; + + const uintptr_t map_address = reinterpret_cast(mapped_memory); + + /* Load NSO segments. */ + R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Text]), nso_header->text_compressed_size, nso_header->text_hash, (nso_header->flags & NsoHeader::Flag_CompressedText) != 0, + (nso_header->flags & NsoHeader::Flag_CheckHashText) != 0, map_address + nso_header->text_dst_offset, map_address + nso_size)); + R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Ro]), nso_header->ro_compressed_size, nso_header->ro_hash, (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0, + (nso_header->flags & NsoHeader::Flag_CheckHashRo) != 0, map_address + nso_header->ro_dst_offset, map_address + nso_size)); + R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Rw]), nso_header->rw_compressed_size, nso_header->rw_hash, (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0, + (nso_header->flags & NsoHeader::Flag_CheckHashRw) != 0, map_address + nso_header->rw_dst_offset, map_address + nso_size)); + + /* Clear unused space to zero. */ + const size_t text_end = nso_header->text_dst_offset + nso_header->text_size; + const size_t ro_end = nso_header->ro_dst_offset + nso_header->ro_size; + const size_t rw_end = nso_header->rw_dst_offset + nso_header->rw_size; + std::memset(reinterpret_cast(map_address + 0), 0, nso_header->text_dst_offset); + std::memset(reinterpret_cast(map_address + text_end), 0, nso_header->ro_dst_offset - text_end); + std::memset(reinterpret_cast(map_address + ro_end), 0, nso_header->rw_dst_offset - ro_end); + std::memset(reinterpret_cast(map_address + rw_end), 0, nso_header->bss_size); + + /* Apply embedded patches. */ + ApplyEmbeddedPatchesToModule(nso_header->module_id, map_address, nso_size); + + /* Apply IPS patches. */ + LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size); + + /* Apply PCV and PTM patches */ +if (g_is_pcv) + oc::pcv::Patch(map_address, nso_size); +if (g_is_ptm) + oc::ptm::Patch(map_address, nso_size); + + } + + /* Set permissions. */ + const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize); + const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize); + const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize); + if (text_size) { + R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute)); + } + if (ro_size) { + R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, os::MemoryPermission_ReadOnly)); + } + if (rw_size) { + R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, os::MemoryPermission_ReadWrite)); + } + + R_SUCCEED(); + } + + Result LoadAutoLoadModules(const ProcessInfo *process_info, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument, bool prevent_code_reads) { + /* Load each NSO. */ + for (size_t i = 0; i < Nso_Count; i++) { + if (has_nso[i]) { + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + R_TRY(LoadAutoLoadModule(process_info->process_handle, file, nso_headers + i, process_info->nso_address[i], process_info->nso_size[i], prevent_code_reads)); + } + } + + /* Load arguments, if present. */ + if (argument != nullptr) { + /* Write argument data into memory. */ + { + void *map_address = nullptr; + R_TRY(os::MapProcessMemory(std::addressof(map_address), process_info->process_handle, process_info->args_address, process_info->args_size, GenerateSecureRandom)); + ON_SCOPE_EXIT { os::UnmapProcessMemory(map_address, process_info->process_handle, process_info->args_address, process_info->args_size); }; + + ProgramArguments *args = static_cast(map_address); + std::memset(args, 0, sizeof(*args)); + args->allocated_size = process_info->args_size; + args->arguments_size = argument->argument_size; + std::memcpy(args->arguments, argument->argument, argument->argument_size); + } + + /* Set argument region permissions. */ + /* NOTE: Nintendo uses svc::SetProcessMemoryPermission directly here. */ + R_TRY(os::SetProcessMemoryPermission(process_info->process_handle, process_info->args_address, process_info->args_size, os::MemoryPermission_ReadWrite)); + } + + R_SUCCEED(); + } + + Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) { + /* Get CreateProcessParameter. */ + svc::CreateProcessParameter param; + R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit)); + + /* Decide on an NSO layout. */ + R_TRY(DecideAddressSpaceLayout(out, std::addressof(param), nso_headers, has_nso, argument)); + + /* Actually create process. */ + svc::Handle process_handle; + R_TRY(svc::CreateProcess(std::addressof(process_handle), std::addressof(param), static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(u32))); + + /* Set the output handle, and ensure that if we fail after this point we clean it up. */ + out->process_handle = process_handle; + ON_RESULT_FAILURE { svc::CloseHandle(process_handle); }; + + /* Load all auto load modules. */ + R_RETURN(LoadAutoLoadModules(out, nso_headers, has_nso, argument, (meta->npdm->flags & ldr::Npdm::MetaFlag_PreventCodeReads) != 0)); + } + + } + + /* Process Creation API. */ + Result CreateProcess(os::NativeHandle *out, PinId pin_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const char *path, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit, const ldr::ProgramAttributes &attrs) { + /* Mount code. */ + AMS_UNUSED(path); + ScopedCodeMount mount(loc, override_status, attrs); + R_TRY(mount.GetResult()); + + /* Load meta, possibly from cache. */ + Meta meta; + R_TRY(LoadMetaFromCache(std::addressof(meta), loc, override_status, attrs.platform)); + + /* Validate meta. */ + R_TRY(ValidateMeta(std::addressof(meta), loc, mount.GetCodeVerificationData())); + + /* Load, validate NSO headers. */ + R_TRY(LoadAutoLoadHeaders(g_nso_headers, g_has_nso)); + R_TRY(CheckAutoLoad(g_nso_headers, g_has_nso)); + + /* Actually create the process and load NSOs into process memory. */ + ProcessInfo info; + R_TRY(CreateProcessAndLoadAutoLoadModules(std::addressof(info), std::addressof(meta), g_nso_headers, g_has_nso, argument, flags, resource_limit)); + + /* Register NSOs with the RoManager. */ + { + /* Nintendo doesn't validate this get, but we do. */ + os::ProcessId process_id = os::GetProcessId(info.process_handle); + + /* Register new process. */ + const auto as_type = GetAddressSpaceType(std::addressof(meta)); + RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::AddressSpaceType_64Bit || as_type == Npdm::AddressSpaceType_64BitDeprecated); + + /* Register all NSOs. */ + for (size_t i = 0; i < Nso_Count; i++) { + if (g_has_nso[i]) { + RoManager::GetInstance().AddNso(pin_id, g_nso_headers[i].module_id, info.nso_address[i], info.nso_size[i]); + } + } + } + + /* If we're overriding for HBL, perform HTML document redirection. */ + if (override_status.IsHbl()) { + /* Don't validate result, failure is okay. */ + RedirectHtmlDocumentPathForHbl(loc); + } + + /* Clear the external code for the program. */ + fssystem::DestroyExternalCode(loc.program_id); + + /* Note that we've created the program. */ + SetLaunchedBootProgram(loc.program_id); + + /* Move the process handle to output. */ + *out = info.process_handle; + + R_SUCCEED(); + } + + Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const char *path, const ldr::ProgramAttributes &attrs) { + Meta meta; + + /* Load Meta. */ + { + AMS_UNUSED(path); + + ScopedCodeMount mount(loc, attrs); + R_TRY(mount.GetResult()); + R_TRY(LoadMeta(std::addressof(meta), loc, mount.GetOverrideStatus(), attrs.platform, false)); + if (out_status != nullptr) { + *out_status = mount.GetOverrideStatus(); + } + } + + return GetProgramInfoFromMeta(out, std::addressof(meta)); + } + + Result PinProgram(PinId *out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status) { + R_UNLESS(RoManager::GetInstance().Allocate(out_id, loc, override_status), ldr::ResultMaxProcess()); + R_SUCCEED(); + } + + Result UnpinProgram(PinId id) { + R_UNLESS(RoManager::GetInstance().Free(id), ldr::ResultNotPinned()); + R_SUCCEED(); + } + + Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) { + R_UNLESS(RoManager::GetInstance().GetProcessModuleInfo(out_count, out, max_out_count, process_id), ldr::ResultNotPinned()); + R_SUCCEED(); + } + + Result GetProgramLocationAndOverrideStatusFromPinId(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id) { + R_UNLESS(RoManager::GetInstance().GetProgramLocationAndStatus(out, out_status, pin_id), ldr::ResultNotPinned()); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp index 95d219cf..b20780da 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp @@ -56,7 +56,7 @@ volatile CustomizeTable C = { * - System instabilities * - NAND corruption */ -.eristaEmcMaxClock = 2265280, +.eristaEmcMaxClock = 2240000, /* Mariko CPU: * - Max Voltage in mV: diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp index 13c94636..b6057757 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp @@ -79,6 +79,8 @@ // tRC (ACTIVATE-ACTIVATE command period same bank) in ns const u32 tRC = tRPpb + tRAS; + + const u32 tRTW = !C.t6_tRTW ? 10 : tWTR_values[C.t6_tRTW-1]; // DQS output access time from CK_t/CK_c const double tDQSCK_min = 1.5; // DQS output access time from CK_t/CK_c @@ -197,7 +199,7 @@ const u32 RL = 32 - C.mem_burst_latency; // minimum number of cycles from any read command to any write command, irrespective of bank - const u32 R2W = CEIL (RL + CEIL(tDQSCK_max/tCK_avg) + BL/2 - WL + tWPRE + FLOOR(tRPST)); + const u32 R2W = WL + BL/2 + 1 + CEIL(tRTW/tCK_avg); // Delay Time From WRITE-to-READ const u32 W2R = WL + BL/2 + 1 + CEIL(tWTR/tCK_avg); diff --git a/Source/sys-clk/manager/Makefile b/Source/sys-clk/manager/Makefile index 52f9880e..bfd99a64 100644 --- a/Source/sys-clk/manager/Makefile +++ b/Source/sys-clk/manager/Makefile @@ -43,7 +43,7 @@ SOURCES := src ../common/src ../common/src/client RESOURCES := resources DATA := data INCLUDES := ../common/include -APP_TITLE := sys-clk-OCS2 Manager +APP_TITLE := Horizon OC Manager APP_AUTHOR := Souldbminer, meha, b0rd2dEAth and RetroNX Team ROMFS := $(BUILD)/romfs diff --git a/Source/sys-clk/overlay/Makefile b/Source/sys-clk/overlay/Makefile index 35538c0e..0cc33c74 100644 --- a/Source/sys-clk/overlay/Makefile +++ b/Source/sys-clk/overlay/Makefile @@ -26,7 +26,7 @@ DATA := data INCLUDES := ../common/include EXEFS_SRC := exefs_src -APP_TITLE := sys-clk-ocs2 +APP_TITLE := Horizon OC NO_ICON := 1 # This location should reflect where you place the libultrahand directory (lib can vary between projects). diff --git a/Source/sys-clk/overlay/src/ui/gui/base_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/base_gui.cpp index 0f707f19..d32986b2 100644 --- a/Source/sys-clk/overlay/src/ui/gui/base_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/base_gui.cpp @@ -36,7 +36,7 @@ std::string getVersionString() { void BaseGui::preDraw(tsl::gfx::Renderer* renderer) { // renderer->drawBitmap(LOGO_X, LOGO_Y, LOGO_WIDTH, LOGO_HEIGHT, logo_rgba_bin); - renderer->drawString("sys-clk-ocs2 overlay", false, LOGO_X, LOGO_Y, LOGO_LABEL_FONT_SIZE, renderer->a(TEXT_COLOR)); + renderer->drawString("Horizon OC overlay", false, LOGO_X, LOGO_Y, LOGO_LABEL_FONT_SIZE, renderer->a(TEXT_COLOR)); // renderer->drawString(TARGET_VERSION, false, VERSION_X, VERSION_Y, VERSION_FONT_SIZE, tsl::bannerVersionTextColor); } diff --git a/Source/sys-clk/overlay/src/ui/gui/fatal_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/fatal_gui.cpp index 9804858c..a1f225a0 100644 --- a/Source/sys-clk/overlay/src/ui/gui/fatal_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/fatal_gui.cpp @@ -23,7 +23,7 @@ void FatalGui::openWithResultCode(std::string tag, Result rc) info.append(rcStr, snprintf(rcStr, sizeof(rcStr), "\n\n[0x%x] %04d-%04d", rc, R_MODULE(rc), R_DESCRIPTION(rc))); tsl::changeTo( - "Could not connect to sys-clk-ocs2 sysmodule.\n\n" + "Could not connect to Horizon OC sysmodule.\n\n" "\n" "Please make sure everything is\n\n" "correctly installed and enabled.", diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 00000000..ac30e7de Binary files /dev/null and b/assets/logo.png differ diff --git a/build.sh b/build.sh index 9b97824a..8353cecc 100644 --- a/build.sh +++ b/build.sh @@ -15,7 +15,7 @@ cd build/stratosphere/loader || exit 1 rm out/nintendo_nx_arm64_armv8a/release/loader.kip rm -rf build/ make -j"$(nproc)" -hactool -t kip1 out/nintendo_nx_arm64_armv8a/release/loader.kip --uncompress=ocs2.kip +hactool -t kip1 out/nintendo_nx_arm64_armv8a/release/loader.kip --uncompress=hoc.kip rm out/nintendo_nx_arm64_armv8a/release/loader.kip rm -rf build/ diff --git a/build_kip.sh b/build_kip.sh deleted file mode 100644 index e69de29b..00000000