diff --git a/README.md b/README.md index 3c0a3b20..a5f88766 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. -## DISCLAIMER: USE AT YOUR OWN RISK! +**DISCLAIMER: USE AT YOUR OWN RISK!** - Overclocking in general will shorten the lifespan of some hardware components. @@ -119,9 +119,9 @@ Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW.
Grab necessary patches from the repo, then compile sys-clk, ReverseNX-RT and Atmosphere loader with devkitpro. -If you are to install nro forwarders, stub `ValidateAcidSignature()` with `R_SUCCEED();` in `Atmosphere/stratosphere/loader/source/ldr_meta.cpp` to make them work again. +Before compiling Atmosphere loader, run `patch.py` in `Atmosphere/stratosphere/loader/source/` to insert oc module into loader sysmodule. -Uncompress the kip to make it work with configurator: `hactool -t kip1 Atmosphere/stratosphere/loader/out/nintendo_nx_arm64_armv8a/release/loader.kip --uncompress=./loader.kip` +When compilation is done, uncompress the kip to make it work with configurator: `hactool -t kip1 Atmosphere/stratosphere/loader/out/nintendo_nx_arm64_armv8a/release/loader.kip --uncompress=./loader.kip`
@@ -134,4 +134,5 @@ Uncompress the kip to make it work with configurator: `hactool -t kip1 Atmospher - 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 diff --git a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp b/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp deleted file mode 100644 index dbdac49b..00000000 --- a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp +++ /dev/null @@ -1,753 +0,0 @@ -/* - * 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 "oc/ldr_oc_suite.hpp" - -namespace ams::ldr { - - namespace { - - /* Convenience defines. */ - constexpr size_t SystemResourceSizeMax = 0x1FE00000; - - /* Types. */ - enum NsoIndex { - Nso_Rtld = 0, - Nso_Main = 1, - Nso_SubSdk0 = 2, - Nso_SubSdk1 = 3, - Nso_SubSdk2 = 4, - Nso_SubSdk3 = 5, - Nso_SubSdk4 = 6, - Nso_SubSdk5 = 7, - Nso_SubSdk6 = 8, - Nso_SubSdk7 = 9, - Nso_SubSdk8 = 10, - Nso_SubSdk9 = 11, - Nso_Sdk = 12, - Nso_Count, - }; - - constexpr inline const char *NsoPaths[Nso_Count] = { - ENCODE_ATMOSPHERE_CODE_PATH("/rtld"), - ENCODE_ATMOSPHERE_CODE_PATH("/main"), - 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()); - - /* 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; - - /* 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))); - - /* 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; - } - - *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) { - /* 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, 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) { - /* 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])); - } - } - - /* 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)); - } - - } - - /* 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) { - /* Mount code. */ - AMS_UNUSED(path); - ScopedCodeMount mount(loc, override_status); - R_TRY(mount.GetResult()); - - /* Load meta, possibly from cache. */ - Meta meta; - R_TRY(LoadMetaFromCache(std::addressof(meta), loc, override_status)); - - /* 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) { - Meta meta; - - /* Load Meta. */ - { - AMS_UNUSED(path); - - ScopedCodeMount mount(loc); - R_TRY(mount.GetResult()); - R_TRY(LoadMeta(std::addressof(meta), loc, mount.GetOverrideStatus())); - 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/Makefile b/Source/Atmosphere/stratosphere/loader/source/oc/Makefile index 9496113d..57863400 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/Makefile +++ b/Source/Atmosphere/stratosphere/loader/source/oc/Makefile @@ -1,9 +1,49 @@ -export CC := g++ +TARGET_EXEC := test -all: test +BUILD_DIR := ./build +SRC_DIRS := ./ -test: - $(CC) ldr_oc_suite.cpp test.cpp -o ./test -O2 -std=c++20 -Wall +# Find all the C and C++ files we want to compile +# Note the single quotes around the * expressions. Make will incorrectly expand these otherwise. +SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s') +# String substitution for every C/C++ file. +# As an example, hello.cpp turns into ./build/hello.cpp.o +OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) + +# String substitution (suffix version without %). +# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d +DEPS := $(OBJS:.o=.d) + +# Every folder in ./src will need to be passed to GCC so that it can find header files +INC_DIRS := $(shell find $(SRC_DIRS) -type d) +# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag +INC_FLAGS := $(addprefix -I,$(INC_DIRS)) + +CPPFLAGS := $(INC_FLAGS) -Wall -Werror -std=c++20 -Og -g + +# The final build step. +$(TARGET_EXEC): $(OBJS) + @echo "Linking $@" + @$(CXX) $(OBJS) -o $@ $(LDFLAGS) + +# Build step for C source +$(BUILD_DIR)/%.c.o: %.c + @mkdir -p $(dir $@) + @echo "$<" + @$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +# Build step for C++ source +$(BUILD_DIR)/%.cpp.o: %.cpp + @mkdir -p $(dir $@) + @echo "$<" + @$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ + +.PHONY: clean clean: - rm ./test + @rm -r $(BUILD_DIR) $(TARGET_EXEC) + +# Include the .d makefiles. The - at the front suppresses the errors of missing +# Makefiles. Initially, all the .d files will be missing, and we don't want those +# errors to show up. +-include $(DEPS) \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_customize.inl b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp similarity index 65% rename from Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_customize.inl rename to Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp index ccb02f49..41731e13 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_customize.inl +++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp @@ -1,28 +1,43 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 "customize.hpp" + namespace ams::ldr::oc { -#include "mtc_empty_table.inl" -static const volatile CustomizeTable C = { + +volatile CustomizeTable C = { /* DRAM Timing: * AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density (Default). * AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density. - * ENTIRE_TABLE_ERISTA/ENTIRE_TABLE_MARIKO: - * Replace the entire max mtc table with customized one (provided by user). */ .mtcConf = AUTO_ADJ_MARIKO_SAFE, /* Mariko CPU: * - Max Clock in kHz: * Default: 1785000 - * >= 2193000 will enable overvolting (> 1120 mV) + * >= 2397000 will enable overvolting (> 1120 mV) * - Boost Clock in kHz: * Default: 1785000 * Boost clock will be applied when applications request higher CPU frequency for quicker loading. * - Max Voltage in mV: * Default voltage: 1120 - * Haven't tested anything higher than 1220. */ .marikoCpuMaxClock = 2397000, .marikoCpuBoostClock = 1785000, -.marikoCpuMaxVolt = 1220, +.marikoCpuMaxVolt = 1235, /* Mariko GPU: * - Max Clock in kHz: @@ -44,13 +59,12 @@ static const volatile CustomizeTable C = { .marikoEmcMaxClock = 1996800, /* Erista CPU: - * Untested and not enabled by default. + * Not tested but enabled by default. * - Enable Overclock - * Require modificaitions towards NewCpuTables! * - Max Voltage in mV */ -.eristaCpuOCEnable = 0, -.eristaCpuMaxVolt = 0, +.eristaCpuOCEnabled= 1, +.eristaCpuMaxVolt = 1257, /* Erista EMC: * - RAM Clock in kHz @@ -67,7 +81,6 @@ static const volatile CustomizeTable C = { */ .eristaEmcMaxClock = 1862400, .eristaEmcVolt = 0, - -.eristaMtc = reinterpret_cast(const_cast(EmptyMtcTable)), }; + } \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp new file mode 100644 index 00000000..f9f417d8 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 + +#define CUST_REV 2 + +#include "oc_suite_common.hpp" + +namespace ams::ldr::oc { + +#include "mtc_timing_table.hpp" + +enum MtcConfig { + AUTO_ADJ_MARIKO_SAFE = 0, + AUTO_ADJ_MARIKO_4266 = 1, +}; + +typedef struct __attribute__((packed)) CustomizeTable { + u8 cust[4] = {'C', 'U', 'S', 'T'}; + u16 custRev = CUST_REV; + u16 mtcConf = AUTO_ADJ_MARIKO_SAFE; + u32 marikoCpuMaxClock; + u32 marikoCpuBoostClock; + u32 marikoCpuMaxVolt; + u32 marikoGpuMaxClock; + u32 marikoEmcMaxClock; + u32 eristaCpuOCEnabled; + u32 eristaCpuMaxVolt; + u32 eristaEmcMaxClock; + u32 eristaEmcVolt; +} CustomizeTable; + +extern volatile CustomizeTable C; + +} \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.cpp deleted file mode 100644 index f4a3cb31..00000000 --- a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.cpp +++ /dev/null @@ -1,1271 +0,0 @@ -/* - * Copyright (C) Switch-OC-Suite - * - * 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 . - */ - -//#define EXPERIMENTAL - -#ifndef ATMOSPHERE_IS_STRATOSPHERE -#include "ldr_oc_suite_test.hpp" -#else -#include -#include "ldr_oc_suite.hpp" -#include "ldr_oc_customize.inl" -#endif - -namespace ams::ldr::oc { - namespace pcv { - Result MemPllmLimitHandler(u32* ptr) { - clk_pll_param* entry = reinterpret_cast(ptr); - R_UNLESS(entry->max_0 == entry->max_1, ldr::ResultInvalidMemPllmEntry()); - - // Double the max clk simply - u32 max_clk = entry->max_0 * 2; - entry->max_0 = max_clk; - entry->max_1 = max_clk; - R_SUCCEED(); - } - - template - Result MtcOverwrite(M* des, M* src) { - constexpr u32 mtc_magic = 0x5F43544D; - R_UNLESS(src->rev == mtc_magic, ldr::ResultInvalidMtcMagic()); - - // Ignore params from dvfs_ver to clock_src; - for (size_t offset = offsetof(M, clk_src_emc); offset < sizeof(M); offset += sizeof(u32)) { - u32* src_ent = reinterpret_cast(reinterpret_cast(src) + offset); - u32* des_ent = reinterpret_cast(reinterpret_cast(des) + offset); - u32 src_val = *src_ent; - - constexpr u32 ignore_val = UINT32_MAX; - if (src_val != ignore_val) { - PatchOffset(des_ent, src_val); - } - } - - R_SUCCEED(); - } - } - - namespace pcv::Mariko { - constexpr u32 CpuClkOSLimit = 1785'000; - constexpr u32 CpuClkOfficial = 1963'500; - constexpr u32 CpuVoltOfficial = 1120; - // constexpr u32 GpuClkOSLimit = 921'600; - constexpr u32 GpuClkOfficial = 1267'200; - constexpr u32 MemClkOSLimit = 1600'000; - constexpr u32 MemClkOSAlt = 1331'200; - constexpr u32 GpuClkPllLimit = 1300'000'000; - constexpr u32 MemClkPllmLimit = 2133'000'000; - - constexpr u32 CommonDvfsEntryCnt = 32; - /* CPU */ - constexpr u32 OldCpuDvfsEntryCnt = 18; - constexpr cpu_freq_cvb_table_t NewCpuTables[] = { - // OldCpuTables - // { 204000, { 721589, -12695, 27 }, {} }, - // { 306000, { 747134, -14195, 27 }, {} }, - // { 408000, { 776324, -15705, 27 }, {} }, - // { 510000, { 809160, -17205, 27 }, {} }, - // { 612000, { 845641, -18715, 27 }, {} }, - // { 714000, { 885768, -20215, 27 }, {} }, - // { 816000, { 929540, -21725, 27 }, {} }, - // { 918000, { 976958, -23225, 27 }, {} }, - // { 1020000, { 1028021, -24725, 27 }, { 1120000 } }, - // { 1122000, { 1082730, -26235, 27 }, { 1120000 } }, - // { 1224000, { 1141084, -27735, 27 }, { 1120000 } }, - // { 1326000, { 1203084, -29245, 27 }, { 1120000 } }, - // { 1428000, { 1268729, -30745, 27 }, { 1120000 } }, - // { 1581000, { 1374032, -33005, 27 }, { 1120000 } }, - // { 1683000, { 1448791, -34505, 27 }, { 1120000 } }, - // { 1785000, { 1527196, -36015, 27 }, { 1120000 } }, - // { 1887000, { 1609246, -37515, 27 }, { 1120000 } }, - // { 1963500, { 1675751, -38635, 27 }, { 1120000 } }, - { 2091000, { 1785520, -40523, 27 }, { 1120000 } }, - { 2193000, { 1878755, -42027, 27 }, { 1120000 } }, - { 2295000, { 1975655, -43531, 27 }, { 1120000 } }, - { 2397000, { 2076220, -45036, 27 }, { 1120000 } }, - }; - static_assert(sizeof(NewCpuTables) <= sizeof(cpu_freq_cvb_table_t) * (CommonDvfsEntryCnt - OldCpuDvfsEntryCnt)); - - /* GPU */ - constexpr u32 OldGpuDvfsEntryCnt = 17; - constexpr gpu_cvb_pll_table_t NewGpuTables[] = { - // OldGpuTables - // { 76800, {}, { 610000, } }, - // { 153600, {}, { 610000, } }, - // { 230400, {}, { 610000, } }, - // { 307200, {}, { 610000, } }, - // { 384000, {}, { 610000, } }, - // { 460800, {}, { 610000, } }, - // { 537600, {}, { 801688, -10900, -163, 298, -10599, 162 } }, - // { 614400, {}, { 824214, -5743, -452, 238, -6325, 81 } }, - // { 691200, {}, { 848830, -3903, -552, 119, -4030, -2 } }, - // { 768000, {}, { 891575, -4409, -584, 0, -2849, 39 } }, - // { 844800, {}, { 940071, -5367, -602, -60, -63, -93 } }, - // { 921600, {}, { 986765, -6637, -614, -179, 1905, -13 } }, - // { 998400, {}, { 1098475, -13529, -497, -179, 3626, 9 } }, - // { 1075200, {}, { 1163644, -12688, -648, 0, 1077, 40 } }, - // { 1152000, {}, { 1204812, -9908, -830, 0, 1469, 110 } }, - // { 1228800, {}, { 1277303, -11675, -859, 0, 3722, 313 } }, - // { 1267200, {}, { 1335531, -12567, -867, 0, 3681, 559 } }, - { 1305600, {}, { 1374130, -13725, -859, 0, 4442, 576 } }, - }; - static_assert(sizeof(NewGpuTables) <= sizeof(gpu_cvb_pll_table_t) * (CommonDvfsEntryCnt - OldGpuDvfsEntryCnt)); - - /* GPU Max Clock asm Pattern: - * - * MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11) - * sf | opc | | hw | imm16 | Rd - * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0 - * 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1 - * - * MOVK W11, #0xE, LSL#16 16 0xE 0xB (11) - * sf | opc | | hw | imm16 | Rd - * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0 - * 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1 - */ - constexpr u32 gpuOfficialMarikoPattern[2] = { - 0x52820000, - 0x72A001C0 - }; - const u32 gpuMaxClockMarikoPattern[2] = { - (gpuOfficialMarikoPattern[0] & 0xFFE00000) | ((C.marikoGpuMaxClock & 0xFFFF) << 5), - (gpuOfficialMarikoPattern[1] & 0xFFE00000) | (((C.marikoGpuMaxClock >> 16) & 0xFFFF) << 5) - }; - - #define COMPARE_HIGH(val1, val2, bit_div) (((val1 ^ val2) >> bit_div) == 0) - - /* EMC */ - constexpr u32 MTC_TABLE_REV = 3; - // DvbTable is all about frequency scaling along with CPU core voltage, no need to care about this for now. - - // constexpr emc_dvb_dvfs_table_t EmcDvbTable[6] = - // { - // { 204000, { 637, 637, 637, } }, - // { 408000, { 637, 637, 637, } }, - // { 800000, { 637, 637, 637, } }, - // { 1065600, { 637, 637, 637, } }, - // { 1331200, { 650, 637, 637, } }, - // { 1600000, { 675, 650, 637, } }, - // }; - - void MtcPllmbDivHandler(MarikoMtcTable* table) { - // Calculate DIVM and DIVN (clock divisors) - // Common PLL oscillator is 38.4 MHz - // PLLMB_OUT = 38.4 MHz / PLLLMB_DIVM * PLLMB_DIVN - typedef struct { - u8 numerator : 4; - u8 denominator : 4; - } pllmb_div; - - constexpr pllmb_div div[] = { - {3, 4}, {1, 2}, {1, 4}, {0, 1} - }; - - constexpr u32 pll_osc_in = 38400; - - u32 divm {}, divn {}; - const u32 remainder = C.marikoEmcMaxClock % pll_osc_in; - for (const auto &index : div) { - if (remainder >= pll_osc_in * index.numerator / index.denominator) { - divm = index.denominator; - divn = C.marikoEmcMaxClock / pll_osc_in * divm + index.numerator; - break; - } - } - - table->pllmb_divm = divm; - table->pllmb_divn = divn; - } - - void MtcTableAutoAdjust(MarikoMtcTable* table, const MarikoMtcTable* ref) - { - /* Official Tegra X1 TRM, sign up for nvidia developer program (free) to download: - * https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual - * Section 18.11: MC Registers - * - * Retail Mariko: 200FBGA 16Gb DDP LPDDR4X SDRAM x 2 - * x16/Ch, 1Ch/die, Double-die, 2Ch, 1CS(rank), 8Gb density per die - * 64Mb x 16DQ x 8banks x 2channels = 2048MB (x32DQ) per package - * - * Devkit Mariko: 200FBGA 32Gb DDP LPDDR4X SDRAM x 2 - * x16/Ch, 1Ch/die, Quad-die, 2Ch, 2CS(rank), 8Gb density per die - * X1+ EMC can R/W to both ranks at the same time, resulting in doubled DQ - * 64Mb x 32DQ x 8banks x 2channels = 4096MB (x64DQ) per package - * - * If you have access to LPDDR4(X) specs or datasheets (from manufacturers or Google), - * you'd better calculate timings yourself rather than relying on following algorithm. - */ - - #define ADJUST_PROP(TARGET, REF) \ - (u32)(std::ceil(REF + ((C.marikoEmcMaxClock-MemClkOSAlt)*(TARGET-REF))/(MemClkOSLimit-MemClkOSAlt))) - - #define ADJUST_PARAM(TARGET, REF) \ - TARGET = ADJUST_PROP(TARGET, REF); - - #define ADJUST_PARAM_TABLE(TABLE, PARAM, REF) ADJUST_PARAM(TABLE->PARAM, REF->PARAM) - - #define ADJUST_PARAM_ALL_REG(TABLE, PARAM, REF) \ - ADJUST_PARAM_TABLE(TABLE, burst_regs.PARAM, REF) \ - ADJUST_PARAM_TABLE(TABLE, shadow_regs_ca_train.PARAM, REF) \ - ADJUST_PARAM_TABLE(TABLE, shadow_regs_rdwr_train.PARAM, REF) - - #define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE)\ - TABLE->burst_regs.PARAM = VALUE; \ - TABLE->shadow_regs_ca_train.PARAM = VALUE; \ - TABLE->shadow_regs_rdwr_train.PARAM = VALUE; - - ADJUST_PARAM_ALL_REG(table, emc_r2w, ref); - ADJUST_PARAM_ALL_REG(table, emc_w2r, ref); - ADJUST_PARAM_ALL_REG(table, emc_r2p, ref); - ADJUST_PARAM_ALL_REG(table, emc_w2p, ref); - ADJUST_PARAM_ALL_REG(table, emc_trtm, ref); - ADJUST_PARAM_ALL_REG(table, emc_twtm, ref); - ADJUST_PARAM_ALL_REG(table, emc_tratm, ref); - ADJUST_PARAM_ALL_REG(table, emc_twatm, ref); - - ADJUST_PARAM_ALL_REG(table, emc_rw2pden, ref); - - ADJUST_PARAM_ALL_REG(table, emc_tclkstop, ref); - - ADJUST_PARAM_ALL_REG(table, emc_pmacro_dll_cfg_2, ref); // EMC_DLL_CFG_2_0: level select for VDDA? - - // ADJUST_PARAM_TABLE(table, dram_timings.rl); // not used on Mariko - - ADJUST_PARAM_TABLE(table, la_scale_regs.mc_mll_mpcorer_ptsa_rate, ref); - ADJUST_PARAM_TABLE(table, la_scale_regs.mc_ptsa_grant_decrement, ref); - - // ADJUST_PARAM_TABLE(table, min_mrs_wait); // not used on LPDDR4X - // ADJUST_PARAM_TABLE(table, latency); // not used - - /* Timings that are available in or can be derived from LPDDR4X datasheet or TRM */ - { - const bool use_4266_spec = C.mtcConf == AUTO_ADJ_MARIKO_4266; - // tCK_avg (average clock period) in ns - const double tCK_avg = 1000'000. / C.marikoEmcMaxClock; - // tRPpb (row precharge time per bank) in ns - const u32 tRPpb = 18; - // tRPab (row precharge time all banks) in ns - const u32 tRPab = 21; - // tRAS (row active time) in ns - const u32 tRAS = 42; - // tRC (ACTIVATE-ACTIVATE command period same bank) in ns - const u32 tRC = tRPpb + tRAS; - // tRFCab (refresh cycle time all banks) in ns for 8Gb density - const u32 tRFCab = 280; - // tRFCpb (refresh cycle time per bank) in ns for 8Gb density - const u32 tRFCpb = 140; - // tRCD (RAS-CAS delay) in ns - const u32 tRCD = 18; - // tRRD (Active bank-A to Active bank-B) in ns - const double tRRD = use_4266_spec ? 7.5 : 10.; - // tREFpb (average refresh interval per bank) in ns for 8Gb density - const u32 tREFpb = 488; - // tREFab (average refresh interval all 8 banks) in ns for 8Gb density - // const u32 tREFab = tREFpb * 8; - // #_of_rows per die for 8Gb density - const u32 numOfRows = 65536; - // {REFRESH, REFRESH_LO} = max[(tREF/#_of_rows) / (emc_clk_period) - 64, (tREF/#_of_rows) / (emc_clk_period) * 97%] - // emc_clk_period = dram_clk / 2; - // 1600 MHz: 5894, but N' set to 6176 (~4.8% margin) - const u32 REFRESH = u32(std::ceil((double(tREFpb) * C.marikoEmcMaxClock / numOfRows * 1.048 / 2 - 64))) / 4 * 4; - // tPDEX2WR, tPDEX2RD (timing delay from exiting powerdown mode to a write/read command) in ns - const u32 tPDEX2 = 10; - // [Guessed] tACT2PDEN (timing delay from an activate, MRS or EMRS command to power-down entry) in ns - const u32 tACT2PDEN = 14; - // [Guessed] tPDEX2MRR (timing delay from exiting powerdown mode to MRR command) in ns - const double tPDEX2MRR = 28.75; - // [Guessed] tCKE2PDEN (timing delay from turning off CKE to power-down entry) in ns - const double tCKE2PDEN = 8.5; - // tXSR (SELF REFRESH exit to next valid command delay) in ns - const double tXSR = tRFCab + 7.5; - // tCKE (minimum CKE high pulse width) in ns - const u32 tCKE = 8; - // tCKELPD (minimum CKE low pulse width in SELF REFRESH) in ns - const u32 tCKELPD = 15; - // [Guessed] tPD (minimum CKE low pulse width in power-down mode) in ns - const double tPD = 7.5; - // tFAW (Four-bank Activate Window) in ns - const u32 tFAW = use_4266_spec ? 30 : 40; - - #define GET_CYCLE_CEIL(PARAM) u32(std::ceil(double(PARAM) / tCK_avg)) - - WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(tRC)); - WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(tRFCab)); - WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(tRFCpb)); - WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(tRAS)); - WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(tRPpb)); - WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD)); - WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD)); - WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD)); - WRITE_PARAM_ALL_REG(table, emc_refresh, REFRESH); - WRITE_PARAM_ALL_REG(table, emc_pre_refresh_req_cnt, REFRESH / 4); - WRITE_PARAM_ALL_REG(table, emc_pdex2wr, GET_CYCLE_CEIL(tPDEX2)); - WRITE_PARAM_ALL_REG(table, emc_pdex2rd, GET_CYCLE_CEIL(tPDEX2)); - WRITE_PARAM_ALL_REG(table, emc_act2pden,GET_CYCLE_CEIL(tACT2PDEN)); - WRITE_PARAM_ALL_REG(table, emc_cke2pden,GET_CYCLE_CEIL(tCKE2PDEN)); - WRITE_PARAM_ALL_REG(table, emc_pdex2mrr,GET_CYCLE_CEIL(tPDEX2MRR)); - WRITE_PARAM_ALL_REG(table, emc_txsr, GET_CYCLE_CEIL(tXSR)); - WRITE_PARAM_ALL_REG(table, emc_txsrdll, GET_CYCLE_CEIL(tXSR)); - WRITE_PARAM_ALL_REG(table, emc_tcke, GET_CYCLE_CEIL(tCKE)); - WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tCKELPD)); - WRITE_PARAM_ALL_REG(table, emc_tpd, GET_CYCLE_CEIL(tPD)); - WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW)); - WRITE_PARAM_ALL_REG(table, emc_trpab, GET_CYCLE_CEIL(tRPab)); - WRITE_PARAM_ALL_REG(table, emc_trefbw, REFRESH + 64); - - constexpr u32 MC_ARB_DIV = 4; // ? - table->burst_mc_regs.mc_emem_arb_timing_rcd = std::ceil(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV - 2); - table->burst_mc_regs.mc_emem_arb_timing_rp = std::ceil(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV - 1); - table->burst_mc_regs.mc_emem_arb_timing_rc = std::ceil(std::max(GET_CYCLE_CEIL(tRC), GET_CYCLE_CEIL(tRAS)+GET_CYCLE_CEIL(tRPpb)) / MC_ARB_DIV); - table->burst_mc_regs.mc_emem_arb_timing_ras = std::ceil(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV - 2); - table->burst_mc_regs.mc_emem_arb_timing_faw = std::ceil(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV - 1); - table->burst_mc_regs.mc_emem_arb_timing_rrd = std::ceil(GET_CYCLE_CEIL(tRRD) / MC_ARB_DIV - 1); - table->burst_mc_regs.mc_emem_arb_timing_rap2pre = std::ceil(table->burst_regs.emc_r2p / MC_ARB_DIV); - table->burst_mc_regs.mc_emem_arb_timing_wap2pre = std::ceil(table->burst_regs.emc_w2p / MC_ARB_DIV); - table->burst_mc_regs.mc_emem_arb_timing_r2w = std::ceil(table->burst_regs.emc_r2w / MC_ARB_DIV + 1); - table->burst_mc_regs.mc_emem_arb_timing_w2r = std::ceil(table->burst_regs.emc_w2r / MC_ARB_DIV + 1); - table->burst_mc_regs.mc_emem_arb_timing_rfcpb = std::ceil(GET_CYCLE_CEIL(tRFCpb) / MC_ARB_DIV + 1); // ? - } - - #ifdef EXPERIMENTAL - { - #define ADJUST_PARAM_ROUND2_ALL_REG(TARGET_TABLE, REF_TABLE, PARAM) \ - TARGET_TABLE->burst_regs.PARAM = \ - ((ADJUST_PROP(TARGET_TABLE->burst_regs.PARAM, REF_TABLE->burst_regs.PARAM) + 1) >> 1) << 1; \ - TARGET_TABLE->shadow_regs_ca_train.PARAM = \ - ((ADJUST_PROP(TARGET_TABLE->shadow_regs_ca_train.PARAM, REF_TABLE->shadow_regs_ca_train.PARAM) + 1) >> 1) << 1; \ - TARGET_TABLE->shadow_regs_rdwr_train.PARAM = \ - ((ADJUST_PROP(TARGET_TABLE->shadow_regs_rdwr_train.PARAM, REF_TABLE->shadow_regs_rdwr_train.PARAM) + 1) >> 1) << 1; - - #define TRIM_BIT(IN_BITS, HIGH, LOW) \ - ((IN_BITS >> LOW) & ( (1u << (HIGH - LOW + 1u)) - 1u )) - - #define ADJUST_BIT(TARGET_PARAM, REF_PARAM, HIGH, LOW) \ - ADJUST_PROP(TRIM_BIT(TARGET_PARAM, HIGH, LOW), TRIM_BIT(REF_PARAM, HIGH, LOW)) - - #define CLEAR_BIT(BITS, HIGH, LOW) \ - BITS = BITS & ~( ((1u << HIGH) << 1u) - (1u << LOW) ); - - #define ADJUST_BIT_ALL_REG_SINGLE_OP(TARGET_TABLE, REF_TABLE, PARAM, HIGH, LOW, OPERATION) \ - TARGET_TABLE->burst_regs.PARAM = \ - (ADJUST_BIT(TARGET_TABLE->burst_regs.PARAM, REF_TABLE->burst_regs.PARAM, HIGH, LOW) << LOW) OPERATION; \ - TARGET_TABLE->shadow_regs_ca_train.PARAM = \ - (ADJUST_BIT(TARGET_TABLE->shadow_regs_ca_train.PARAM, REF_TABLE->shadow_regs_ca_train.PARAM, HIGH, LOW)) << LOW OPERATION; \ - TARGET_TABLE->shadow_regs_rdwr_train.PARAM = \ - (ADJUST_BIT(TARGET_TABLE->shadow_regs_rdwr_train.PARAM, REF_TABLE->shadow_regs_rdwr_train.PARAM, HIGH, LOW)) << LOW OPERATION; - - #define ADJUST_BIT_ALL_REG_PAIR(TARGET_TABLE, REF_TABLE, PARAM, HIGH1, LOW1, HIGH2, LOW2) \ - TARGET_TABLE->burst_regs.PARAM = \ - ADJUST_BIT(TARGET_TABLE->burst_regs.PARAM, REF_TABLE->burst_regs.PARAM, HIGH1, LOW1) << LOW1 \ - | ADJUST_BIT(TARGET_TABLE->burst_regs.PARAM, REF_TABLE->burst_regs.PARAM, HIGH2, LOW2) << LOW2; \ - TARGET_TABLE->shadow_regs_ca_train.PARAM = \ - ADJUST_BIT(TARGET_TABLE->shadow_regs_ca_train.PARAM, REF_TABLE->shadow_regs_ca_train.PARAM, HIGH1, LOW1) << LOW1 \ - | ADJUST_BIT(TARGET_TABLE->shadow_regs_ca_train.PARAM, REF_TABLE->shadow_regs_ca_train.PARAM, HIGH2, LOW2) << LOW2; \ - TARGET_TABLE->shadow_regs_rdwr_train.PARAM = \ - ADJUST_BIT(TARGET_TABLE->shadow_regs_rdwr_train.PARAM, REF_TABLE->shadow_regs_rdwr_train.PARAM, HIGH1, LOW1) << LOW1 \ - | ADJUST_BIT(TARGET_TABLE->shadow_regs_rdwr_train.PARAM, REF_TABLE->shadow_regs_rdwr_train.PARAM, HIGH2, LOW2) << LOW2; - - /* For latency allowance */ - #define ADJUST_INVERSE(TARGET) (TARGET * (MemClkOSLimit / 1000) / (C.marikoEmcMaxClock / 1000)) - - /* emc_wdv, emc_wsv, emc_wev, emc_wdv_mask, - emc_quse, emc_quse_width, emc_ibdly, emc_obdly, - emc_einput, emc_einput_duration, emc_qrst, emc_qsafe, - emc_rdv, emc_rdv_mask, emc_rdv_early, emc_rdv_early_mask */ - ADJUST_PARAM_ROUND2_ALL_REG(target_table, ref_table, emc_wdv); - ADJUST_PARAM_ROUND2_ALL_REG(target_table, ref_table, emc_wsv); - ADJUST_PARAM_ROUND2_ALL_REG(target_table, ref_table, emc_wev); - ADJUST_PARAM_ROUND2_ALL_REG(target_table, ref_table, emc_wdv_mask); - - ADJUST_PARAM_ALL_REG(target_table, ref_table, emc_quse); - ADJUST_PARAM_ALL_REG(target_table, ref_table, emc_quse_width); - - ADJUST_BIT_ALL_REG_SINGLE_OP(target_table, ref_table, emc_ibdly, 6,0, | (1 << 28)); - ADJUST_BIT_ALL_REG_SINGLE_OP(target_table, ref_table, emc_obdly, 5,0, | (1 << 28)); - - ADJUST_PARAM_ALL_REG(target_table, ref_table, emc_einput); - ADJUST_PARAM_ALL_REG(target_table, ref_table, emc_einput_duration); - - ADJUST_BIT_ALL_REG_SINGLE_OP(target_table, ref_table, emc_qrst, 6,0, | (6 << 16)); - ADJUST_PARAM_ALL_REG(target_table, ref_table, emc_qsafe); - - ADJUST_PARAM_ALL_REG(target_table, ref_table, emc_rdv); - target_table->burst_regs.emc_rdv_mask = target_table->burst_regs.emc_rdv + 2; - target_table->shadow_regs_ca_train.emc_rdv_mask = target_table->shadow_regs_ca_train.emc_rdv + 2; - target_table->shadow_regs_rdwr_train.emc_rdv_mask = target_table->shadow_regs_rdwr_train.emc_rdv + 2; - - ADJUST_PARAM_ALL_REG(target_table, ref_table, emc_rdv_early); - target_table->burst_regs.emc_rdv_early_mask = target_table->burst_regs.emc_rdv_early + 2; - target_table->shadow_regs_ca_train.emc_rdv_early_mask = target_table->shadow_regs_ca_train.emc_rdv_early + 2; - target_table->shadow_regs_rdwr_train.emc_rdv_early_mask = target_table->shadow_regs_rdwr_train.emc_rdv_early + 2; - - /* emc_pmacro_..., - emc_zcal_wait_cnt, emc_mrs_wait_cnt(2), - emc_pmacro_autocal_cfg_common, emc_dyn_self_ref_control, emc_qpop, emc_pmacro_cmd_pad_tx_ctrl, - emc_tr_timing_0, emc_tr_rdv, emc_tr_qpop, emc_tr_rdv_mask, emc_tr_qsafe, emc_tr_qrst, - emc_training_vref_settle */ - /* DDLL values */ - { - #define OFFSET_ALL_REG(PARAM) \ - offsetof(MarikoMtcTable, burst_regs.PARAM), \ - offsetof(MarikoMtcTable, shadow_regs_ca_train.PARAM), \ - offsetof(MarikoMtcTable, shadow_regs_rdwr_train.PARAM) \ - - /* Section 1: adjust HI bits: BIT 26:16 */ - const std::vector ddll_high = { - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dq_rank1_4), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dq_rank1_5), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank0_4), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank0_5), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank1_4), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank1_5), - OFFSET_ALL_REG(emc_pmacro_ddll_long_cmd_0), - OFFSET_ALL_REG(emc_pmacro_ddll_long_cmd_1), - OFFSET_ALL_REG(emc_pmacro_ddll_long_cmd_2), - OFFSET_ALL_REG(emc_pmacro_ddll_long_cmd_3), - OFFSET_ALL_REG(emc_pmacro_ddll_long_cmd_4), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_0), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_1), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_2), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_3), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_4), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_5), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_0), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_1), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_2), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_3), - }; - for (const auto &offset : ddll_high) - { - u32 *ddll = reinterpret_cast(reinterpret_cast(table) + offset); - u32 *ddll_ref = reinterpret_cast(reinterpret_cast(ref) + offset); - u16 adjusted_ddll = ADJUST_BIT(*ddll, *ddll_ref, 26,16) & ((1 << (26-16)) - 1); - CLEAR_BIT(*ddll, 26,16) - *ddll |= adjusted_ddll << 16; - } - - /* Section 2: adjust LOW bits: BIT 10:0 */ - const std::vector ddll_low = { - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dq_rank1_4), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dq_rank1_5), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank0_0), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank0_1), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank0_3), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank0_4), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank1_0), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank1_1), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank1_3), - OFFSET_ALL_REG(emc_pmacro_ob_ddll_long_dqs_rank1_4), - OFFSET_ALL_REG(emc_pmacro_ddll_long_cmd_0), - OFFSET_ALL_REG(emc_pmacro_ddll_long_cmd_1), - OFFSET_ALL_REG(emc_pmacro_ddll_long_cmd_2), - OFFSET_ALL_REG(emc_pmacro_ddll_long_cmd_3), - OFFSET_ALL_REG(emc_pmacro_ddll_long_cmd_4), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_0), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_1), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_2), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_3), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_4), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_5), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_0), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_1), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_2), - offsetof(MarikoMtcTable, trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_3), - }; - for (const auto &offset : ddll_low) - { - u32 *ddll = reinterpret_cast(reinterpret_cast(table) + offset); - u32 *ddll_ref = reinterpret_cast(reinterpret_cast(ref) + offset); - u16 adjusted_ddll = ADJUST_BIT(*ddll, *ddll_ref, 10,0) & ((1 << 10) - 1); - CLEAR_BIT(*ddll, 10,0) - *ddll |= adjusted_ddll; - } - } - - ADJUST_BIT_ALL_REG_PAIR(target_table, ref_table, emc_zcal_wait_cnt, 21,16, 10,0) - ADJUST_BIT_ALL_REG_PAIR(target_table, ref_table, emc_mrs_wait_cnt, 21,16, 10,0) - ADJUST_BIT_ALL_REG_PAIR(target_table, ref_table, emc_mrs_wait_cnt2, 21,16, 10,0) - - ADJUST_BIT_ALL_REG_SINGLE_OP(target_table, ref_table, emc_auto_cal_channel, 5,0, | 0xC1E00300) - ADJUST_BIT_ALL_REG_SINGLE_OP(target_table, ref_table, emc_pmacro_autocal_cfg_common, 5,0, | 8 << 8) - - ADJUST_BIT_ALL_REG_PAIR(target_table, ref_table, emc_dyn_self_ref_control, 31,31, 15,0) - - ADJUST_PARAM_ALL_REG(target_table, ref_table, emc_qpop); - - ADJUST_BIT_ALL_REG_SINGLE_OP(target_table, ref_table, emc_tr_timing_0, 9,0, | 0x1186100) - - ADJUST_PARAM_ALL_REG(target_table, ref_table, emc_tr_rdv); - target_table->burst_regs.emc_tr_rdv_mask = target_table->burst_regs.emc_tr_rdv + 2; - target_table->shadow_regs_ca_train.emc_tr_rdv_mask = target_table->shadow_regs_ca_train.emc_tr_rdv + 2; - target_table->shadow_regs_rdwr_train.emc_tr_rdv_mask = target_table->shadow_regs_rdwr_train.emc_tr_rdv + 2; - - ADJUST_PARAM_ALL_REG(target_table, ref_table, emc_tr_qpop); - ADJUST_PARAM_ALL_REG(target_table, ref_table, emc_tr_qsafe); - ADJUST_BIT_ALL_REG_SINGLE_OP(target_table, ref_table, emc_tr_qrst, 6,0, | (6 << 16)); - - ADJUST_BIT_ALL_REG_SINGLE_OP(target_table, ref_table, emc_training_vref_settle, 15,0, | (4 << 16)); - - /* External Memory Arbitration Configuration */ - /* BIT 20:16 - EXTRA_TICKS_PER_UPDATE: 0 */ - /* BIT 8:0 - CYCLES_PER_UPDATE: 12(1600MHz), 10(1331.2MHz) */ - ADJUST_PARAM_TABLE(table, burst_mc_regs.mc_emem_arb_cfg, ref); - - /* External Memory Arbitration Configuration: Direction Arbiter: Turns */ - /* BIT 31:24 - W2R_TURN: approx. mc_emem_arb_timing_w2r */ - /* BIT 23:16 - R2W_TURN: approx. mc_emem_arb_timing_r2w */ - /* BIT 15:8 - W2W_TURN: 0 */ - /* BIT 7:0 - R2R_TURN: 0 */ - { - u8 w2r_turn = table->burst_mc_regs.mc_emem_arb_timing_w2r; - u8 r2w_turn = table->burst_mc_regs.mc_emem_arb_timing_r2w; - table->burst_mc_regs.mc_emem_arb_da_turns = w2r_turn << 24 | r2w_turn << 16; - } - - /* External Memory Arbitration Configuration: Direction Arbiter: Covers */ - /* BIT 23:16 - RCD_W_COVER: 13(1600MHz), 11(1331.2MHz) */ - /* BIT 15:8 - RCD_R_COVER: 8(1600MHz), 7(1331.2MHz) */ - /* BIT 7:0 - RC_COVER: approx. mc_emem_arb_timing_rc, 12(1600MHz), 9(1331.2MHz) */ - { - u32 param_max = table->burst_mc_regs.mc_emem_arb_da_covers; - u32 param_ref = ref->burst_mc_regs.mc_emem_arb_da_covers; - u8 rcd_w_cover = ADJUST_BIT(param_max, param_ref, 23,16); - u8 rcd_r_cover = (ADJUST_BIT(param_max, param_ref, 23,16) + 3) / 2; - u8 rc_cover = table->burst_mc_regs.mc_emem_arb_timing_rc; - table->burst_mc_regs.mc_emem_arb_da_covers = rcd_w_cover << 16 | rcd_r_cover << 8 | rc_cover; - } - - /* External Memory Arbitration Configuration: Miscellaneous Thresholds (0) */ - /* BIT 20:16 - PRIORITY_INVERSION_ISO_THRESHOLD: 12(1600MHz), 10(1331.2MHz) */ - /* BIT 14:8 - PRIORITY_INVERSION_THRESHOLD: 36(1600MHz), 30(1331.2MHz) */ - /* BIT 7:0 - BC2AA_HOLDOFF_THRESHOLD: set to mc_emem_arb_timing_rc */ - { - u32 param_max = table->burst_mc_regs.mc_emem_arb_misc0; - u32 param_ref = ref->burst_mc_regs.mc_emem_arb_misc0; - u8 priority_inversion_iso_threshold = ADJUST_BIT(param_max, param_ref, 20,16); - u8 priority_inversion_threshold = 3 * ADJUST_BIT(param_max, param_ref, 20,16); - u8 bc2aa_holdoff_threshold = table->burst_mc_regs.mc_emem_arb_timing_rc; - CLEAR_BIT(table->burst_mc_regs.mc_emem_arb_misc0, 20,16) - CLEAR_BIT(table->burst_mc_regs.mc_emem_arb_misc0, 14,8) - CLEAR_BIT(table->burst_mc_regs.mc_emem_arb_misc0, 7,0) - table->burst_mc_regs.mc_emem_arb_misc0 |= - (priority_inversion_iso_threshold << 16 | priority_inversion_threshold << 8 | bc2aa_holdoff_threshold); - } - - /* Latency allowance settings */ - { - /* Section 1: adjust write latency */ - /* BIT 23:16 - ALLOWANCE_WRITE: 128(1600MHz), 153(1331.2MHz) */ - const uint32_t latency_write_offset[] = { - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_xusb_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_xusb_1), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_tsec_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_sdmmca_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_sdmmcaa_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_sdmmc_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_sdmmcab_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_ppcs_1), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_mpcore_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_avpc_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_gpu_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_gpu2_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_nvenc_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_nvdec_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_vic_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_isp2_1), - }; - for (uint32_t i = 0; i < sizeof(latency_write_offset)/sizeof(uint32_t); i++) - { - uint32_t *latency = reinterpret_cast(reinterpret_cast(target_table) + latency_write_offset[i]); - CLEAR_BIT(*latency, 23,16) - *latency |= ADJUST_INVERSE(128) << 16; - } - - /* Section 2: adjust read latency */ - /* BIT 7:0 - ALLOWANCE_READ */ - const uint32_t latency_read_offset[] = { - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_hc_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_hc_1), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_gpu_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_gpu2_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_vic_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_vi2_0), - offsetof(MarikoMtcTable, la_scale_regs.mc_latency_allowance_isp2_1), - }; - for (uint32_t i = 0; i < sizeof(latency_read_offset)/sizeof(uint32_t); i++) - { - uint32_t *latency = reinterpret_cast(reinterpret_cast(target_table) + latency_read_offset[i]); - uint8_t adjusted_latency = ADJUST_INVERSE(TRIM_BIT(*latency, 7,0)); - CLEAR_BIT(*latency, 7,0) - *latency |= adjusted_latency; - } - } - - /* PLLM and PLLMB control */ - { - /* - * CLK_RST_CONTROLLER_PLLM_SS_CTRL1: - * BIT 31:16 : PLLM_SDM_SSC_MAX - * BIT 15:0 : PLLM_SDM_SSC_MIN - * - * CLK_RST_CONTROLLER_PLLM_SS_CTRL2: - * BIT 31:16 : PLLM_SDM_SSC_STEP - * BIT 15:0 : PLLM_SDM_DIN - * - * pllm(b)_ss_ctrl1: - * 1365, 342 (1600MHz) - * 0xFAAB, 0xF404 (1331MHz) - * - * pllm(b)_ss_ctrl2: - * 2, 1365 (1600MHz) - * 6, 0xFAAB (1331MHz) - * - * No need to care about this if Spread Spectrum (SS) is disabled - */ - // Disable PLL Spread Spectrum Control (degrades performance) - table->pll_en_ssc = 0; - table->pllm_ss_cfg = 1 << 30; - } - - /* EMC misc. configuration */ - { - /* ? Command Trigger: MRW, MRW2: MRW_OP - [PMC] data to be written ? - * - * EMC_MRW: MRW_OP - * 1600 MHz: 0x54 - * 1331 MHz: 0x44 - * 1065 MHz: 0x34 - * 800 MHz: 0x34 - * 665 MHz: 0x14 - * 408 MHz: 0x04 - * 204 MHz: 0x04 - * - * EMC_MRW2: MRW2_OP - * 1600 MHz: 0x2D 45 5*9 - * 1331 MHz: 0x24 36 4*9 - * 1065 MHz: 0x1B 27 3*9 - * 800 MHz: 0x12 18 2*9 - * 665 MHz: 0x09 9 1*9 - * 408 MHz: 0x00 - * 204 MHz: 0x00 - */ - { - - } - - /* EMC_CFG_2 */ - /* BIT 5:3 - ZQ_EXTRA_DELAY: 6(1600MHz), 5(1331.2MHz), max possible value: 7 */ - { - CLEAR_BIT(target_table->emc_cfg_2, 5,3) - target_table->emc_cfg_2 |= 7 << 3; - } - } - } - #endif - } - - Result CpuClockVddHandler(u32* ptr) { - R_UNLESS(*(ptr + 2) == 0, ldr::ResultInvalidCpuClockVddEntry()); - R_UNLESS(*(ptr + 12) == 1525000, ldr::ResultInvalidCpuClockVddEntry()); - - if (C.marikoCpuMaxClock) - PatchOffset(ptr, C.marikoCpuMaxClock); - - R_SUCCEED(); - } - - Result CpuDvfsHandler(u32* ptr, uintptr_t nso_end_offset) { - cpu_freq_cvb_table_t* entry_1963 = reinterpret_cast(ptr); - cpu_freq_cvb_table_t* entry_free = entry_1963 + 1; - cpu_freq_cvb_table_t* entry_204 = entry_free - 18; - cpu_freq_cvb_table_t* entry_1020 = entry_204 + 8; - uintptr_t entry_end_offset = reinterpret_cast(entry_free) + sizeof(NewCpuTables) - sizeof(u32); - - R_UNLESS(entry_end_offset < nso_end_offset, ldr::ResultOutOfRange()); - R_UNLESS(*(reinterpret_cast(entry_free)) == 0, ldr::ResultInvalidCpuDvfs()); - R_UNLESS(*(reinterpret_cast(entry_204)) == 204'000, ldr::ResultInvalidCpuDvfs()); - R_UNLESS(*(reinterpret_cast(entry_end_offset)) == 0, ldr::ResultInvalidCpuDvfs()); - - if (C.marikoCpuMaxClock > CpuClkOfficial) - std::memcpy(reinterpret_cast(entry_free), NewCpuTables, sizeof(NewCpuTables)); - - // Patch CPU max volt in CPU dvfs table - cpu_freq_cvb_table_t* entry_current = entry_1020; - R_UNLESS(entry_current->cvb_pll_param.c0 == CpuVoltOfficial * 1000, ldr::ResultInvalidCpuDvfs()); - - if (C.marikoCpuMaxVolt) { - while (entry_current->cvb_pll_param.c0 == CpuVoltOfficial * 1000) { - PatchOffset(reinterpret_cast(&(entry_current->cvb_pll_param)), C.marikoCpuMaxVolt * 1000); - entry_current++; - } - } - - R_SUCCEED(); - } - - Result GpuDvfsHandler(u32* ptr, uintptr_t nso_end_offset) { - gpu_cvb_pll_table_t* entry_1267 = reinterpret_cast(ptr); - gpu_cvb_pll_table_t* entry_free = entry_1267 + 1; - gpu_cvb_pll_table_t* entry_76_8 = entry_free - 17; - uintptr_t entry_end_offset = reinterpret_cast(entry_free) + sizeof(NewGpuTables) - sizeof(u32); - - R_UNLESS(entry_end_offset < nso_end_offset, ldr::ResultOutOfRange()); - R_UNLESS(*(reinterpret_cast(entry_free)) == 0, ldr::ResultInvalidGpuDvfs()); - R_UNLESS(*(reinterpret_cast(entry_76_8)) == 76'800, ldr::ResultInvalidGpuDvfs()); - R_UNLESS(*(reinterpret_cast(entry_end_offset)) == 0, ldr::ResultInvalidGpuDvfs()); - - if (C.marikoGpuMaxClock > GpuClkOfficial) - std::memcpy(reinterpret_cast(entry_free), NewGpuTables, sizeof(NewGpuTables)); - - R_SUCCEED(); - } - - Result CpuVoltRangeHandler(u32* ptr) { - u32 value_cpu_min_volt = *(ptr - 1); - switch (value_cpu_min_volt) { - case 800: - case 637: - case 620: - case 610: - if (C.marikoCpuMaxVolt) - PatchOffset(ptr, C.marikoCpuMaxVolt); - R_SUCCEED(); - default: - R_THROW(ldr::ResultInvalidCpuMinVolt()); - } - } - - Result GpuMaxClockHandler(u32* ptr) { - u32 value = *(ptr); - u32* ptr_next = ptr + 1; - u32 value_next = *(ptr_next); - if (COMPARE_HIGH(value_next, gpuOfficialMarikoPattern[1], 5)) - { - u32 reg_id = value & ((1 << 5) - 1); - u32 reg_id_next = value_next & ((1 << 5) - 1); - if (reg_id == reg_id_next) - { - if (C.marikoGpuMaxClock) { - PatchOffset(ptr , gpuMaxClockMarikoPattern[0] | reg_id); - PatchOffset(ptr_next, gpuMaxClockMarikoPattern[1] | reg_id); - } - - R_SUCCEED(); - } - } - R_THROW(ldr::ResultInvalidGpuMaxClkPattern()); - } - - Result MtcTableHandler(u32* ptr) { - MarikoMtcTable* const mtc_table_max = reinterpret_cast(ptr - offsetof(MarikoMtcTable, rate_khz) / sizeof(u32)); - MarikoMtcTable* const mtc_table_alt = mtc_table_max - 1; - R_UNLESS(mtc_table_max->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); - R_UNLESS(mtc_table_alt->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); - R_UNLESS(mtc_table_alt->rate_khz == MemClkOSAlt, ldr::ResultInvalidMtcTable()); - - MarikoMtcTable* const table = const_cast(C.marikoMtc); - bool replace_entire_table = (C.mtcConf == ENTIRE_TABLE_MARIKO); - if (replace_entire_table) { - std::memcpy(reinterpret_cast(mtc_table_alt), reinterpret_cast(mtc_table_max), sizeof(MarikoMtcTable)); - return MtcOverwrite(mtc_table_max, table); - } - - std::memcpy(reinterpret_cast(table), reinterpret_cast(mtc_table_max), sizeof(MarikoMtcTable)); - MtcTableAutoAdjust(mtc_table_max, mtc_table_alt); - MtcPllmbDivHandler(mtc_table_max); - std::memcpy(reinterpret_cast(mtc_table_alt), reinterpret_cast(table), sizeof(MarikoMtcTable)); - R_SUCCEED(); - } - - Result DvbTableHandler(u32* ptr) { - emc_dvb_dvfs_table_t* dvb_max_entry = reinterpret_cast(ptr); - emc_dvb_dvfs_table_t* dvb_1331_entry = dvb_max_entry - 1; - - u32* dvb_1331_offset = reinterpret_cast(dvb_1331_entry); - R_UNLESS(*(dvb_1331_offset) == MemClkOSAlt, ldr::ResultInvalidDvbTable()); - - PatchOffset(dvb_1331_offset, MemClkOSLimit); - R_SUCCEED(); - } - - Result MemMaxClockHandler(u32* ptr) { - u32 value_next = *(ptr + 1); - u32 value_next2 = *(ptr + 2); - - // Mariko have 3 mtc tables (204/1331/1600 MHz), only these 3 frequencies could be set. - // Replace 1331 MHz with 1600 MHz as perf @ 1331 MHz is crap. - constexpr u32 mtc_min_volt = 1100; - constexpr u32 dvb_entry_volt = 675; - - if (value_next == mtc_min_volt) { - R_TRY(MtcTableHandler(ptr)); - } else if (value_next2 == dvb_entry_volt) { - R_TRY(DvbTableHandler(ptr)); - } - - PatchOffset(ptr, C.marikoEmcMaxClock); - R_SUCCEED(); - } - - Result GpuPllLimitHandler(u32* ptr) { - u32 value_next = *(ptr + 1); - R_UNLESS(value_next == 0, ldr::ResultInvalidGpuPllEntry()); - - // Double the max clk simply - u32 max_clk = *(ptr) * 2; - PatchOffset(ptr, max_clk); - R_SUCCEED(); - } - - void Patch(uintptr_t mapped_nso, size_t nso_size) { - enum PatchSuccessCnt { - MEM_CLOCK, - CPU_CLOCK_VDD, - CPU_TABLE, - GPU_TABLE, - CPU_MAX_VOLT, - GPU_MAX_CLOCK, - GPU_PLL_CLK, - MEM_PLL_CLK, - CNT_MAX, - }; - - u8 cnt[CNT_MAX] = {}; - - for (uintptr_t ptr = mapped_nso; - ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); - ptr += sizeof(u32)) - { - u32* ptr32 = reinterpret_cast(ptr); - u32 value = *(ptr32); - - switch (value) { - case CpuClkOSLimit: [[unlikely]] - if (R_SUCCEEDED(CpuClockVddHandler(ptr32))) - cnt[CPU_CLOCK_VDD]++; - continue; - case CpuClkOfficial: [[unlikely]] - if (R_SUCCEEDED(CpuDvfsHandler(ptr32, mapped_nso + nso_size))) - cnt[CPU_TABLE]++; - continue; - case GpuClkOfficial: [[unlikely]] - if (R_SUCCEEDED(GpuDvfsHandler(ptr32, mapped_nso + nso_size))) - cnt[GPU_TABLE]++; - continue; - case CpuVoltOfficial:[[unlikely]] - if (R_SUCCEEDED(CpuVoltRangeHandler(ptr32))) - cnt[CPU_MAX_VOLT]++; - continue; - case MemClkOSLimit: [[unlikely]] - if (R_SUCCEEDED(MemMaxClockHandler(ptr32))) - cnt[MEM_CLOCK]++; - continue; - case GpuClkPllLimit: [[unlikely]] - if (R_SUCCEEDED(GpuPllLimitHandler(ptr32))) - cnt[GPU_PLL_CLK]++; - continue; - case MemClkPllmLimit:[[unlikely]] - if (R_SUCCEEDED(MemPllmLimitHandler(ptr32))) - cnt[MEM_PLL_CLK]++; - continue; - default: [[likely]] - break; - } - - if (COMPARE_HIGH(value, gpuOfficialMarikoPattern[0], 5)) { - if (R_SUCCEEDED(GpuMaxClockHandler(ptr32))) - cnt[GPU_MAX_CLOCK]++; - continue; - } - } - - LOGGING("CpuClkOSLimit Count: %u\n"\ - "CpuClkOfficial Count: %u\n"\ - "GpuClkOfficial Count: %u\n"\ - "CpuVoltOfficial Count: %u\n"\ - "MemClkOSLimit Count: %u\n"\ - "GpuClkPllLimit Count: %u\n"\ - "MemClkPllmLimit Count: %u\n"\ - "GpuAsmPattern Count: %u", - cnt[CPU_CLOCK_VDD], - cnt[CPU_TABLE], - cnt[GPU_TABLE], - cnt[CPU_MAX_VOLT], - cnt[MEM_CLOCK], - cnt[GPU_PLL_CLK], - cnt[MEM_PLL_CLK], - cnt[GPU_MAX_CLOCK]); - - if ( - #ifndef ATMOSPHERE_IS_STRATOSPHERE - !cnt[CPU_CLOCK_VDD] || - !cnt[CPU_TABLE] || - !cnt[GPU_TABLE] || - !cnt[CPU_MAX_VOLT] || - !cnt[GPU_PLL_CLK] || - !cnt[MEM_PLL_CLK] || - !cnt[GPU_MAX_CLOCK] || - #endif - cnt[CPU_CLOCK_VDD] > 1 || - cnt[CPU_TABLE] > 1 || - cnt[GPU_TABLE] > 1 || - cnt[CPU_MAX_VOLT] > 13 || - cnt[GPU_PLL_CLK] > 1 || - cnt[MEM_PLL_CLK] > 2 || - cnt[GPU_MAX_CLOCK] > 2) - { - CRASH(); - } - } - } - - namespace pcv::Erista { - constexpr u32 CpuClkOSLimit = 1785'000; - constexpr u32 CpuVoltLimit1 = 1132; - constexpr u32 CpuVoltLimit2 = 1170; - constexpr u32 CpuVoltLimit3 = 1227; - constexpr u32 MemVoltHOS = 1125'000; - constexpr u32 MemClkOSLimit = 1600'000; - constexpr u32 MemClkPllmLimit = 1866'000'000; - - constexpr cpu_freq_cvb_table_t NewCpuTables[] = { - // OldCpuTables - // { 204000, { 721094 }, {} }, - // { 306000, { 754040 }, {} }, - // { 408000, { 786986 }, {} }, - // { 510000, { 819932 }, {} }, - // { 612000, { 852878 }, {} }, - // { 714000, { 885824 }, {} }, - // { 816000, { 918770 }, {} }, - // { 918000, { 951716 }, {} }, - // { 1020000, { 984662 }, { -2875621, 358099, -8585 } }, - // { 1122000, { 1017608 }, { -52225, 104159, -2816 } }, - // { 1224000, { 1050554 }, { 1076868, 8356, -727 } }, - // { 1326000, { 1083500 }, { 2208191, -84659, 1240 } }, - // { 1428000, { 1116446 }, { 2519460, -105063, 1611 } }, - // { 1581000, { 1130000 }, { 2889664, -122173, 1834 } }, - // { 1683000, { 1168000 }, { 5100873, -279186, 4747 } }, - // { 1785000, { 1227500 }, { 5100873, -279186, 4747 } }, - { 1887000, {}, {} }, - { 1989000, {}, {} }, - { 2091000, {}, {} }, - }; - - constexpr u32 MTC_TABLE_REV = 7; - - Result CpuDvfsHandler(u32* ptr, uintptr_t nso_end_offset) { - cpu_freq_cvb_table_t* entry_1785 = reinterpret_cast(ptr); - cpu_freq_cvb_table_t* entry_free = entry_1785 + 1; - cpu_freq_cvb_table_t* entry_204 = entry_free - 16; - uintptr_t entry_end_offset = reinterpret_cast(entry_free) + sizeof(NewCpuTables) - sizeof(u32); - - R_UNLESS(entry_end_offset < nso_end_offset, ldr::ResultOutOfRange()); - R_UNLESS(*(reinterpret_cast(entry_free)) == 0, ldr::ResultInvalidCpuDvfs()); - R_UNLESS(*(reinterpret_cast(entry_204)) == 204'000, ldr::ResultInvalidCpuDvfs()); - R_UNLESS(*(reinterpret_cast(entry_end_offset)) == 0, ldr::ResultInvalidCpuDvfs()); - - if (C.eristaCpuOCEnable) - std::memcpy(reinterpret_cast(entry_free), NewCpuTables, sizeof(NewCpuTables)); - - R_SUCCEED(); - } - - Result CpuVoltRangeHandler(u32* ptr) { - u32 value_cpu_min_volt = *(ptr - 1); - switch (value_cpu_min_volt) { - case 950: - case 850: - case 825: - case 810: - if (C.eristaCpuMaxVolt) - PatchOffset(ptr, C.eristaCpuMaxVolt); - R_SUCCEED(); - default: - LOGGING("Invalid min voltage: %u @%p!", *(ptr-1), ptr-1); - R_THROW(ldr::ResultInvalidCpuMinVolt()); - } - } - - Result MtcTableHandler(u32* ptr) { - u32 khz_list[] = { 1600000, 1331200, 1065600, 800000, 665600, 408000, 204000, 102000, 68000, 40800 }; - u32 khz_list_size = sizeof(khz_list) / sizeof(u32); - EristaMtcTable* table_list[khz_list_size]; - table_list[0] = reinterpret_cast(ptr - offsetof(EristaMtcTable, rate_khz) / sizeof(u32)); - - for (u32 i = 1; i < khz_list_size; i++ ) { - table_list[i] = table_list[i-1] - 1; - R_UNLESS(table_list[i]->rate_khz == khz_list[i], ldr::ResultInvalidMtcTable()); - R_UNLESS(table_list[i]->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); - } - - // Make room for new mtc table, discarding useless 40.8 MHz table - for (u32 i = khz_list_size - 1; i > 0; i--) - std::memcpy(static_cast(table_list[i]), static_cast(table_list[i - 1]), sizeof(EristaMtcTable)); - - bool replace_entire_table = (C.mtcConf == ENTIRE_TABLE_ERISTA); - if (replace_entire_table) { - EristaMtcTable* const mtc_table_max = reinterpret_cast(ptr - offsetof(EristaMtcTable, rate_khz) / sizeof(u32)); - EristaMtcTable* const table = const_cast(C.eristaMtc); - return MtcOverwrite(mtc_table_max, table); - } - - R_SUCCEED(); - } - - Result MemMaxClockHandler(u32* ptr) { - u32 value_next = *(ptr + 1); - constexpr u32 mtc_min_volt = 887; - - if (value_next == mtc_min_volt) - R_TRY(MtcTableHandler(ptr)); - - if (C.eristaEmcMaxClock > MemClkOSLimit) - PatchOffset(ptr, C.eristaEmcMaxClock); - - R_SUCCEED(); - } - - Result MemVoltHandler(u32* ptr) { - u32 emc_uv = C.eristaEmcVolt; - if (emc_uv) { - constexpr u32 uv_step = 12'500; - if (emc_uv % uv_step) - emc_uv = emc_uv / uv_step * uv_step; - - PatchOffset(ptr, emc_uv); - } - - R_SUCCEED(); - } - - void Patch(uintptr_t mapped_nso, size_t nso_size) { - enum PatchSuccessCnt { - CPU_CLOCK, - CPU_MAX_VOLT, - MEM_CLOCK, - MEM_VOLT, - MEM_PLL_CLK, - CNT_MAX, - }; - - u8 cnt[CNT_MAX] = {}; - - for (uintptr_t ptr = mapped_nso; - ptr <= mapped_nso + nso_size - sizeof(EristaMtcTable); - ptr += sizeof(u32)) - { - u32* ptr32 = reinterpret_cast(ptr); - u32 value = *(ptr32); - - switch (value) { - case CpuClkOSLimit: [[unlikely]] - if (R_SUCCEEDED(CpuDvfsHandler(ptr32, mapped_nso + nso_size))) - cnt[CPU_CLOCK]++; - continue; - case CpuVoltLimit1: [[unlikely]] - case CpuVoltLimit2: [[unlikely]] - case CpuVoltLimit3: [[unlikely]] - if (R_SUCCEEDED(CpuVoltRangeHandler(ptr32))) - cnt[CPU_MAX_VOLT]++; - continue; - case MemClkOSLimit: [[unlikely]] - if (R_SUCCEEDED(MemMaxClockHandler(ptr32))) - cnt[MEM_CLOCK]++; - continue; - case MemVoltHOS: [[unlikely]] - if (R_SUCCEEDED(MemVoltHandler(ptr32))) - cnt[MEM_VOLT]++; - continue; - case MemClkPllmLimit: [[unlikely]] - if (R_SUCCEEDED(MemPllmLimitHandler(ptr32))) - cnt[MEM_PLL_CLK]++; - continue; - default: [[likely]] - break; - } - } - - LOGGING("CpuClkOSLimit Count: %u\n"\ - "CpuVoltLimit* Count: %u\n"\ - "MemClkOSLimit Count: %u\n"\ - "MemVoltHOS Count: %u\n"\ - "MemClkPllmLimit Count: %u", - cnt[CPU_CLOCK], - cnt[CPU_MAX_VOLT], - cnt[MEM_CLOCK], - cnt[MEM_VOLT], - cnt[MEM_PLL_CLK]); - - if ( - #ifndef ATMOSPHERE_IS_STRATOSPHERE - !cnt[CPU_CLOCK] || - !cnt[CPU_MAX_VOLT] || - !cnt[MEM_CLOCK] || - !cnt[MEM_VOLT] || - !cnt[MEM_PLL_CLK] || - #endif - cnt[CPU_CLOCK] > 1 || - cnt[MEM_VOLT] > 2 || - cnt[MEM_PLL_CLK] > 2) - { - CRASH(); - } - } - } - - namespace pcv { - void SafetyCheck() { - if (C.custRev != CUST_REV || - C.marikoCpuMaxVolt >= 1300 || - C.eristaCpuMaxVolt >= 1400 || - (C.eristaEmcVolt && - (C.eristaEmcVolt < 600'000 || C.eristaEmcVolt > 1250'000))) - { - CRASH(); - } - } - - void Patch(uintptr_t mapped_nso, size_t nso_size) { - #ifdef ATMOSPHERE_IS_STRATOSPHERE - SafetyCheck(); - bool isMariko = (spl::GetSocType() == spl::SocType_Mariko); - if (isMariko) - Mariko::Patch(mapped_nso, nso_size); - else - Erista::Patch(mapped_nso, nso_size); - #endif - } - } - - namespace ptm { - void Patch(uintptr_t mapped_nso, size_t nso_size) { - #ifdef ATMOSPHERE_IS_STRATOSPHERE - bool isMariko = (spl::GetSocType() == spl::SocType_Mariko); - if (!isMariko) - return; - #endif - - perf_conf_entry* confTable = 0; - constexpr u32 entryCnt = 16; - constexpr u32 cpuPtmDefault = 1020'000'000; - constexpr u32 cpuPtmDevOC = 1224'000'000; - constexpr u32 cpuPtmBoost = 1785'000'000; - const u32 cpuPtmBoostNew = (C.marikoCpuBoostClock ? - C.marikoCpuBoostClock * 1000 : cpuPtmBoost); - constexpr u32 memPtmLimit = 1600'000'000; - constexpr u32 memPtmAlt = 1331'200'000; - constexpr u32 memPtmClamp = 1065'600'000; - const u32 memPtmMax = (C.marikoEmcMaxClock ? - C.marikoEmcMaxClock * 1000 : memPtmLimit); - - enum PatchSuccessCnt { - CPU_BOOST, - MEM_MAX, - MEM_ALT, - CNT_MAX - }; - - u8 cnt[CNT_MAX] = {}; - - for (uintptr_t ptr = mapped_nso; - ptr <= mapped_nso + nso_size - sizeof(perf_conf_entry) * entryCnt; - ptr += sizeof(u32)) - { - u32* ptr32 = reinterpret_cast(ptr); - u32 value = *(ptr32); - - if (value == cpuPtmDefault) - { - u32 value_next = *(ptr32 + 1); - if (value_next != cpuPtmDefault) - continue; - - confTable = reinterpret_cast(ptr - offsetof(perf_conf_entry, cpu_freq_1)); - break; - } - } - - if (!confTable) { - LOGGING("confTable not found!"); - CRASH(); - } - - for (u32 i = 0; i < entryCnt; i++) - { - perf_conf_entry* entry_current = confTable + i; - - if (entry_current->cpu_freq_1 != entry_current->cpu_freq_2 || - entry_current->gpu_freq_1 != entry_current->gpu_freq_2 || - entry_current->emc_freq_1 != entry_current->emc_freq_2) - { - LOGGING("@%p: Invalid confTable entry", &entry_current); - CRASH(); - } - - switch (entry_current->cpu_freq_1) - { - case cpuPtmBoost: - PatchOffset(&(entry_current->cpu_freq_1), cpuPtmBoostNew); - PatchOffset(&(entry_current->cpu_freq_2), cpuPtmBoostNew); - cnt[CPU_BOOST]++; - // LOGGING("0x%08x: CPU: Boost Freq", entry_current->conf_id); - break; - case cpuPtmDefault: - case cpuPtmDevOC: - break; - default: - LOGGING("Unknown CPU Freq: %u @%p!", entry_current->cpu_freq_1, &(entry_current->cpu_freq_1)); - CRASH(); - } - - switch (entry_current->emc_freq_1) - { - case memPtmLimit: - PatchOffset(&(entry_current->emc_freq_1), memPtmMax); - PatchOffset(&(entry_current->emc_freq_2), memPtmMax); - cnt[MEM_MAX]++; - // LOGGING("0x%08x: MEM: Max Freq", entry_current->conf_id); - break; - case memPtmAlt: - case memPtmClamp: - PatchOffset(&(entry_current->emc_freq_1), memPtmLimit); - PatchOffset(&(entry_current->emc_freq_2), memPtmLimit); - cnt[MEM_ALT]++; - // LOGGING("0x%08x: MEM: Alt Freq", entry_current->conf_id); - break; - default: - LOGGING("Unknown MEM Freq: %u @%p!", entry_current->emc_freq_1, &(entry_current->emc_freq_1)); - CRASH(); - } - } - - LOGGING("cpuPtmBoost Count: %u\n"\ - "memPtmMax Count: %u\n"\ - "memPtmAlt Count: %u", - cnt[CPU_BOOST], - cnt[MEM_MAX], - cnt[MEM_ALT]); - - if (cnt[CPU_BOOST] > 2 || - cnt[MEM_MAX] > 9 || - cnt[MEM_ALT] > 7) - { - CRASH(); - } - } - } - -} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.hpp deleted file mode 100644 index 149d1c88..00000000 --- a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.hpp +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) Switch-OC-Suite - * - * 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 "mtc_timing_table.hpp" - -#define CUST_REV 2 - -#ifdef ATMOSPHERE_IS_STRATOSPHERE -#include -#define LOGGING(fmt, ...) ((void)0) -#endif - -#define CRASH() { AMS_ABORT(); __builtin_unreachable(); } - -namespace ams::ldr { - R_DEFINE_ERROR_RESULT(OutOfRange, 1000); - R_DEFINE_ERROR_RESULT(InvalidMemPllmEntry, 1001); - R_DEFINE_ERROR_RESULT(InvalidMtcMagic, 1002); - R_DEFINE_ERROR_RESULT(InvalidMtcTable, 1003); - R_DEFINE_ERROR_RESULT(InvalidDvbTable, 1004); - R_DEFINE_ERROR_RESULT(InvalidCpuClockVddEntry, 1005); - R_DEFINE_ERROR_RESULT(InvalidCpuDvfs, 1006); - R_DEFINE_ERROR_RESULT(InvalidCpuMinVolt, 1007); - R_DEFINE_ERROR_RESULT(InvalidGpuDvfs, 1008); - R_DEFINE_ERROR_RESULT(InvalidGpuMaxClkPattern, 1009); - R_DEFINE_ERROR_RESULT(InvalidGpuPllEntry, 1010); -} - -namespace ams::ldr::oc { - enum MtcConfig { - AUTO_ADJ_MARIKO_SAFE = 0, - AUTO_ADJ_MARIKO_4266 = 1, - ENTIRE_TABLE_ERISTA = 2, - ENTIRE_TABLE_MARIKO = 3, - }; - - typedef struct CustomizeTable { - u8 cust[4] = {'C', 'U', 'S', 'T'}; - u16 custRev = CUST_REV; - u16 mtcConf = AUTO_ADJ_MARIKO_SAFE; - u32 marikoCpuMaxClock; - u32 marikoCpuBoostClock; - u32 marikoCpuMaxVolt; - u32 marikoGpuMaxClock; - u32 marikoEmcMaxClock; - u32 eristaCpuOCEnable; - u32 eristaCpuMaxVolt; - u32 eristaEmcMaxClock; - u32 eristaEmcVolt; - union { - EristaMtcTable* eristaMtc; - MarikoMtcTable* marikoMtc; - }; - } CustomizeTable; - - inline void PatchOffset(u32* offset, u32 value) { *(offset) = value; } - - namespace pcv { - typedef struct cvb_coefficients { - s32 c0 = 0; - s32 c1 = 0; - s32 c2 = 0; - s32 c3 = 0; - s32 c4 = 0; - s32 c5 = 0; - } cvb_coefficients; - - typedef struct cpu_freq_cvb_table_t { - u64 freq; - cvb_coefficients cvb_dfll_param; - cvb_coefficients cvb_pll_param; // only c0 is reserved - } cpu_freq_cvb_table_t; - - typedef struct gpu_cvb_pll_table_t { - u64 freq; - cvb_coefficients cvb_dfll_param; // empty, dfll clock source not selected - cvb_coefficients cvb_pll_param; - } gpu_cvb_pll_table_t; - - typedef struct emc_dvb_dvfs_table_t { - u64 freq; - s32 volt[4] = {0}; - } emc_dvb_dvfs_table_t; - - typedef struct clk_pll_param { - u32 max_0; - u32 unk[5]; - u32 max_1; - } clk_pll_param; - - void Patch(uintptr_t mapped_nso, size_t nso_size); - } - - namespace ptm { - typedef struct { - u32 conf_id; - u32 cpu_freq_1; - u32 cpu_freq_2; - u32 gpu_freq_1; - u32 gpu_freq_2; - u32 emc_freq_1; - u32 emc_freq_2; - u32 padding; - } perf_conf_entry; - - void Patch(uintptr_t mapped_nso, size_t nso_size); - } -} \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite_test.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite_test.hpp deleted file mode 100644 index ea38dd4a..00000000 --- a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite_test.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef ATMOSPHERE_IS_STRATOSPHERE -#include -#include -#include -#include - -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef int32_t s32; -typedef uint64_t u64; -typedef int Result; - -#define R_SUCCEEDED(arg) (arg == 0) -#define R_FAILED(arg) (arg != 0) -#define LOGGING(fmt, ...) { printf(fmt "\n\tin %s\n", ##__VA_ARGS__, __PRETTY_FUNCTION__); } -#define AMS_ABORT() { fprintf(stderr, "Failed in %s!\n", __PRETTY_FUNCTION__); exit(-1); } -#define R_SUCCEED() { return 0; } -#define R_THROW(err) { return err; } -#define R_TRY(expr) { Result _rc = (expr); if (R_FAILED(_rc)) { return _rc; } } -#define R_UNLESS(expr, rc) { if (!(expr)) { return rc; } } - -#define R_DEFINE_ERROR_RESULT(name, rc) \ - inline Result Result##name() { return rc; } - -#include "ldr_oc_suite.hpp" - -namespace ams::ldr::oc { - volatile u8 EmptyMtcTable[0x1340] = { 'M', 'T', 'C', '_', }; - static const volatile CustomizeTable C = { - .mtcConf = AUTO_ADJ_MARIKO_SAFE, - .marikoCpuMaxClock = 2397000, - .marikoCpuMaxVolt = 1220, - .marikoGpuMaxClock = 1305600, - .marikoEmcMaxClock = 1996800, - .eristaCpuOCEnable = 1, - .eristaCpuMaxVolt = 1300, - .eristaEmcMaxClock = 1862400, - .eristaEmcVolt = 1200'000, - .eristaMtc = reinterpret_cast(const_cast(EmptyMtcTable)), - }; -} -#endif \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_empty_table.inl b/Source/Atmosphere/stratosphere/loader/source/oc/mtc_empty_table.inl deleted file mode 100644 index 47f069f5..00000000 --- a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_empty_table.inl +++ /dev/null @@ -1,310 +0,0 @@ -volatile u8 EmptyMtcTable[0x1340] = { - 'M', 'T', 'C', '_', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -}; diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_suite.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_suite.hpp new file mode 100644 index 00000000..97cb5188 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_suite.hpp @@ -0,0 +1,21 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 "oc_suite_common.hpp" +#include "pcv/pcv.hpp" +#include "ptm/ptm.hpp" diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_suite_common.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_suite_common.hpp new file mode 100644 index 00000000..bf14ed01 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_suite_common.hpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 + +#ifdef ATMOSPHERE_IS_STRATOSPHERE + #include + #include + #define LOGGING(fmt, ...) ((void)0) + #define CRASH(msg, ...) { ams::diag::AbortImpl(msg, __PRETTY_FUNCTION__, "", 0); __builtin_unreachable(); } +#else + #include "oc_suite_test.hpp" +#endif + +#include "customize.hpp" + +namespace ams::ldr { + R_DEFINE_ERROR_RESULT(OutOfRange, 1000); + R_DEFINE_ERROR_RESULT(InvalidMemPllmEntry, 1001); + R_DEFINE_ERROR_RESULT(InvalidMtcMagic, 1002); + R_DEFINE_ERROR_RESULT(InvalidMtcTable, 1003); + R_DEFINE_ERROR_RESULT(InvalidDvbTable, 1004); + R_DEFINE_ERROR_RESULT(InvalidCpuFreqVddEntry, 1005); + R_DEFINE_ERROR_RESULT(InvalidCpuDvfs, 1006); + R_DEFINE_ERROR_RESULT(InvalidCpuMinVolt, 1007); + R_DEFINE_ERROR_RESULT(InvalidGpuDvfs, 1008); + R_DEFINE_ERROR_RESULT(InvalidGpuFreqMaxPattern, 1009); + R_DEFINE_ERROR_RESULT(InvalidGpuPllEntry, 1010); + R_DEFINE_ERROR_RESULT(UninitializedPatcher, 1011); + R_DEFINE_ERROR_RESULT(UnsuccessfulPatcher, 1012); +} + +namespace ams::ldr::oc { + template + struct PatcherEntry { + using patternFn = bool(*)(Pointer* ptr); + using patcherFn = Result(*)(Pointer* ptr); + + const char* description; + patcherFn patcher_fn = nullptr; + size_t maximum_patched_count = 0; + patternFn pattern_search_fn = nullptr; + Pointer value_search; + + size_t patched_count = 0; + + Result Apply(Pointer* ptr) { + Result res = patcher_fn(ptr); + if (R_SUCCEEDED(res)) + patched_count++; + + return res; + } + + Result SearchAndApply(Pointer* ptr) { + bool searchOk = false; + if (pattern_search_fn) { + if (pattern_search_fn(ptr)) searchOk = true; + } else { + if (value_search == *(ptr)) searchOk = true; + } + + if (searchOk) + return Apply(ptr); + + R_THROW(ldr::ResultUnsuccessfulPatcher()); + } + + Result CheckResult() { + #ifndef ATMOSPHERE_IS_STRATOSPHERE + R_UNLESS(patched_count > 0, ldr::ResultUnsuccessfulPatcher()); + #endif + + if (maximum_patched_count) + R_UNLESS(patched_count <= maximum_patched_count, ldr::ResultUnsuccessfulPatcher()); + + R_SUCCEED(); + } + }; + + template + inline void PatchOffset(T* offset, T value) { static_assert(sizeof(T) < sizeof(u64)); *(offset) = value; } +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/test.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_suite_test.cpp similarity index 89% rename from Source/Atmosphere/stratosphere/loader/source/oc/test.cpp rename to Source/Atmosphere/stratosphere/loader/source/oc/oc_suite_test.cpp index ca31cef4..eb470090 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/test.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_suite_test.cpp @@ -13,30 +13,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + #ifndef ATMOSPHERE_IS_STRATOSPHERE -#include -#include -#include -#include -#include - -namespace ams::ldr::oc { - namespace pcv { - namespace Erista { - void Patch(uintptr_t mapped_exe, size_t nso_size); - } - - namespace Mariko { - void Patch(uintptr_t mapped_exe, size_t nso_size); - } - - void SafetyCheck(); - } - - namespace ptm { - void Patch(uintptr_t mapped_nso, size_t nso_size); - } -} +#include "oc_suite_test.hpp" +#include "oc_suite.hpp" void* loadExec(const char* file_loc, size_t* out_size) { FILE* fp = fopen(file_loc, "rb"); @@ -131,7 +111,7 @@ int main(int argc, char** argv) { std::memcpy(erista_buf, file_buffer, file_size); printf("Patching %s for Erista...\n", pcv_opt); - ams::ldr::oc::pcv::Erista::Patch(reinterpret_cast(erista_buf), file_size); + ams::ldr::oc::pcv::erista::Patch(reinterpret_cast(erista_buf), file_size); if (save_patched) { char* exec_path_erista = reinterpret_cast(malloc(exec_path_patched_len)); strlcpy(exec_path_erista, exec_path, exec_path_patched_len); @@ -147,7 +127,7 @@ int main(int argc, char** argv) { std::memcpy(mariko_buf, file_buffer, file_size); printf("Patching %s for Mariko...\n", pcv_opt); - ams::ldr::oc::pcv::Mariko::Patch(reinterpret_cast(mariko_buf), file_size); + ams::ldr::oc::pcv::mariko::Patch(reinterpret_cast(mariko_buf), file_size); if (save_patched) { char* exec_path_mariko = reinterpret_cast(malloc(exec_path_patched_len)); strlcpy(exec_path_mariko, exec_path, exec_path_patched_len); diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_suite_test.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_suite_test.hpp new file mode 100644 index 00000000..93e4e7f1 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_suite_test.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 + +#ifndef ATMOSPHERE_IS_STRATOSPHERE +#include +#include +#include +#include +#include +#include + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef int32_t s32; +typedef uint64_t u64; +typedef int Result; + +#define R_SUCCEEDED(arg) (arg == 0) +#define R_FAILED(arg) (arg != 0) +#define LOGGING(fmt, ...) { printf(fmt "\n", ##__VA_ARGS__); } +#define CRASH(msg, ...) { fprintf(stderr, "%s\nFailed in %s!\n", msg, __PRETTY_FUNCTION__); exit(-1); } +#define R_SUCCEED() { return 0; } +#define R_THROW(err) { return err; } +#define R_TRY(expr) { Result _rc = (expr); if (R_FAILED(_rc)) { return _rc; } } +#define R_UNLESS(expr, rc) { if (!(expr)) { return rc; } } + +#define R_DEFINE_ERROR_RESULT(name, rc) \ + inline Result Result##name() { return rc; } + +#endif \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp new file mode 100644 index 00000000..ce67ed13 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 "pcv.hpp" + +namespace ams::ldr::oc::pcv { + +Result MemFreqPllmLimit(u32* ptr) { + clk_pll_param* entry = reinterpret_cast(ptr); + R_UNLESS(entry->freq == entry->vco_max, ldr::ResultInvalidMemPllmEntry()); + + // Double the max clk simply + u32 max_clk = entry->freq * 2; + entry->freq = max_clk; + entry->vco_max = max_clk; + R_SUCCEED(); +} + +void SafetyCheck() { + if (C.custRev != CUST_REV || + C.marikoCpuMaxVolt >= 1300 || + C.eristaCpuMaxVolt >= 1300 || + (C.eristaEmcVolt && (C.eristaEmcVolt < 600'000 || C.eristaEmcVolt > 1250'000))) + { + CRASH("Triggered"); + } +} + +void Patch(uintptr_t mapped_nso, size_t nso_size) { + #ifdef ATMOSPHERE_IS_STRATOSPHERE + SafetyCheck(); + bool isMariko = (spl::GetSocType() == spl::SocType_Mariko); + if (isMariko) + mariko::Patch(mapped_nso, nso_size); + else + erista::Patch(mapped_nso, nso_size); + #endif +} + +} \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp new file mode 100644 index 00000000..00976a80 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 "../oc_suite_common.hpp" + +namespace ams::ldr::oc::pcv { + +typedef struct cvb_coefficients { + s32 c0 = 0; + s32 c1 = 0; + s32 c2 = 0; + s32 c3 = 0; + s32 c4 = 0; + s32 c5 = 0; +} cvb_coefficients; + +typedef struct cpu_freq_cvb_table_t { + u64 freq; + cvb_coefficients cvb_dfll_param; + cvb_coefficients cvb_pll_param; +} cpu_freq_cvb_table_t; +static_assert(sizeof(cpu_freq_cvb_table_t) == 0x38); + +typedef struct gpu_cvb_pll_table_t { + u64 freq; + cvb_coefficients cvb_dfll_param; + cvb_coefficients cvb_pll_param; +} gpu_cvb_pll_table_t; +static_assert(sizeof(gpu_cvb_pll_table_t) == 0x38); + +typedef struct emc_dvb_dvfs_table_t { + u64 freq; + s32 volt[4] = {0}; +} emc_dvb_dvfs_table_t; + +typedef struct __attribute__((packed)) div_nmp { + u8 divn_shift; + u8 divn_width; + u8 divm_shift; + u8 divm_width; + u8 divp_shift; + u8 divp_width; + u8 override_divn_shift; + u8 override_divm_shift; + u8 override_divp_shift; +} div_nmp; + +typedef struct __attribute__((packed)) clk_pll_param { + u32 freq; + u32 input_min; + u32 input_max; + u32 cf_min; + u32 cf_max; + u32 vco_min; + u32 vco_max; + s32 lock_delay; + u32 fixed_rate; + u32 unk_0; + struct div_nmp *div_nmp; + u32 unk_1[4]; + void (*unk_fn)(u64* unk_struct); // set_defaults? +} clk_pll_param; + +typedef struct __attribute__((packed)) dvfs_rail { + u32 id; + u32 unk_0[5]; + u32 freq; + u32 unk_1[8]; + u32 unk_flag; + u32 min_mv; + u32 step_mv; + u32 max_mv; + u32 unk_2[11]; +} dvfs_rail; + +constexpr u32 CpuClkOSLimit = 1785'000; + +constexpr u32 MemClkOSLimit = 1600'000; + +#define R_SKIP() R_SUCCEED() + +Result MemFreqPllmLimit(u32* ptr); + +template +Result MemMtcTableClone(Table* des, Table* src) { + constexpr u32 mtc_magic = 0x5F43544D; + R_UNLESS(src->rev == mtc_magic, ldr::ResultInvalidMtcMagic()); + + // Skip params from dvfs_ver to clock_src; + for (size_t offset = offsetof(Table, clk_src_emc); offset < sizeof(Table); offset += sizeof(u32)) { + u32* src_ent = reinterpret_cast(reinterpret_cast(src) + offset); + u32* des_ent = reinterpret_cast(reinterpret_cast(des) + offset); + u32 src_val = *src_ent; + + constexpr u32 placeholder_val = UINT32_MAX; + if (src_val != placeholder_val) { + PatchOffset(des_ent, src_val); + } + } + + R_SUCCEED(); +}; + +namespace erista { + void Patch(uintptr_t mapped_nso, size_t nso_size); +} + +namespace mariko { + void Patch(uintptr_t mapped_nso, size_t nso_size); +} + +void SafetyCheck(); +void Patch(uintptr_t mapped_nso, size_t nso_size); + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp new file mode 100644 index 00000000..ea378233 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 "pcv_erista.hpp" + +namespace ams::ldr::oc::pcv::erista { + +Result CpuFreqCvbTable(u32* ptr) { + cpu_freq_cvb_table_t* default_end = reinterpret_cast(ptr); + cpu_freq_cvb_table_t* new_start = default_end + 1; + + // Validate existing table + void* cpu_cvb_table_head = reinterpret_cast(new_start) - sizeof(CpuCvbTableDefault); + bool validated = std::memcmp(cpu_cvb_table_head, CpuCvbTableDefault, sizeof(CpuCvbTableDefault)) == 0; + R_UNLESS(validated, ldr::ResultInvalidCpuDvfs()); + + if (!C.eristaCpuOCEnabled) + R_SKIP(); + + std::memcpy(reinterpret_cast(new_start), CpuCvbTableAppend, sizeof(CpuCvbTableAppend)); + + R_SUCCEED(); +} + +Result CpuVoltRange(u32* ptr) { + u32 min_volt_got = *(ptr - 1); + for (const auto& mv : CpuMinVolts) { + if (min_volt_got != mv) + continue; + + if (!C.eristaCpuMaxVolt) + R_SKIP(); + + PatchOffset(ptr, C.eristaCpuMaxVolt); + R_SUCCEED(); + } + R_THROW(ldr::ResultInvalidCpuMinVolt()); +} + +Result MemFreqMtcTable(u32* ptr) { + u32 khz_list[] = { 1600000, 1331200, 1065600, 800000, 665600, 408000, 204000, 102000, 68000, 40800 }; + u32 khz_list_size = sizeof(khz_list) / sizeof(u32); + + // Generate list for mtc table pointers + EristaMtcTable* table_list[khz_list_size]; + for (u32 i = 0; i < khz_list_size; i++) { + u8* table = reinterpret_cast(ptr) - offsetof(EristaMtcTable, rate_khz) - i * sizeof(EristaMtcTable); + table_list[i] = reinterpret_cast(table); + R_UNLESS(table_list[i]->rate_khz == khz_list[i], ldr::ResultInvalidMtcTable()); + R_UNLESS(table_list[i]->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); + } + + if (C.eristaEmcMaxClock <= MemClkOSLimit) + R_SKIP(); + + // Make room for new mtc table, discarding useless 40.8 MHz table + // 40800 overwritten by 68000, ..., 1331200 overwritten by 1600000, leaving table_list[0] not overwritten + for (u32 i = khz_list_size - 1; i > 0; i--) + std::memcpy(static_cast(table_list[i]), static_cast(table_list[i - 1]), sizeof(EristaMtcTable)); + + PatchOffset(ptr, C.eristaEmcMaxClock); + + R_SUCCEED(); +} + +Result MemFreqMax(u32* ptr) { + if (C.eristaEmcMaxClock <= MemClkOSLimit) + R_SKIP(); + + PatchOffset(ptr, C.eristaEmcMaxClock); + + R_SUCCEED(); +} + +Result MemVoltHandler(u32* ptr) { + u32 emc_uv = C.eristaEmcVolt; + if (!emc_uv) + R_SKIP(); + + constexpr u32 uv_step = 12'500; + if (emc_uv % uv_step) + emc_uv = emc_uv / uv_step * uv_step; // rounding + + PatchOffset(ptr, emc_uv); + + R_SUCCEED(); +} + +void Patch(uintptr_t mapped_nso, size_t nso_size) { + PatcherEntry patches[] = { + { "CPU Freq Table", &CpuFreqCvbTable, 1, nullptr, CpuClkOSLimit }, + { "CPU Volt Limit", &CpuVoltRange, 0, &CpuMaxVoltPatternFn }, + { "MEM Freq Mtc", &MemFreqMtcTable, 0, nullptr, MemClkOSLimit }, + { "MEM Freq Max", &MemFreqMax, 0, nullptr, MemClkOSLimit }, + { "MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, MemClkPllmLimit }, + { "MEM Volt", &MemVoltHandler, 2, nullptr, MemVoltHOS }, + }; + + for (uintptr_t ptr = mapped_nso; + ptr <= mapped_nso + nso_size - sizeof(EristaMtcTable); + ptr += sizeof(u32)) + { + u32* ptr32 = reinterpret_cast(ptr); + for (auto& entry : patches) { + if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) + break; + } + } + + for (auto& entry : patches) { + LOGGING("%s Count: %zu", entry.description, entry.patched_count); + if (R_FAILED(entry.CheckResult())) + CRASH(entry.description); + } +} + +} \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.hpp new file mode 100644 index 00000000..16b3149d --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.hpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 "pcv.hpp" + +namespace ams::ldr::oc::pcv::erista { + +constexpr cpu_freq_cvb_table_t CpuCvbTableDefault[] = { + // CPU_PLL_CVB_TABLE_ODN + { 204000, { 721094 }, {} }, + { 306000, { 754040 }, {} }, + { 408000, { 786986 }, {} }, + { 510000, { 819932 }, {} }, + { 612000, { 852878 }, {} }, + { 714000, { 885824 }, {} }, + { 816000, { 918770 }, {} }, + { 918000, { 951716 }, {} }, + { 1020000, { 984662 }, { -2875621, 358099, -8585 } }, + { 1122000, { 1017608 }, { -52225, 104159, -2816 } }, + { 1224000, { 1050554 }, { 1076868, 8356, -727 } }, + { 1326000, { 1083500 }, { 2208191, -84659, 1240 } }, + { 1428000, { 1116446 }, { 2519460, -105063, 1611 } }, + { 1581000, { 1130000 }, { 2889664, -122173, 1834 } }, + { 1683000, { 1168000 }, { 5100873, -279186, 4747 } }, + { 1785000, { 1227500 }, { 5100873, -279186, 4747 } }, +}; + +constexpr cpu_freq_cvb_table_t CpuCvbTableAppend[] = { + { 1887000, { 1235000 }, { 5100873, -279186, 4747 } }, + { 1989000, { 1235000 }, { 5100873, -279186, 4747 } }, + { 2091000, { 1235000 }, { 5100873, -279186, 4747 } }, +}; + +constexpr u32 CpuMinVolts[] = { 950, 850, 825, 810 }; + +inline bool CpuMaxVoltPatternFn(u32* ptr32) { + u32 val = *ptr32; + return (val == 1132 || val == 1170 || val == 1227); +} + +constexpr u32 MemVoltHOS = 1125'000; +constexpr u32 MemClkPllmLimit = 1866'000'000; + +constexpr u32 MTC_TABLE_REV = 7; + +void Patch(uintptr_t mapped_nso, size_t nso_size); + +} \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp new file mode 100644 index 00000000..f03da3d8 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp @@ -0,0 +1,403 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 "pcv_mariko.hpp" + +namespace ams::ldr::oc::pcv::mariko { + +Result CpuFreqVdd(u32* ptr) { + dvfs_rail* entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(dvfs_rail, freq)); + + R_UNLESS(entry->id == 1, ldr::ResultInvalidCpuFreqVddEntry()); + R_UNLESS(entry->min_mv == 250'000, ldr::ResultInvalidCpuFreqVddEntry()); + R_UNLESS(entry->step_mv == 5000, ldr::ResultInvalidCpuFreqVddEntry()); + R_UNLESS(entry->max_mv == 1525'000, ldr::ResultInvalidCpuFreqVddEntry()); + + if (!C.marikoCpuMaxClock) + R_SKIP(); + + PatchOffset(ptr, C.marikoCpuMaxClock); + + R_SUCCEED(); +} + +Result CpuFreqCvbTable(u32* ptr) { + cpu_freq_cvb_table_t* default_end = reinterpret_cast(ptr); + cpu_freq_cvb_table_t* new_start = default_end + 1; + + // Validate existing table + void* cpu_cvb_table_head = reinterpret_cast(new_start) - sizeof(CpuCvbTableDefault); + bool validated = std::memcmp(cpu_cvb_table_head, CpuCvbTableDefault, sizeof(CpuCvbTableDefault)) == 0; + R_UNLESS(validated, ldr::ResultInvalidCpuDvfs()); + + if (C.marikoCpuMaxClock > CpuClkOfficial) + std::memcpy(reinterpret_cast(new_start), CpuCvbTableAppend, sizeof(CpuCvbTableAppend)); + + // Patch CPU max volt in existing and appended CPU dvfs table + if (C.marikoCpuMaxVolt) { + size_t table_size = (sizeof(CpuCvbTableDefault) + sizeof(CpuCvbTableAppend)) / sizeof(cpu_freq_cvb_table_t); + cpu_freq_cvb_table_t* entry = static_cast(cpu_cvb_table_head); + for (size_t i = 0; i < table_size; i++) { + if (entry->cvb_pll_param.c0 == CpuVoltOfficial * 1000) { + PatchOffset(reinterpret_cast(&(entry->cvb_pll_param.c0)), C.marikoCpuMaxVolt * 1000); + } + entry++; + } + } + + R_SUCCEED(); +} + +Result CpuVoltRange(u32* ptr) { + u32 min_volt_got = *(ptr - 1); + for (const auto& mv : CpuMinVolts) { + if (min_volt_got != mv) + continue; + + if (!C.marikoCpuMaxVolt) + R_SKIP(); + + PatchOffset(ptr, C.marikoCpuMaxVolt); + R_SUCCEED(); + } + R_THROW(ldr::ResultInvalidCpuMinVolt()); +} + +Result GpuFreqCvbTable(u32* ptr) { + gpu_cvb_pll_table_t* default_end = reinterpret_cast(ptr); + gpu_cvb_pll_table_t* new_start = default_end + 1; + + // Validate existing table + void* gpu_cvb_table_head = reinterpret_cast(new_start) - sizeof(GpuCvbTableDefault); + bool validated = std::memcmp(gpu_cvb_table_head, GpuCvbTableDefault, sizeof(GpuCvbTableDefault)) == 0; + R_UNLESS(validated, ldr::ResultInvalidGpuDvfs()); + + if (C.marikoGpuMaxClock <= GpuClkOfficial) + R_SKIP(); + + std::memcpy(reinterpret_cast(new_start), GpuCvbTableAppend, sizeof(GpuCvbTableAppend)); + + R_SUCCEED(); +} + +Result GpuFreqMaxAsm(u32* ptr32) { + // Check if both two instructions match the pattern + u32 ins1 = *ptr32, ins2 = *(ptr32 + 1); + if (!(asm_compare_no_rd(ins1, asm_pattern[0]) && asm_compare_no_rd(ins2, asm_pattern[1]))) + R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); + + // Both instructions should operate on the same register + u8 rd = asm_get_rd(ins1); + if (rd != asm_get_rd(ins2)) + R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); + + if (!C.marikoGpuMaxClock) + R_SKIP(); + + u32 asm_patch[2] = { + asm_set_rd(asm_set_imm16(asm_pattern[0], C.marikoGpuMaxClock), rd), + asm_set_rd(asm_set_imm16(asm_pattern[1], C.marikoGpuMaxClock >> 16), rd) + }; + PatchOffset(ptr32, asm_patch[0]); + PatchOffset(ptr32 + 1, asm_patch[1]); + + R_SUCCEED(); +} + +Result GpuFreqPllLimit(u32* ptr) { + clk_pll_param* entry = reinterpret_cast(ptr); + + // All zero except for freq + for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) { + R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry()); + } + + // Double the max clk simply + u32 max_clk = entry->freq * 2; + entry->freq = max_clk; + R_SUCCEED(); +} + +void MemMtcTableAutoAdjust(MarikoMtcTable* table, const MarikoMtcTable* ref) { + /* Official Tegra X1 TRM, sign up for nvidia developer program (free) to download: + * https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual + * Section 18.11: MC Registers + * + * Retail Mariko: 200FBGA 16Gb DDP LPDDR4X SDRAM x 2 + * x16/Ch, 1Ch/die, Double-die, 2Ch, 1CS(rank), 8Gb density per die + * 64Mb x 16DQ x 8banks x 2channels = 2048MB (x32DQ) per package + * + * Devkit Mariko: 200FBGA 32Gb DDP LPDDR4X SDRAM x 2 + * x16/Ch, 1Ch/die, Quad-die, 2Ch, 2CS(rank), 8Gb density per die + * X1+ EMC can R/W to both ranks at the same time, resulting in doubled DQ + * 64Mb x 32DQ x 8banks x 2channels = 4096MB (x64DQ) per package + * + * If you have access to LPDDR4(X) specs or datasheets (from manufacturers or Google), + * you'd better calculate timings yourself rather than relying on following algorithm. + */ + + #define ADJUST_PROP(TARGET, REF) \ + (u32)(std::ceil(REF + ((C.marikoEmcMaxClock-MemClkOSAlt)*(TARGET-REF))/(MemClkOSLimit-MemClkOSAlt))) + + #define ADJUST_PARAM(TARGET, REF) \ + TARGET = ADJUST_PROP(TARGET, REF); + + #define ADJUST_PARAM_TABLE(TABLE, PARAM, REF) ADJUST_PARAM(TABLE->PARAM, REF->PARAM) + + #define ADJUST_PARAM_ALL_REG(TABLE, PARAM, REF) \ + ADJUST_PARAM_TABLE(TABLE, burst_regs.PARAM, REF) \ + ADJUST_PARAM_TABLE(TABLE, shadow_regs_ca_train.PARAM, REF) \ + ADJUST_PARAM_TABLE(TABLE, shadow_regs_rdwr_train.PARAM, REF) + + #define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE)\ + TABLE->burst_regs.PARAM = VALUE; \ + TABLE->shadow_regs_ca_train.PARAM = VALUE; \ + TABLE->shadow_regs_rdwr_train.PARAM = VALUE; + + ADJUST_PARAM_ALL_REG(table, emc_r2w, ref); + ADJUST_PARAM_ALL_REG(table, emc_w2r, ref); + ADJUST_PARAM_ALL_REG(table, emc_r2p, ref); + ADJUST_PARAM_ALL_REG(table, emc_w2p, ref); + ADJUST_PARAM_ALL_REG(table, emc_trtm, ref); + ADJUST_PARAM_ALL_REG(table, emc_twtm, ref); + ADJUST_PARAM_ALL_REG(table, emc_tratm, ref); + ADJUST_PARAM_ALL_REG(table, emc_twatm, ref); + + ADJUST_PARAM_ALL_REG(table, emc_rw2pden, ref); + + ADJUST_PARAM_ALL_REG(table, emc_tclkstop, ref); + + ADJUST_PARAM_ALL_REG(table, emc_pmacro_dll_cfg_2, ref); // EMC_DLL_CFG_2_0: level select for VDDA? + + ADJUST_PARAM_TABLE(table, la_scale_regs.mc_mll_mpcorer_ptsa_rate, ref); + ADJUST_PARAM_TABLE(table, la_scale_regs.mc_ptsa_grant_decrement, ref); + + /* Timings that are available in or can be derived from LPDDR4X datasheet or TRM */ + const bool use_4266_spec = C.mtcConf == AUTO_ADJ_MARIKO_4266; + // tCK_avg (average clock period) in ns + const double tCK_avg = 1000'000. / C.marikoEmcMaxClock; + // tRPpb (row precharge time per bank) in ns + const u32 tRPpb = 18; + // tRPab (row precharge time all banks) in ns + const u32 tRPab = 21; + // tRAS (row active time) in ns + const u32 tRAS = 42; + // tRC (ACTIVATE-ACTIVATE command period same bank) in ns + const u32 tRC = tRPpb + tRAS; + // tRFCab (refresh cycle time all banks) in ns for 8Gb density + const u32 tRFCab = 280; + // tRFCpb (refresh cycle time per bank) in ns for 8Gb density + const u32 tRFCpb = 140; + // tRCD (RAS-CAS delay) in ns + const u32 tRCD = 18; + // tRRD (Active bank-A to Active bank-B) in ns + const double tRRD = use_4266_spec ? 7.5 : 10.; + // tREFpb (average refresh interval per bank) in ns for 8Gb density + const u32 tREFpb = 488; + // tREFab (average refresh interval all 8 banks) in ns for 8Gb density + // const u32 tREFab = tREFpb * 8; + // #_of_rows per die for 8Gb density + const u32 numOfRows = 65536; + // {REFRESH, REFRESH_LO} = max[(tREF/#_of_rows) / (emc_clk_period) - 64, (tREF/#_of_rows) / (emc_clk_period) * 97%] + // emc_clk_period = dram_clk / 2; + // 1600 MHz: 5894, but N' set to 6176 (~4.8% margin) + const u32 REFRESH = u32(std::ceil((double(tREFpb) * C.marikoEmcMaxClock / numOfRows * 1.048 / 2 - 64))) / 4 * 4; + // tPDEX2WR, tPDEX2RD (timing delay from exiting powerdown mode to a write/read command) in ns + const u32 tPDEX2 = 10; + // [Guessed] tACT2PDEN (timing delay from an activate, MRS or EMRS command to power-down entry) in ns + const u32 tACT2PDEN = 14; + // [Guessed] tPDEX2MRR (timing delay from exiting powerdown mode to MRR command) in ns + const double tPDEX2MRR = 28.75; + // [Guessed] tCKE2PDEN (timing delay from turning off CKE to power-down entry) in ns + const double tCKE2PDEN = 8.5; + // tXSR (SELF REFRESH exit to next valid command delay) in ns + const double tXSR = tRFCab + 7.5; + // tCKE (minimum CKE high pulse width) in ns + const u32 tCKE = 8; + // tCKELPD (minimum CKE low pulse width in SELF REFRESH) in ns + const u32 tCKELPD = 15; + // [Guessed] tPD (minimum CKE low pulse width in power-down mode) in ns + const double tPD = 7.5; + // tFAW (Four-bank Activate Window) in ns + const u32 tFAW = use_4266_spec ? 30 : 40; + + #define GET_CYCLE_CEIL(PARAM) u32(std::ceil(double(PARAM) / tCK_avg)) + + WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(tRC)); + WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(tRFCab)); + WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(tRFCpb)); + WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(tRAS)); + WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(tRPpb)); + WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD)); + WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD)); + WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD)); + WRITE_PARAM_ALL_REG(table, emc_refresh, REFRESH); + WRITE_PARAM_ALL_REG(table, emc_pre_refresh_req_cnt, REFRESH / 4); + WRITE_PARAM_ALL_REG(table, emc_pdex2wr, GET_CYCLE_CEIL(tPDEX2)); + WRITE_PARAM_ALL_REG(table, emc_pdex2rd, GET_CYCLE_CEIL(tPDEX2)); + WRITE_PARAM_ALL_REG(table, emc_act2pden,GET_CYCLE_CEIL(tACT2PDEN)); + WRITE_PARAM_ALL_REG(table, emc_cke2pden,GET_CYCLE_CEIL(tCKE2PDEN)); + WRITE_PARAM_ALL_REG(table, emc_pdex2mrr,GET_CYCLE_CEIL(tPDEX2MRR)); + WRITE_PARAM_ALL_REG(table, emc_txsr, GET_CYCLE_CEIL(tXSR)); + WRITE_PARAM_ALL_REG(table, emc_txsrdll, GET_CYCLE_CEIL(tXSR)); + WRITE_PARAM_ALL_REG(table, emc_tcke, GET_CYCLE_CEIL(tCKE)); + WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tCKELPD)); + WRITE_PARAM_ALL_REG(table, emc_tpd, GET_CYCLE_CEIL(tPD)); + WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW)); + WRITE_PARAM_ALL_REG(table, emc_trpab, GET_CYCLE_CEIL(tRPab)); + WRITE_PARAM_ALL_REG(table, emc_trefbw, REFRESH + 64); + + constexpr u32 MC_ARB_DIV = 4; // ? + table->burst_mc_regs.mc_emem_arb_timing_rcd = std::ceil(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV - 2); + table->burst_mc_regs.mc_emem_arb_timing_rp = std::ceil(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV - 1); + table->burst_mc_regs.mc_emem_arb_timing_rc = std::ceil(std::max(GET_CYCLE_CEIL(tRC), GET_CYCLE_CEIL(tRAS)+GET_CYCLE_CEIL(tRPpb)) / MC_ARB_DIV); + table->burst_mc_regs.mc_emem_arb_timing_ras = std::ceil(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV - 2); + table->burst_mc_regs.mc_emem_arb_timing_faw = std::ceil(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV - 1); + table->burst_mc_regs.mc_emem_arb_timing_rrd = std::ceil(GET_CYCLE_CEIL(tRRD) / MC_ARB_DIV - 1); + table->burst_mc_regs.mc_emem_arb_timing_rap2pre = std::ceil(table->burst_regs.emc_r2p / MC_ARB_DIV); + table->burst_mc_regs.mc_emem_arb_timing_wap2pre = std::ceil(table->burst_regs.emc_w2p / MC_ARB_DIV); + table->burst_mc_regs.mc_emem_arb_timing_r2w = std::ceil(table->burst_regs.emc_r2w / MC_ARB_DIV + 1); + table->burst_mc_regs.mc_emem_arb_timing_w2r = std::ceil(table->burst_regs.emc_w2r / MC_ARB_DIV + 1); + table->burst_mc_regs.mc_emem_arb_timing_rfcpb = std::ceil(GET_CYCLE_CEIL(tRFCpb) / MC_ARB_DIV + 1); // ? +} + +void MemMtcPllmbDivisor(MarikoMtcTable* table) { + // Calculate DIVM and DIVN (clock divisors) + // Common PLL oscillator is 38.4 MHz + // PLLMB_OUT = 38.4 MHz / PLLLMB_DIVM * PLLMB_DIVN + typedef struct { + u8 numerator : 4; + u8 denominator : 4; + } pllmb_div; + + constexpr pllmb_div div[] = { + {3, 4}, {2, 3}, {1, 2}, {1, 3}, {1, 4}, {0, 2} + }; + + constexpr u32 pll_osc_in = 38'400; + u32 divm {}, divn {}; + const u32 remainder = C.marikoEmcMaxClock % pll_osc_in; + for (const auto &index : div) { + // Round down + if (remainder >= pll_osc_in * index.numerator / index.denominator) { + divm = index.denominator; + divn = C.marikoEmcMaxClock / pll_osc_in * divm + index.numerator; + break; + } + } + + table->pllmb_divm = divm; + table->pllmb_divn = divn; +} + +Result MemFreqMtcTable(u32* ptr) { + u32 khz_list[] = { 1600000, 1331200, 204000 }; + u32 khz_list_size = sizeof(khz_list) / sizeof(u32); + + // Generate list for mtc table pointers + MarikoMtcTable* table_list[khz_list_size]; + for (u32 i = 0; i < khz_list_size; i++) { + u8* table = reinterpret_cast(ptr) - offsetof(MarikoMtcTable, rate_khz) - i * sizeof(MarikoMtcTable); + table_list[i] = reinterpret_cast(table); + R_UNLESS(table_list[i]->rate_khz == khz_list[i], ldr::ResultInvalidMtcTable()); + R_UNLESS(table_list[i]->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); + } + + if (C.marikoEmcMaxClock <= MemClkOSLimit) + R_SKIP(); + + MarikoMtcTable *table_alt = table_list[1], *table_max = table_list[0]; + MarikoMtcTable *tmp = new MarikoMtcTable; + + // Copy unmodified 1600000 table to tmp + std::memcpy(reinterpret_cast(tmp), reinterpret_cast(table_max), sizeof(MarikoMtcTable)); + // Adjust max freq mtc timing parameters with reference to 1331200 table + MemMtcTableAutoAdjust(table_max, table_alt); + MemMtcPllmbDivisor(table_max); + // Overwrite 13312000 table with unmodified 1600000 table copied back + std::memcpy(reinterpret_cast(table_alt), reinterpret_cast(tmp), sizeof(MarikoMtcTable)); + + delete tmp; + + PatchOffset(ptr, C.marikoEmcMaxClock); + + R_SUCCEED(); +} + +Result MemFreqDvbTable(u32* ptr) { + emc_dvb_dvfs_table_t* default_end = reinterpret_cast(ptr); + emc_dvb_dvfs_table_t* new_start = default_end + 1; + + // Validate existing table + void* mem_dvb_table_head = reinterpret_cast(new_start) - sizeof(EmcDvbTableDefault); + bool validated = std::memcmp(mem_dvb_table_head, EmcDvbTableDefault, sizeof(EmcDvbTableDefault)) == 0; + R_UNLESS(validated, ldr::ResultInvalidDvbTable()); + + if (C.marikoEmcMaxClock <= MemClkOSLimit) + R_SKIP(); + + if (C.marikoEmcMaxClock <= 1862400) { + std::memcpy(new_start, default_end, sizeof(emc_dvb_dvfs_table_t)); + } else { + emc_dvb_dvfs_table_t oc_table = { 2131200, { 700, 675, 650, } }; + std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); + } + new_start->freq = C.marikoEmcMaxClock; + + R_SUCCEED(); +} + +Result MemFreqMax(u32* ptr) { + if (C.marikoEmcMaxClock <= MemClkOSLimit) + R_SKIP(); + + PatchOffset(ptr, C.marikoEmcMaxClock); + R_SUCCEED(); +} + +void Patch(uintptr_t mapped_nso, size_t nso_size) { + PatcherEntry patches[] = { + { "CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit }, + { "CPU Freq Table", &CpuFreqCvbTable, 1, nullptr, CpuClkOfficial }, + { "CPU Volt Limit", &CpuVoltRange, 13, nullptr, CpuVoltOfficial }, + { "GPU Freq Table", &GpuFreqCvbTable, 1, nullptr, GpuClkOfficial }, + { "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn }, + { "GPU Freq PLL", &GpuFreqPllLimit, 1, nullptr, GpuClkPllLimit }, + { "MEM Freq Mtc", &MemFreqMtcTable, 0, nullptr, MemClkOSLimit }, + { "MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, MemClkOSLimit }, + { "MEM Freq Max", &MemFreqMax, 0, nullptr, MemClkOSLimit }, + { "MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, MemClkPllmLimit }, + }; + + for (uintptr_t ptr = mapped_nso; + ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); + ptr += sizeof(u32)) + { + u32* ptr32 = reinterpret_cast(ptr); + for (auto& entry : patches) { + if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) + break; + } + } + + for (auto& entry : patches) { + LOGGING("%s Count: %zu", entry.description, entry.patched_count); + if (R_FAILED(entry.CheckResult())) + CRASH(entry.description); + } +} + +} \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp new file mode 100644 index 00000000..032a71b9 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 "pcv.hpp" + +namespace ams::ldr::oc::pcv::mariko { + +constexpr cpu_freq_cvb_table_t CpuCvbTableDefault[] = { + // CPUB01_CVB_TABLE + { 204000, { 721589, -12695, 27 }, {} }, + { 306000, { 747134, -14195, 27 }, {} }, + { 408000, { 776324, -15705, 27 }, {} }, + { 510000, { 809160, -17205, 27 }, {} }, + { 612000, { 845641, -18715, 27 }, {} }, + { 714000, { 885768, -20215, 27 }, {} }, + { 816000, { 929540, -21725, 27 }, {} }, + { 918000, { 976958, -23225, 27 }, {} }, + { 1020000, { 1028021, -24725, 27 }, { 1120000 } }, + { 1122000, { 1082730, -26235, 27 }, { 1120000 } }, + { 1224000, { 1141084, -27735, 27 }, { 1120000 } }, + { 1326000, { 1203084, -29245, 27 }, { 1120000 } }, + { 1428000, { 1268729, -30745, 27 }, { 1120000 } }, + { 1581000, { 1374032, -33005, 27 }, { 1120000 } }, + { 1683000, { 1448791, -34505, 27 }, { 1120000 } }, + { 1785000, { 1527196, -36015, 27 }, { 1120000 } }, + { 1887000, { 1609246, -37515, 27 }, { 1120000 } }, + { 1963500, { 1675751, -38635, 27 }, { 1120000 } }, +}; + +constexpr cpu_freq_cvb_table_t CpuCvbTableAppend[] = { + { 2091000, { 1716501, -39395, 27 }, { 1120000 } }, + { 2193000, { 1775132, -40505, 27 }, { 1120000 } }, + { 2295000, { 1866287, -42005, 27 }, { 1120000 } }, + // 2397000 kHz is not listed in l4t + { 2397000, { 1961107, -43506, 27 }, { 1120000 } }, +}; + +constexpr u32 CpuMinVolts[] = { 800, 637, 620, 610 }; + +constexpr u32 CpuClkOfficial = 1963'500; +constexpr u32 CpuVoltOfficial = 1120; + +constexpr gpu_cvb_pll_table_t GpuCvbTableDefault[] = { + // GPUB01_NA_CVB_TABLE + { 76800, {}, { 610000, } }, + { 153600, {}, { 610000, } }, + { 230400, {}, { 610000, } }, + { 307200, {}, { 610000, } }, + { 384000, {}, { 610000, } }, + { 460800, {}, { 610000, } }, + { 537600, {}, { 801688, -10900, -163, 298, -10599, 162 } }, + { 614400, {}, { 824214, -5743, -452, 238, -6325, 81 } }, + { 691200, {}, { 848830, -3903, -552, 119, -4030, -2 } }, + { 768000, {}, { 891575, -4409, -584, 0, -2849, 39 } }, + { 844800, {}, { 940071, -5367, -602, -60, -63, -93 } }, + { 921600, {}, { 986765, -6637, -614, -179, 1905, -13 } }, + { 998400, {}, { 1098475, -13529, -497, -179, 3626, 9 } }, + { 1075200, {}, { 1163644, -12688, -648, 0, 1077, 40 } }, + { 1152000, {}, { 1204812, -9908, -830, 0, 1469, 110 } }, + { 1228800, {}, { 1277303, -11675, -859, 0, 3722, 313 } }, + { 1267200, {}, { 1335531, -12567, -867, 0, 3681, 559 } }, +}; + +constexpr gpu_cvb_pll_table_t GpuCvbTableAppend[] = { + // 1305600 kHz is not listed in l4t + { 1305600, {}, { 1374130, -13725, -859, 0, 4442, 576 } }, +}; + +constexpr u32 GpuClkOfficial = 1267'200; +constexpr u32 GpuClkPllLimit = 1300'000'000; + +/* GPU Max Clock asm Pattern: + * + * MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11) + * sf | opc | | hw | imm16 | Rd + * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0 + * 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1 + * + * MOVK W11, #0xE, LSL#16 16 0xE 0xB (11) + * sf | opc | | hw | imm16 | Rd + * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0 + * 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1 + */ +inline constexpr u32 asm_pattern[] = { 0x52820000, 0x72A001C0 }; +inline auto asm_compare_no_rd = [](u32 ins1, u32 ins2) { return ((ins1 ^ ins2) >> 5) == 0; }; +inline auto asm_get_rd = [](u32 ins) { return ins & ((1 << 5) - 1); }; +inline auto asm_set_rd = [](u32 ins, u8 rd) { return (ins & 0xFFFFFFE0) | (rd & 0x1F); }; +inline auto asm_set_imm16 = [](u32 ins, u16 imm) { return (ins & 0xFFE0001F) | ((imm & 0xFFFF) << 5); }; + +inline bool GpuMaxClockPatternFn(u32* ptr32) { + return asm_compare_no_rd(*ptr32, asm_pattern[0]); +} + +constexpr emc_dvb_dvfs_table_t EmcDvbTableDefault[] = { + { 204000, { 637, 637, 637, } }, + { 408000, { 637, 637, 637, } }, + { 800000, { 637, 637, 637, } }, + { 1065600, { 637, 637, 637, } }, + { 1331200, { 650, 637, 637, } }, + { 1600000, { 675, 650, 637, } }, +}; + +constexpr emc_dvb_dvfs_table_t EmcDvbTableReplaced[] = { + { 1862400, { 675, 650, 637, } }, + { 2133000, { 700, 675, 650, } }, +}; + +constexpr u32 MemClkOSAlt = 1331'200; +constexpr u32 MemClkPllmLimit = 2133'000'000; + +constexpr u32 MTC_TABLE_REV = 3; + +void Patch(uintptr_t mapped_nso, size_t nso_size); + +} \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/ptm/ptm.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/ptm/ptm.cpp new file mode 100644 index 00000000..7429474a --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/ptm/ptm.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 "ptm.hpp" + +namespace ams::ldr::oc::ptm { + +Result CpuPtmBoost(perf_conf_entry* entry) { + if (!C.marikoCpuBoostClock) + R_SUCCEED(); + + u32 cpuPtmBoostNew = C.marikoCpuBoostClock * 1000; + + PatchOffset(&(entry->cpu_freq_1), cpuPtmBoostNew); + PatchOffset(&(entry->cpu_freq_2), cpuPtmBoostNew); + + R_SUCCEED(); +} + +Result MemPtmMax(perf_conf_entry* entry) { + if (!C.marikoEmcMaxClock) + R_SUCCEED(); + + u32 memPtmMax = C.marikoEmcMaxClock * 1000; + + PatchOffset(&(entry->emc_freq_1), memPtmMax); + PatchOffset(&(entry->emc_freq_2), memPtmMax); + + R_SUCCEED(); +} + +Result MemPtmAlt(perf_conf_entry* entry) { + PatchOffset(&(entry->emc_freq_1), memPtmLimit); + PatchOffset(&(entry->emc_freq_2), memPtmLimit); + + R_SUCCEED(); +} + +bool PtmEntryIsValid(perf_conf_entry* entry) { + return (entry->cpu_freq_1 == entry->cpu_freq_2 && + entry->gpu_freq_1 == entry->gpu_freq_2 && + entry->emc_freq_1 == entry->emc_freq_2); +} + +bool PtmTablePatternFn(u32* ptr) { + perf_conf_entry* entry = reinterpret_cast(ptr); + if (!PtmEntryIsValid(entry)) + return false; + + return entry->cpu_freq_1 == cpuPtmDefault; +} + +void Patch(uintptr_t mapped_nso, size_t nso_size) { + #ifdef ATMOSPHERE_IS_STRATOSPHERE + bool isMariko = (spl::GetSocType() == spl::SocType_Mariko); + if (!isMariko) + return; + #endif + + perf_conf_entry* confTable = nullptr; + for (uintptr_t ptr = mapped_nso; + ptr <= mapped_nso + nso_size - sizeof(perf_conf_entry) * entryCnt; + ptr += sizeof(u32)) + { + u32* ptr32 = reinterpret_cast(ptr); + if (PtmTablePatternFn(ptr32)) { + confTable = reinterpret_cast(ptr); + break; + } + } + + if (!confTable) { + CRASH("confTable not found!"); + } + + PatcherEntry patches[] = { + { "CPU Ptm Boost", &CpuPtmBoost, 2, }, + { "MEM Ptm Max", &MemPtmMax, 9, }, + { "MEM Ptm Alt", &MemPtmAlt, 7, }, + }; + + for (u32 i = 0; i < entryCnt; i++) { + perf_conf_entry* entry = confTable + i; + + if (!PtmEntryIsValid(entry)) { + LOGGING("@%p", &entry); + CRASH("Invalid ptm confTable entry"); + } + + switch (entry->cpu_freq_1) { + case cpuPtmBoost: + patches[0].Apply(entry); + break; + case cpuPtmDefault: + case cpuPtmDevOC: + break; + default: + LOGGING("%u (0x%08x) @%p", entry->cpu_freq_1, entry->conf_id, &(entry->cpu_freq_1)); + CRASH("Unknown CPU Freq"); + } + + switch (entry->emc_freq_1) { + case memPtmLimit: + patches[1].Apply(entry); + break; + case memPtmAlt: + case memPtmClamp: + patches[2].Apply(entry); + break; + default: + LOGGING("%u (0x%08x) @%p", entry->emc_freq_1, entry->conf_id, &(entry->emc_freq_2)); + CRASH("Unknown MEM Freq"); + } + } + + for (auto& patch : patches) { + LOGGING("%s Count: %zu", patch.description, patch.patched_count); + if (R_FAILED(patch.CheckResult())) + CRASH(patch.description); + } +} + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/ptm/ptm.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/ptm/ptm.hpp new file mode 100644 index 00000000..38d6fb8d --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/ptm/ptm.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * 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 "../oc_suite_common.hpp" + +namespace ams::ldr::oc::ptm { + +typedef struct { + u32 conf_id; + u32 cpu_freq_1; // min-max pair? + u32 cpu_freq_2; + u32 gpu_freq_1; + u32 gpu_freq_2; + u32 emc_freq_1; + u32 emc_freq_2; + u32 padding; +} perf_conf_entry; + +constexpr u32 entryCnt = 16; +constexpr u32 cpuPtmDefault = 1020'000'000; +constexpr u32 cpuPtmDevOC = 1224'000'000; +constexpr u32 cpuPtmBoost = 1785'000'000; + +constexpr u32 memPtmLimit = 1600'000'000; +constexpr u32 memPtmAlt = 1331'200'000; +constexpr u32 memPtmClamp = 1065'600'000; + +void Patch(uintptr_t mapped_nso, size_t nso_size); + +} \ No newline at end of file diff --git a/Source/Atmosphere/stratosphere/loader/source/patch.py b/Source/Atmosphere/stratosphere/loader/source/patch.py new file mode 100755 index 00000000..0cf2b7dc --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/patch.py @@ -0,0 +1,74 @@ +#!python3 + +import sys +import os + +def file_replace_str(file_path, search_replace_list): + assert file_path + assert search_replace_list + with open(file_path, "r") as f: + content = f.read() + + for entry in search_replace_list: + (search, replace) = entry + if search in content: + content = content.replace(search, replace) + else: + assert replace in content, f"Pattern \"{search}\" not found" + + with open(file_path, "w") as f: + f.write(content) + + +dir_path = os.path.dirname(__file__) + +ldr_main = os.path.join(dir_path, "ldr_main.cpp") +file_replace_str(ldr_main, [("constinit u8 g_heap_memory[16_KB];", "constinit u8 g_heap_memory[16_KB + 5_KB];")]) + +ldr_process_creation = os.path.join(dir_path, "ldr_process_creation.cpp") +file_replace_str(ldr_process_creation, + [("""#include "ldr_ro_manager.hpp" + +namespace ams::ldr {""", +"""#include "ldr_ro_manager.hpp" +#include "oc/oc_suite.hpp" + +namespace ams::ldr {"""), + (""" NsoHeader g_nso_headers[Nso_Count]; + + Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {""", + """ 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) {"""), + (""" R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId()); + + /* Validate the kernel capabilities. */""", + """ R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId()); + + /* 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; + + /* Validate the kernel capabilities. */"""), + (""" LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size); + }""", + """ 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); + }""")]) + +ldr_meta = os.path.join(dir_path, "ldr_meta.cpp") +file_replace_str(ldr_meta, + [(""" Result ValidateAcidSignature(Meta *meta) { + /* Loader did not check signatures prior to 10.0.0. */""", + """ Result ValidateAcidSignature(Meta *meta) { + R_SUCCEED(); + /* Loader did not check signatures prior to 10.0.0. */""")]) diff --git a/Source/loaderConfigurator/dist/main.js b/Source/loaderConfigurator/dist/main.js index 5272b623..321c1b52 100644 --- a/Source/loaderConfigurator/dist/main.js +++ b/Source/loaderConfigurator/dist/main.js @@ -9,7 +9,7 @@ function FindMagicOffset(buffer) { } throw new Error("Invalid loader.kip file"); } -function CustEntry(name, size, desc, defval, minmax = [0, 1000000], step = 1, extra_validator = null) { +function CustEntry(name, size, desc, defval, minmax = [0, 1000000], step = 1, extra_validator) { this.name = name; this.size = size; this.desc = desc; @@ -25,35 +25,32 @@ function InitCustTable() { let cust = [ new CustEntry("mtcConf", 2, "DRAM Timing\
  • 0: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density.
  • \ -
  • 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.
  • \ -
  • 2: ENTIRE_TABLE_ERISTA: Not implemented.
  • \ -
  • 3: ENTIRE_TABLE_MARIKO: Not implemented.
  • ", 0, [0, 3]), +
  • 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.
  • ", 0, [0, 3]), new CustEntry("marikoCpuMaxClock", 4, "Mariko CPU Max Clock in kHz\
  • System default: 1785000
  • \ -
  • ≥ 2193000 will enable overvolting (> 1120 mV)
  • ", 2397000, [1785000, 3000000], 100, (x) => { return (x % 100) == 0; }), +
  • ≥ 2397000 will enable overvolting (> 1120 mV)
  • ", 2397000, [1785000, 3000000], 100, (x) => (x % 100) == 0), new CustEntry("marikoCpuBoostClock", 4, "Mariko CPU Boost Clock in kHz\
  • System default: 1785000
  • \ -
  • Must not be higher than marikoCpuMaxClock
  • ", 1785000, [1785000, 3000000], 100, (x) => { return (x % 100) == 0; }), +
  • Must not be higher than marikoCpuMaxClock
  • ", 1785000, [1785000, 3000000], 100, (x) => (x % 100) == 0), new CustEntry("marikoCpuMaxVolt", 4, "Mariko CPU Max Voltage in mV\
  • System default: 1120
  • \ -
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", 1220, [1100, 1300]), +
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", 1235, [1100, 1300]), new CustEntry("marikoGpuMaxClock", 4, "Mariko GPU Max Clock in kHz\
  • System default: 921600
  • \ -
  • Tegra X1+ official maximum: 1267200
  • ", 1305600, [768000, 1536000], 100, (x) => { return (x % 100) == 0; }), +
  • Tegra X1+ official maximum: 1267200
  • ", 1305600, [768000, 1536000], 100, (x) => (x % 100) == 0), new CustEntry("marikoEmcMaxClock", 4, "Mariko RAM Max Clock in kHz\
  • Values should be > 1600000, and divided evenly by 3200.
  • \ -
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", 1996800, [1612800, 2400000], 3200, (x) => { return (x % 3200) == 0; }), +
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", 1996800, [1612800, 2400000], 3200, (x) => (x % 3200) == 0), new CustEntry("eristaCpuOCEnable", 4, "Erista CPU Enable Overclock\ -
  • Not usable unless CPU cvb table is filled in
  • ", 0, [0, 1]), +
  • Not tested
  • ", 1, [0, 1]), new CustEntry("eristaCpuMaxVolt", 4, "Erista CPU Max Voltage in mV\ -
  • Acceptable range: 1100 ≤ x ≤ 1400
  • \ -
  • Not enabled by default
  • ", 0, [0, 1400], 100, (x) => { return x >= 1100; }), +
  • Acceptable range: 1100 ≤ x ≤ 1400
  • ", 1257, [0, 1400], 100, (x) => x >= 1100), new CustEntry("eristaEmcMaxClock", 4, "Erista RAM Max Clock in kHz\
  • Values should be > 1600000, and divided evenly by 3200.
  • \ -
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", 1862400, [1600000, 2400000], 3200, (x) => { return (x % 3200) == 0; }), +
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", 1862400, [1600000, 2400000], 3200, (x) => (x % 3200) == 0), new CustEntry("eristaEmcVolt", 4, "Erista RAM Voltage in uV\
  • Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.
  • \ -
  • Not enabled by default
  • ", 0, [0, 1250000], 12500, (x) => { return (x % 12500) == 0 && x >= 1100000; }), +
  • Not enabled by default
  • ", 0, [0, 1250000], 12500, (x) => (x % 12500) == 0 && x >= 1100000), ]; return cust; } @@ -160,7 +157,7 @@ function UpdateHTMLForm(cust) { tip.setAttribute("for", id); div.appendChild(tip); } - form.appendChild(div); + form === null || form === void 0 ? void 0 : form.appendChild(div); } input.value = dict[i.name].value; } @@ -222,8 +219,8 @@ fileInput.addEventListener('change', (event) => { reader.readAsArrayBuffer(event.target.files[0]); reader.onloadend = (progEvent) => { if (progEvent.target.readyState === FileReader.DONE) { - buffer = progEvent.target.result; try { + buffer = progEvent.target.result; let offset = FindMagicOffset(buffer); ParseCust(offset, buffer); } diff --git a/Source/loaderConfigurator/src/main.ts b/Source/loaderConfigurator/src/main.ts index b2cb8271..0333ec8e 100644 --- a/Source/loaderConfigurator/src/main.ts +++ b/Source/loaderConfigurator/src/main.ts @@ -1,5 +1,5 @@ const CUST_REV = 2; -var buffer; +var buffer: string | ArrayBuffer; function FindMagicOffset(buffer) { let view = new DataView(buffer); @@ -11,7 +11,7 @@ function FindMagicOffset(buffer) { throw new Error("Invalid loader.kip file"); } -function CustEntry(name, size, desc, defval, minmax = [0, 1_000_000], step = 1, extra_validator = null) { +function CustEntry(name: string, size: number, desc: string, defval: number, minmax = [0, 1_000_000], step = 1, extra_validator?) { this.name = name; this.size = size; this.desc = desc; @@ -31,9 +31,7 @@ function InitCustTable() { 2, "DRAM Timing\
  • 0: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density.
  • \ -
  • 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.
  • \ -
  • 2: ENTIRE_TABLE_ERISTA: Not implemented.
  • \ -
  • 3: ENTIRE_TABLE_MARIKO: Not implemented.
  • ", +
  • 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.
  • ", 0, [0, 3], ), @@ -42,11 +40,11 @@ function InitCustTable() { 4, "Mariko CPU Max Clock in kHz\
  • System default: 1785000
  • \ -
  • ≥ 2193000 will enable overvolting (> 1120 mV)
  • ", +
  • ≥ 2397000 will enable overvolting (> 1120 mV)
  • ", 2397_000, [1785_000, 3000_000], 100, - (x) => { return (x % 100) == 0; } + (x: number) => (x % 100) == 0 ), new CustEntry( "marikoCpuBoostClock", @@ -57,7 +55,7 @@ function InitCustTable() { 1785_000, [1785_000, 3000_000], 100, - (x) => { return (x % 100) == 0; } + (x: number) => (x % 100) == 0 ), new CustEntry( "marikoCpuMaxVolt", @@ -65,7 +63,7 @@ function InitCustTable() { "Mariko CPU Max Voltage in mV\
  • System default: 1120
  • \
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", - 1220, + 1235, [1100, 1300], ), new CustEntry( @@ -77,7 +75,7 @@ function InitCustTable() { 1305_600, [768_000, 1536_000], 100, - (x) => { return (x % 100) == 0; } + (x: number) => (x % 100) == 0 ), new CustEntry( "marikoEmcMaxClock", @@ -88,26 +86,25 @@ function InitCustTable() { 1996_800, [1612_800, 2400_000], 3200, - (x) => { return (x % 3200) == 0; } + (x: number) => (x % 3200) == 0 ), new CustEntry( "eristaCpuOCEnable", 4, "Erista CPU Enable Overclock\ -
  • Not usable unless CPU cvb table is filled in
  • ", - 0, +
  • Not tested
  • ", + 1, [0, 1] ), new CustEntry( "eristaCpuMaxVolt", 4, "Erista CPU Max Voltage in mV\ -
  • Acceptable range: 1100 ≤ x ≤ 1400
  • \ -
  • Not enabled by default
  • ", - 0, +
  • Acceptable range: 1100 ≤ x ≤ 1400
  • ", + 1257, [0, 1400], 100, - (x) => { return x >= 1100; } + (x: number) => x >= 1100 ), new CustEntry( "eristaEmcMaxClock", @@ -118,7 +115,7 @@ function InitCustTable() { 1862_400, [1600_000, 2400_000], 3200, - (x) => { return (x % 3200) == 0; } + (x: number) => (x % 3200) == 0 ), new CustEntry( "eristaEmcVolt", @@ -129,7 +126,7 @@ function InitCustTable() { 0, [0, 1250_000], 12500, - (x) => { return (x % 12500) == 0 && x >= 1100_000; } + (x: number) => (x % 12500) == 0 && x >= 1100000 ), ]; return cust; @@ -140,18 +137,18 @@ function ValidateCust(cust) { if (i.value == 0) continue; if (i.value < i.min || i.value > i.max) { - document.getElementById(i.name).focus(); + document.getElementById(i.name)!.focus(); throw new Error(`Expected range: ${i.min} ≤ ${i.name} ≤ ${i.max}, got ${i.value}`); } if (i.validator && !i.validator(i.value)) { - document.getElementById(i.name).focus(); + document.getElementById(i.name)!.focus(); throw new Error(`Invalid value: ${i.value}(${i.name})\nValidator: ${i.validator}`); } } } function SaveCust(cust, buffer) { - let dict = Object.assign({}, ...cust.map((x) => ({[x.name]: x}))); + let dict = Object.assign({}, ...cust.map((x: { name: any; }) => ({[x.name]: x}))); let view = new DataView(buffer); let storage = {}; for (let i of cust) { @@ -166,7 +163,7 @@ function SaveCust(cust, buffer) { view.setUint32(i.offset, i.value, true); break; default: - document.getElementById(i.name).focus(); + document.getElementById(i.name)!.focus(); throw new Error("Unknown size at " + i); } } @@ -195,7 +192,7 @@ function LastSaved() { function LoadLastSaved() { if (LastSaved()) { let storage = localStorage.getItem("last_saved"); - let sObj = JSON.parse(storage); + let sObj = JSON.parse(storage!); for (let key in sObj) { if (key == "custRev") { continue; @@ -205,16 +202,16 @@ function LoadLastSaved() { } } -function LoadDefault(cust) { - let dict = Object.assign({}, ...cust.map((x) => ({[x.name]: x}))); +function LoadDefault(cust: any[]) { + let dict = Object.assign({}, ...cust.map((x: { name: any; }) => ({[x.name]: x}))); for (let i of cust) { let id = i.name; (document.getElementById(id) as HTMLInputElement).value = i.defval; } } -function UpdateHTMLForm(cust) { - let dict = Object.assign({}, ...cust.map((x) => ({[x.name]: x}))); +function UpdateHTMLForm(cust: any[]) { + let dict = Object.assign({}, ...cust.map((x: { name: any; }) => ({[x.name]: x}))); let form = document.getElementById("form"); for (let i of cust) { let id = i.name; @@ -245,12 +242,12 @@ function UpdateHTMLForm(cust) { div.appendChild(tip); } - form.appendChild(div); + form?.appendChild(div); } input.value = dict[i.name].value; } - let btn = document.getElementById("load"); + let btn = document.getElementById("load")!; btn.classList.remove("hide"); if (LastSaved()) { btn.innerHTML = "Load Last Saved"; @@ -263,7 +260,7 @@ function UpdateHTMLForm(cust) { }); } - btn = document.getElementById("save"); + btn = document.getElementById("save")!; btn.classList.remove("hide"); btn.addEventListener('click', () => { try { @@ -294,7 +291,7 @@ function ParseCust(magicOffset, buffer) { i.value = view.getUint32(offset, true); break; default: - document.getElementById(i.name).focus(); + document.getElementById(i.name)!.focus(); throw new Error("Unknown size at " + i); } offset += i.size; @@ -306,10 +303,10 @@ function ParseCust(magicOffset, buffer) { const fileInput = document.getElementById("file") as HTMLInputElement; fileInput.addEventListener('change', (event) => { let reader = new FileReader(); - reader.readAsArrayBuffer((event.target as HTMLInputElement).files[0]); + reader.readAsArrayBuffer((event.target as HTMLInputElement).files![0]); reader.onloadend = (progEvent) => { - if (progEvent.target.readyState === FileReader.DONE) { - buffer = progEvent.target.result; + if (progEvent.target!.readyState === FileReader.DONE) { + buffer = progEvent.target!.result!; try { let offset = FindMagicOffset(buffer); ParseCust(offset, buffer);