From 6e4a28ee67058ecc74b6c325f0d0736b8a2476be Mon Sep 17 00:00:00 2001 From: Lightos1 <124387232+Lightos1@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:02:53 +0200 Subject: [PATCH] use hooking for erista mrf --- .../stratosphere/loader/source/oc/pcv/pcv.hpp | 10 +- .../loader/source/oc/pcv/pcv_erista.cpp | 158 +++++++++--------- 2 files changed, 87 insertions(+), 81 deletions(-) diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp index 056ca970..5a39b5bb 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp @@ -37,7 +37,8 @@ namespace ams::ldr::hoc::pcv { return true; }; - template Result CpuFreqCvbTable(u32 *ptr) { + template + Result CpuFreqCvbTable(u32 *ptr) { cvb_entry_t *default_table = isMariko ? (cvb_entry_t *)(&mariko::CpuCvbTableDefault) : (cvb_entry_t *)(&erista::CpuCvbTableDefault); cvb_entry_t *customize_table = nullptr; @@ -112,7 +113,8 @@ namespace ams::ldr::hoc::pcv { PATCH_OFFSET(&(entry->cvb_pll_param.c5), 0); } - template Result GpuFreqCvbTable(u32 *ptr) { + template + Result GpuFreqCvbTable(u32 *ptr) { cvb_entry_t *default_table = isMariko ? (cvb_entry_t *)(&mariko::GpuCvbTableDefault) : (cvb_entry_t *)(&erista::GpuCvbTableDefault); cvb_entry_t *customize_table; if (isMariko) { @@ -135,7 +137,7 @@ namespace ams::ldr::hoc::pcv { default: customize_table = const_cast(C.marikoGpuDvfsTableHiOPT); break; - } + } } else { switch (C.eristaGpuUV) { case 0: @@ -150,7 +152,7 @@ namespace ams::ldr::hoc::pcv { default: customize_table = const_cast(C.eristaGpuDvfsTable); break; - } + } } size_t default_entry_count = GetDvfsTableEntryCount(default_table); diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp index 12b0b1bd..c4d51f97 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp @@ -27,9 +27,25 @@ namespace ams::ldr::hoc::pcv::erista { - std::vector newEmcList; - u32 *nsoStart; - u32 *nsoEnd; + struct HookPayloadData { + struct { + EristaMtcTable *mtcTable; + u32 mtcCount; + } mtcTableAsm; + }; + DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData); + + namespace { + std::vector newEmcList; + u32 *nsoStart; + u32 *nsoEnd; + + struct { + u32 *getEristaMtcTableFnSite = nullptr; + EristaMtcTable *mtcTable = nullptr; + bool foundMtcTablePattern = false; + } getMtcTableCache; + } Result CpuVoltDvfs(u32 *ptr) { if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) { @@ -447,39 +463,6 @@ namespace ams::ldr::hoc::pcv::erista { } } - /* Relocate the table */ - /* Rescanning is simpler than trying to extract a bunch of data from the asm patch, performance impact is negligable */ - /* Also, this is more stable :P */ - u32 RepointEristaEmcTablePtr(uintptr_t fromSlot, uintptr_t toTable) { - constexpr u32 RetIns = 0xD65F03C0; /* ret */ - u32 patched = 0; - - for (u32 *p = nsoStart; p + 4 < nsoEnd; ++p) { - const u32 ins = *p; - if (!AsmIsAdrX0(ins)) { - continue; - } - - const uintptr_t pc = reinterpret_cast(p); - if (AsmAdrTarget(ins, pc) != fromSlot) { - continue; - } - if (!(AsmIsLdpX(p[1]) && AsmIsLdpX(p[2]) && p[3] == RetIns)) { - continue; - } - - /* adr only reaches +-1MB */ - const s64 delta = static_cast(toTable) - static_cast(pc); - if (delta > 0xFFFFF || delta < -0x100000) { - continue; - } - - PATCH_OFFSET(p, AsmSetAdrTarget(ins, pc, toTable)); - ++patched; - } - return patched; - } - /* The silicon instructs; the children obey... */ void MtcGenerateFreqTables() { newEmcList.clear(); @@ -548,24 +531,20 @@ namespace ams::ldr::hoc::pcv::erista { constexpr u32 StartAdjustment = offsetof(EristaMtcTable, rate_khz) + sizeof(EristaMtcTable) * (erista::MtcTableCountDefault - 1); u8 *startPtr = reinterpret_cast(ptr) - StartAdjustment; - const uintptr_t usedSlot = reinterpret_cast(startPtr) + mtcOffset; - EristaMtcTable *table = reinterpret_cast(usedSlot); + EristaMtcTable *table = reinterpret_cast(startPtr + mtcOffset); + R_TRY(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault)); PrepareMtcMemoryRegion(startPtr, table); table = reinterpret_cast(startPtr); - /* We must do this as the NLE tables don't have enough space past them for our extended ones */ - if (usedSlot != reinterpret_cast(startPtr)) { - if (RepointEristaEmcTablePtr(usedSlot, reinterpret_cast(startPtr)) == 0) { - AbortInvalidMtc("Failed to repoint emc table"); - } - } - if (R_FAILED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault))) { AbortInvalidMtc("Failed mtc validation"); } + /* Cache the table for hooks. */ + getMtcTableCache.mtcTable = table; + if (C.eristaEmcMaxClock <= EmcClkOSLimit) { R_SKIP(); } @@ -620,50 +599,71 @@ namespace ams::ldr::hoc::pcv::erista { // R_SUCCEED(); // } + HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count) { + const HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData); + + *count = data->mtcTableAsm.mtcCount; + return data->mtcTableAsm.mtcTable; + } Result MemMtcTableAsm(u32 *ptr) { + /* Return if the pattern was already found. */ + /* This pattern happens multiple times in this function., we only need to find it once. */ + R_UNLESS(!getMtcTableCache.foundMtcTablePattern, ldr::ResultInvalidMtcTablePattern()); + /* This is a mess but the compiler made this painful to patch so we must do it this way */ - constexpr s32 GoodAdrpOffset = -1; - constexpr s32 GoodMovOffset = -7; - constexpr s32 GoodBlOffset = 1; - constexpr u32 MtcGoodBlOpcode = 0x97fe6cfc; - - constexpr u32 MtcBadBlOpcode0 = 0x97ffae64; // bl nn::pcv::GetHardwareType - constexpr u32 MtcBadBlOpcode1 = 0x940036d5; // bl strcmp - constexpr u32 MtcBadAdrpAsm = 0xd00000a1; // adrp x1, s_ModuleResetStatus_ - - constexpr s32 MtcBadBlOffset0 = 2; - constexpr s32 MtcBadBlOffset1 = -1; - constexpr s32 MtcBadAdrpOffset = 1; + constexpr u32 AddrpOffset = 1; + constexpr u32 MovOffset = 7; + constexpr u32 BlOffset = 5; + constexpr u32 MovOffsetOld = 8; /* Ensure we don't dereference memory before nso start. */ - R_UNLESS(ptr + GoodMovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern()); + R_UNLESS(ptr - MovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern()); - /* Check for GetHardwareType asm and skip if it is found */ - /* The pattern will match on the first time, but the location is bad, so it must be skipped */ - if(AsmCompareAdrpNoImm(*(ptr + MtcBadAdrpOffset), MtcBadAdrpAsm) && AsmBlCompareOpcodeOnly(*(ptr + MtcBadBlOffset0), MtcBadBlOpcode0) && AsmBlCompareOpcodeOnly(*(ptr + MtcBadBlOffset1), MtcBadBlOpcode1)) { - R_SKIP(); - } - - /* We don't check for matching register because both registers must be x0 in order to pass the previous checks. */ - /* The correct instructions will always be x0 since the mtcTable pointer is returned. */ - u32 adrp = *(ptr + GoodAdrpOffset); + u32 adrp = *(ptr - AddrpOffset); R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern()); - /* Check for the branch instruction above the cbz to ensure we are patching the right location*/ - u32 bl = *(ptr + GoodBlOffset); - R_UNLESS(AsmBlCompareOpcodeOnly(bl, MtcGoodBlOpcode), ldr::ResultInvalidMtcTablePattern()); - + u32 bl = *(ptr - BlOffset); + R_UNLESS(AsmBlCompareOpcodeOnly(bl, MtcBlIns), ldr::ResultInvalidMtcTablePattern()); /* Check for the mov that actually sets the mtc table count. */ - u32 mov = *(ptr + GoodMovOffset); - R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern()); + u32 mov = *(ptr - MovOffset); + bool foundMov = false; + foundMov = asm_compare_no_rd(mov, MtcMovAsm); - /* Patch out the count of the mov to our custom mtc table amount*/ - u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), asm_get_rd(mov)); + if (!foundMov) { + mov = *(ptr + MovOffsetOld); + /* Check old firmware offset. */ + foundMov = asm_compare_no_rd(mov, MtcMovAsm); + } - PATCH_OFFSET(ptr + GoodMovOffset, movCountPatch); + R_UNLESS(foundMov, ldr::ResultInvalidMtcTablePattern()); + + constexpr u32 PrologueWindow = 140; + u32 *functionPrologue = FindFnPrologue(ptr, PrologueWindow, nsoStart); + R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidMtcTablePattern()); + + getMtcTableCache.getEristaMtcTableFnSite = functionPrologue; + getMtcTableCache.foundMtcTablePattern = true; + + R_SUCCEED(); + } + + Result InstallHooks() { + R_TRY(Hooks().CheckEnabled()); + + R_TRY(Hooks().CopyPayload()); + + R_UNLESS(getMtcTableCache.getEristaMtcTableFnSite != nullptr && getMtcTableCache.mtcTable != nullptr, ldr::ResultInvalidMtcTablePattern()); + + /* Copy the data to the payload. */ + auto *data = Hooks().BindData(e_HookPayloadData); + R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory()); + + data->mtcTableAsm.mtcTable = reinterpret_cast(Hooks().ToVa(getMtcTableCache.mtcTable)); + data->mtcTableAsm.mtcCount = newEmcList.size(); + R_TRY(INSTALL_IMPL_HOOK(getMtcTableCache.getEristaMtcTableFnSite, GetEristaMtcTableImpl)); R_SUCCEED(); } @@ -685,7 +685,7 @@ namespace ams::ldr::hoc::pcv::erista { {"GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial }, {"GPU Freq Table", GpuFreqCvbTable, 1, nullptr, GpuCvbDefaultMaxFreq }, {"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn }, - {"GPU PLL Max", & GpuFreqPllMax, 1, nullptr, GpuClkPllMax }, + {"GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax }, // {"GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit }, {"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn }, {"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit }, @@ -711,6 +711,10 @@ namespace ams::ldr::hoc::pcv::erista { CRASH(entry.description); } } + + if (R_FAILED(InstallHooks())) { + panic::SmcError(panic::Patch); + } } }