diff --git a/README.md b/README.md index ee6238a5..4bf17ffe 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Run build.bat or run "python -m PyInstaller --onefile --add-data "assets;assets ## Credits Lightos for RAM timings
-meha for Switch-Oc-Suite
+KazushiMe and meha for Switch-Oc-Suite
sys-clk team for sys-clk
b0rd2death for Ultrahand sys-clk fork
Lightos and Sammybigio2011 for early testing
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp index 8b790973..6b730350 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp @@ -29,61 +29,19 @@ namespace ams::ldr::oc { //volatile MarikoMtcTable MarikoMtcTablePlaceholder = { .rev = MARIKO_MTC_MAGIC, }; volatile CustomizeTable C = { -/* Common: - * - Boost Clock in kHz: - * Default: 1785000 - * Boost clock will be applied when applications request higher CPU frequency for quicker loading. - * This will be set regardless of whether sys-clk is enabled. - */ -.commonCpuBoostClock = 1785000, -/* - EMC Vddq (Erista Only) and RAM Vdd2 Voltage in uV - * Range: 1100'000 to 1250'000 uV - * Erista Default(HOS): 1125'000 (bootloader: 1100'000) - * Mariko Default: 1100'000 (It will not work without sys-clk-OC.) - * Value should be divided evenly by 12'500. - * Not enabled by default. - */ -.commonEmcMemVolt = 1175000, +.commonCpuBoostClock = 1785000, // Default boost clock + +.commonEmcMemVolt = 1175000, // LPDDR4X JEDEC Specification -/* Erista CPU: - * - Max Voltage in mV - * - CpuVoltL4T: 1235 - */ .eristaCpuMaxVolt = 1235, -/* Erista EMC(RAM): - * - RAM Clock in kHz - * [WARNING] - * RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM: - * - Graphical glitches - * - System instabilities - * - NAND corruption - */ -.eristaEmcMaxClock = 1862400, +.eristaEmcMaxClock = 1600000, // Maximum HB-MGCH ram rating -/* Mariko CPU: - * - Max Voltage in mV: - * Default voltage: 1120 - */ .marikoCpuMaxVolt = 1120, -/* Mariko EMC(RAM): - * - RAM Clock in kHz: - * Values should be ≥ 1600000, and divided evenly by 9600. - * [WARNING] - * RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM: - * - Graphical glitches - * - System instabilities - * - NAND corruption - */ -.marikoEmcMaxClock = 1996800, -/* - EMC Vddq (Mariko Only) Voltage in uV - * Range: 550'000 to 650'000 uV - * Value should be divided evenly by 5'000 - * Default: 600'000 - * Not enabled by default. - */ +.marikoEmcMaxClock = 1862400, // Hynix NME and Samsung AM-MGCJ Rating (others are 4766MT, 2133MHz) + .marikoEmcVddqVolt = 600000, .marikoCpuUV = 0, @@ -104,22 +62,26 @@ volatile CustomizeTable C = { .commonGpuVoltOffset = 0, -.marikoEmcDvbShift = 0, +.EmcDvbShift = 0, -.t1_tRCD = 0, -.t2_tRP = 0, -.t3_tRAS = 0, +// Defaults - (3-3-2) 0-1-4-3-6 + +// Primary +.t1_tRCD = 3, +.t2_tRP = 3, +.t3_tRAS = 2, +// Secondary .t4_tRRD = 0, -.t5_tRFC = 0, -.t6_tRTW = 0, -.t7_tWTR = 0, -.t8_tREFI = 0, +.t5_tRFC = 1, +.t6_tRTW = 4, +.t7_tWTR = 3, +.t8_tREFI= 6, -.mem_burst_latency = 0, +.mem_burst_latency = 0, // 0 - 1600l, 1 = 1866bl, 2 = 2133bl .marikoCpuVmin = 600, -.eristaGpuVmin = 810, +.eristaGpuVmin = 775, .marikoGpuVmin = 610, @@ -258,44 +220,44 @@ volatile CustomizeTable C = { { 691200, { }, { 1149425, 8144, -940, 808, -21583, 226 } }, { 768000, { }, { 1191317, 8144, -940, 808, -21583, 226 } }, { 844800, { }, { 1233208, 8144, -940, 808, -21583, 226 } }, - { 921600, { }, { 1275100, 8144, -940, 808, -21583, 226 } }, -// { 998400, { }, { 1316991, 8144, -940, 808, -21583, 226 } }, -// { 1075200, { }, { 1358882, 8144, -940, 808, -21583, 226 } }, +// { 921600, { }, { 1275100, 8144, -940, 808, -21583, 226 } }, +// { 998400, { }, { 1316991, 8144, -940, 808, -21583, 226 } }, +// { 1075200, { }, { 1358882, 8144, -940, 808, -21583, 226 } }, }, .eristaGpuDvfsTableSLT = { - { 76800, { }, { 772403, 8144, -940, 808, -21583, 226 } }, - { 153600, { }, { 814294, 8144, -940, 808, -21583, 226 } }, - { 230400, { }, { 856186, 8144, -940, 808, -21583, 226 } }, - { 307200, { }, { 898077, 8144, -940, 808, -21583, 226 } }, - { 384000, { }, { 939969, 8144, -940, 808, -21583, 226 } }, - { 460800, { }, { 981860, 8144, -940, 808, -21583, 226 } }, - { 537600, { }, { 1023751, 8144, -940, 808, -21583, 226 } }, - { 614400, { }, { 1065643, 8144, -940, 808, -21583, 226 } }, - { 691200, { }, { 1107534, 8144, -940, 808, -21583, 226 } }, - { 768000, { }, { 1149426, 8144, -940, 808, -21583, 226 } }, - { 844800, { }, { 1191317, 8144, -940, 808, -21583, 226 } }, - { 921600, { }, { 1233209, 8144, -940, 808, -21583, 226 } }, -// { 998400, { }, { 1275100, 8144, -940, 808, -21583, 226 } }, -// { 1075200, { }, { 1316991, 8144, -940, 808, -21583, 226 } }, + { 76800, { }, { 730512, 8144, -940, 808, -21583, 226 } }, + { 153600, { }, { 772403, 8144, -940, 808, -21583, 226 } }, + { 230400, { }, { 814294, 8144, -940, 808, -21583, 226 } }, + { 307200, { }, { 856186, 8144, -940, 808, -21583, 226 } }, + { 384000, { }, { 898077, 8144, -940, 808, -21583, 226 } }, + { 460800, { }, { 939969, 8144, -940, 808, -21583, 226 } }, + { 537600, { }, { 981860, 8144, -940, 808, -21583, 226 } }, + { 614400, { }, { 1023751, 8144, -940, 808, -21583, 226 } }, + { 691200, { }, { 1065643, 8144, -940, 808, -21583, 226 } }, + { 768000, { }, { 1107534, 8144, -940, 808, -21583, 226 } }, + { 844800, { }, { 1149426, 8144, -940, 808, -21583, 226 } }, + { 921600, { }, { 1191317, 8144, -940, 808, -21583, 226 } }, +// { 998400, { }, { 1275100, 8144, -940, 808, -21583, 226 } }, +// { 1075200, { }, { 1316991, 8144, -940, 808, -21583, 226 } }, }, .eristaGpuDvfsTableHigh = { - { 76800, { }, { 730512, 8144, -940, 808, -21583, 226 } }, - { 153600, { }, { 772403, 8144, -940, 808, -21583, 226 } }, - { 230400, { }, { 814295, 8144, -940, 808, -21583, 226 } }, - { 307200, { }, { 856186, 8144, -940, 808, -21583, 226 } }, - { 384000, { }, { 898078, 8144, -940, 808, -21583, 226 } }, - { 460800, { }, { 939969, 8144, -940, 808, -21583, 226 } }, - { 537600, { }, { 981860, 8144, -940, 808, -21583, 226 } }, - { 614400, { }, { 1023752, 8144, -940, 808, -21583, 226 } }, - { 691200, { }, { 1065643, 8144, -940, 808, -21583, 226 } }, - { 768000, { }, { 1107535, 8144, -940, 808, -21583, 226 } }, - { 844800, { }, { 1149426, 8144, -940, 808, -21583, 226 } }, - { 921600, { }, { 1191318, 8144, -940, 808, -21583, 226 } }, -// { 998400, { }, { 1233209, 8144, -940, 808, -21583, 226 } }, -// { 1075200, { }, { 1275100, 8144, -940, 808, -21583, 226 } }, + { 76800, { }, { 646730, 8144, -940, 808, -21583, 226 } }, + { 153600, { }, { 688621, 8144, -940, 808, -21583, 226 } }, + { 230400, { }, { 730512, 8144, -940, 808, -21583, 226 } }, + { 307200, { }, { 772403, 8144, -940, 808, -21583, 226 } }, + { 384000, { }, { 814295, 8144, -940, 808, -21583, 226 } }, + { 460800, { }, { 856186, 8144, -940, 808, -21583, 226 } }, + { 537600, { }, { 898078, 8144, -940, 808, -21583, 226 } }, + { 614400, { }, { 939969, 8144, -940, 808, -21583, 226 } }, + { 691200, { }, { 981860, 8144, -940, 808, -21583, 226 } }, + { 768000, { }, { 1023752, 8144, -940, 808, -21583, 226 } }, + { 844800, { }, { 1065643, 8144, -940, 808, -21583, 226 } }, + { 921600, { }, { 1107535, 8144, -940, 808, -21583, 226 } }, + { 998400, { }, { 1149426, 8144, -940, 808, -21583, 226 } }, +// { 1075200, { }, { 1275100, 8144, -940, 808, -21583, 226 } }, }, /* - Mariko GPU DVFS Table: diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp index e4583c5c..98dd54e2 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp @@ -66,7 +66,7 @@ u32 commonGpuVoltOffset; - u32 marikoEmcDvbShift; + u32 EmcDvbShift; // advanced config u32 t1_tRCD; diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp index 72d04fd2..aef70d6d 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp @@ -12,171 +12,140 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * - * from GCC preprocessor output */ +#pragma once +#include "oc_common.hpp" - #pragma once +namespace ams::ldr::oc { + #define MAX(A, B) std::max(A, B) + #define MIN(A, B) std::min(A, B) + #define CEIL(A) std::ceil(A) + #define FLOOR(A) std::floor(A) - #include "oc_common.hpp" + /* Primary timings. */ + const std::array tRCD_values = {18, 17, 16, 15, 14, 13, 12, 11}; + const std::array tRP_values = {18, 17, 16, 15, 14, 13, 12, 11}; + const std::array tRAS_values = {42, 36, 34, 32, 30, 28, 26, 24, 22, 20}; - namespace ams::ldr::oc { - #define MAX(A, B) std::max(A, B) - #define MIN(A, B) std::min(A, B) - #define CEIL(A) std::ceil(A) - #define FLOOR(A) std::floor(A) + /* Secondary timings. */ + const std::array tRRD_values = {10.0, 7.5, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0}; + const std::array tRFC_values = {140, 120, 100, 80, 60, 40}; + const std::array tRTW_values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; /* Is this even correct? */ + const std::array tWTR_values = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}; + const std::array tREFpb_values = {488, 732, 488 * 2, 488 * 3, 488 * 4, 488 * 6, 488 * 8}; /* TODO: Figure out if it's actually 8 and if this is even right. */ - //Preset One - const std::array tRCD_values = {18, 17, 16, 15, 14, 13, 12, 11}; - const std::array tRP_values = {18, 17, 16, 15, 14, 13, 12, 11}; - const std::array tRAS_values = {42, 36, 34, 32, 30, 28, 26, 24, 22, 20}; - - // Preset Two - const std::array tRRD_values = {10, 7.5, 6, 5, 4, 3, 2, 1}; - const std::array tFAW_values = {40, 30, 24, 16, 12}; - - // Preset Three - const std::array tWR_values = {18, 15, 15, 12, 12, 8}; // TODO: identify what exactly eos tRTW even is (is it even real?) - const std::array tRTP_values = {7.5, 7.5, 6, 6, 4, 4}; - - // Preset Four - const std::array tRFC_values = {140, 120, 100, 80, 70, 60}; - - // Preset Five - const std::array tWTR_values = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}; - - // Preset Six - const std::array tREFpb_values = {488, 976, 1952, 3256, 6512, 9999}; - - // const u32 TIMING_PRESET_ONE = C.ramTimingPresetOne; - // const u32 TIMING_PRESET_TWO = C.ramTimingPresetTwo; - const u32 TIMING_PRESET_THREE = 0; - // const u32 TIMING_PRESET_FOUR = C.ramTimingPresetFour; - // const u32 TIMING_PRESET_FIVE = C.ramTimingPresetFive; - // const u32 TIMING_PRESET_SIX = C.ramTimingPresetSix; - // const u32 TIMING_PRESET_SEVEN = C.ramTimingPresetSeven; - - // Burst Length - const u32 BL = 16; - - // Write Latency + const u32 BL = 16; + const u32 RL = 28 + C.mem_burst_latency; const u32 WL = 14 + C.mem_burst_latency; - // Read Latency - const u32 RL = 32 + C.mem_burst_latency; - // tRFCpb (refresh cycle time per bank) in ns for 8Gb density - const u32 tRFCpb = !C.t5_tRFC ? 140 : tRFC_values[C.t5_tRFC-1]; - - // tRFCab (refresh cycle time all banks) in ns for 8Gb density - const u32 tRFCab = !C.t5_tRFC ? 280 : 2*tRFCpb; - - // tRAS (row active time) in ns - const u32 tRAS = !C.t3_tRAS ? 42 : tRAS_values[C.t3_tRAS-1]; - - // tRPpb (row precharge time per bank) in ns - const u32 tRPpb = !C.t2_tRP ? 18 : tRP_values[C.t2_tRP-1]; - - // tRPab (row precharge time all banks) in ns - const u32 tRPab = !C.t2_tRP ? 21 : tRPpb + 3; - - // tRC (ACTIVATE-ACTIVATE command period same bank) in ns - const u32 tRC = tRPab + tRAS; + /* Refresh Cycle time. (All Banks) */ + const u32 tRFCab = (u32)(tRFC_values[C.t5_tRFC] * 1.5); + /* Precharge to Precharge Delay. (Cycles) */ + /* Don't touch! */ const u32 tPPD = 4; - const u32 tRTW = !C.t6_tRTW ? 10 : tWTR_values[C.t6_tRTW-1]; + /* Four-bank ACTIVATE Window */ + const u32 tFAW = 30; - // Write-to-Read delay - const u32 tWTR = !C.t7_tWTR ? 10 : tWTR_values[C.t7_tWTR-1]; - - // Internal READ-to-PRE-CHARGE command delay in ns - const double tRTP = !TIMING_PRESET_THREE ? 7.5 : tRTP_values[TIMING_PRESET_THREE-1]; - - // write recovery time - const u32 tWR = !TIMING_PRESET_THREE ? 18 : tWR_values[TIMING_PRESET_THREE-1]; - - // tRCD (RAS-CAS delay) in ns - const u32 tRCD = !C.t1_tRCD ? 18 : tRCD_values[C.t1_tRCD-1]; - - // tRRD (Active bank-A to Active bank-B) in ns - const double tRRD = !C.t4_tRRD ? 10. : tRRD_values[C.t4_tRRD-1]; - - // tREFpb (average refresh interval per bank) in ns for 8Gb density - const u32 tREFpb = !C.t8_tREFI ? 488 : tREFpb_values[C.t8_tREFI-1]; - - // Exit power-down to next valid command delay - const double tXP = 7.5; - - // tXSR (SELF REFRESH exit to next valid command delay) in ns - const double tXSR = tRFCab + 7.5; - - // Minimum self refresh time (entry to exit) - const u32 tSR = 15; - - // tFAW (Four-bank Activate Window) in ns - const u32 tFAW = 40;// !TIMING_PRESET_TWO ? 40 : tFAW_values[TIMING_PRESET_TWO-1]; TOGO - - // #_of_rows per die for 8Gb density - const u32 numOfRows = 131072; - - // {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 = MIN((u32)65472, u32(std::ceil((double(tREFpb) * C.marikoEmcMaxClock / numOfRows * 1.048 / 2 - 64))) / 4 * 4); - const u32 REFBW = MIN((u32)65536, REFRESH+64); - - // DQS output access time from CK_t/CK_c - const double tDQSCK_min = 1.5; + /* DQS output access time from CK_t/CK_c. */ const double tDQSCK_max = 3.5; - // Write preamble (tCK) - const double tWPRE = 1.8; - // Read postamble (tCK) - const double tRPST = 0.4; + const double tWPRE = 2.0; + + /* tCK Read postamble. */ + const double tRPST = 0.5; namespace pcv::erista { - // tCK_avg (average clock period) in ns - const double tCK_avg = 1000'000. / C.eristaEmcMaxClock; + /* tCK_avg may have to be improved... */ + const double tCK_avg = 1000'000.0 / C.eristaEmcMaxClock; - // minimum number of cycles from any read command to any write command, irrespective of bank - const u32 R2W = CEIL (RL + CEIL(tDQSCK_max/tCK_avg) + BL/2 - WL + tWPRE + FLOOR(tRPST)) + 6; + /* Primary timings. */ + const double tRCD = MAX(tRCD_values[C.t1_tRCD], 4.0 * tCK_avg); + const double tRPpb = MAX(tRP_values[C.t2_tRP], 4.0 * tCK_avg); + const double tRAS = MAX(tRAS_values[C.t3_tRAS], 3.0 * tCK_avg); - // Delay Time From WRITE-to-READ - const u32 W2R = WL + BL/2 + 1 + CEIL(tWTR/tCK_avg) - 6; + /* Secondary timings. */ + const double tRRD = MAX(tRRD_values[C.t4_tRRD], 4.0 * tCK_avg); + const double tRFCpb = tRFC_values[C.t5_tRFC]; + const u32 tRTW = tRTW_values[C.t6_tRTW]; + const double tWTR = MAX(tWTR_values[C.t7_tWTR], 8.0 * tCK_avg); + const u32 tREFpb = tREFpb_values[C.t8_tREFI]; - // write-to-precharge time for commands to the same bank in cycles - const u32 WTP = WL + BL/2 + 1 + CEIL(tWR/tCK_avg) - 8; + /* Latency stuff. */ + const u32 R2W = CEIL(RL + CEIL(tDQSCK_max/tCK_avg) + (BL/2) - WL + tWPRE + FLOOR(tRPST)) + 6; + const u32 W2R = WL + (BL/2) + 1 + tWTR - 4; + const u32 WTP = WL + (BL/2) + 1 + tWTR - 6; + /* Refresh stuff. */ 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 = MIN((u32)65472, u32(std::ceil((double(tREFpb) * C.eristaEmcMaxClock / numOfRows * 1.048 / 2 - 64))) / 4 * 4); const u32 REFBW = MIN((u32)65536, REFRESH+64); - + + /* Do not touch stuff. */ + /* ACTIVATE-to-ACTIVATE command period. (same bank) */ + const double tRC = tRAS + tRPpb; + + /* Minimum Self-Refresh Time. (Entry to Exit) */ + const double tSR = MAX(15.0, 3.0 * tCK_avg); + /* SELF REFRESH exit to next valid command delay. */ + const double tXSR = MAX(tRFCab + 7.5, 2.0 * tCK_avg); + + /* Exit power down to next valid command delay. */ + const double tXP = MAX(7.5, 5.0 * tCK_avg); + + /* Internal READ to PRECHARGE command delay. */ + const double tRTP = MAX(7.5, 8.0 * tCK_avg); + + /* Row Precharge Time. (all banks) */ + const double tRPab = MAX(21.0, 4.0 * tCK_avg); } namespace pcv::mariko { - // tCK_avg (average clock period) in ns - const double tCK_avg = 1000'000. / C.marikoEmcMaxClock; + /* tCK_avg may have to be improved... */ + const double tCK_avg = 1000'000.0 / C.marikoEmcMaxClock; - const u32 R2W = CEIL (RL + CEIL(tDQSCK_max/tCK_avg) + BL/2 - WL + tWPRE + FLOOR(tRPST)) + 6; + /* Primary timings. */ + const double tRCD = MAX(tRCD_values[C.t1_tRCD], 4.0 * tCK_avg); + const double tRPpb = MAX(tRP_values[C.t2_tRP], 4.0 * tCK_avg); + const double tRAS = MAX(tRAS_values[C.t3_tRAS], 3.0 * tCK_avg); - // Delay Time From WRITE-to-READ - const u32 W2R = WL + BL/2 + 1 + CEIL(tWTR/tCK_avg) - 6; + /* Secondary timings. */ + const double tRRD = MAX(tRRD_values[C.t4_tRRD], 4.0 * tCK_avg); + const double tRFCpb = tRFC_values[C.t5_tRFC]; + const u32 tRTW = tRTW_values[C.t6_tRTW]; + const double tWTR = MAX(tWTR_values[C.t7_tWTR], 8.0 * tCK_avg); + const u32 tREFpb = tREFpb_values[C.t8_tREFI]; - // write-to-precharge time for commands to the same bank in cycles - const u32 WTP = WL + BL/2 + 1 + CEIL(tWR/tCK_avg) - 8; + /* Latency stuff. */ + const u32 R2W = CEIL(RL + CEIL(tDQSCK_max/tCK_avg) + (BL/2) - WL + tWPRE + FLOOR(tRPST)) + 6; + const u32 W2R = WL + (BL/2) + 1 + tWTR - 6; + const u32 WTP = WL + (BL/2) + 1 + tWTR - 8; - const u32 numOfRows = 131072; + /* Refresh stuff. */ + const u32 numOfRows = 65536; + const u32 REFRESH = MIN((u32)65472, u32(std::ceil((double(tREFpb) * C.eristaEmcMaxClock / numOfRows * 1.048 / 2 - 64))) / 4 * 4); + const u32 REFBW = MIN((u32)65536, REFRESH+64); + + /* Do not touch stuff. */ + /* ACTIVATE-to-ACTIVATE command period. (same bank) */ + const double tRC = tRAS + tRPpb; + + /* Minimum Self-Refresh Time. (Entry to Exit) */ + const double tSR = MAX(15.0, 3.0 * tCK_avg); + /* SELF REFRESH exit to next valid command delay. */ + const double tXSR = MAX(tRFCab + 7.5, 2.0 * tCK_avg); + + /* Exit power down to next valid command delay. */ + const double tXP = MAX(7.5, 5.0 * tCK_avg); + + /* Internal READ to PRECHARGE command delay. */ + const double tRTP = MAX(7.5, 8.0 * tCK_avg); + + /* Row Precharge Time. (all banks) */ + const double tRPab = MAX(21.0, 4.0 * tCK_avg); - // {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 = MIN((u32)65472, u32(std::ceil((double(tREFpb) * C.marikoEmcMaxClock / numOfRows * 1.048 / 2 - 64))) / 4 * 4); - const u32 REFBW = MIN((u32)130944, REFRESH+64); - } + } 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 311ecd19..e4719443 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp @@ -65,10 +65,10 @@ Result GpuVmin(u32 *ptr) { Result CpuVoltDfll(u32* ptr) { cvb_cpu_dfll_data *entry = reinterpret_cast(ptr); -// R_UNLESS(entry->tune0_low == 0x0000FFCF, ldr::ResultInvalidCpuVoltDfllEntry()); -// R_UNLESS(entry->tune0_high == 0x00000000, ldr::ResultInvalidCpuVoltDfllEntry()); -// R_UNLESS(entry->tune1_low == 0x012207FF, ldr::ResultInvalidCpuVoltDfllEntry()); -// R_UNLESS(entry->tune1_high == 0x03FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune0_low == 0x152f01, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune0_high == 0x00000000, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune1_low == 0x00000000, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune1_high == 0x00000000, ldr::ResultInvalidCpuVoltDfllEntry()); if(!C.eristaCpuUV) { R_SKIP(); } @@ -78,24 +78,24 @@ Result GpuVmin(u32 *ptr) { switch(C.eristaCpuUV) { case 1: - PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); //process_id 0 // EOS UV1 - PATCH_OFFSET(&(entry->tune1_low), 0x027007FF); + PATCH_OFFSET(&(entry->tune0_high), 0xFFFF); //process_id 0 // EOS UV1 + PATCH_OFFSET(&(entry->tune1_high), 0x027007FF); break; case 2: - PATCH_OFFSET(&(entry->tune0_low), 0x0000EFFF); //process_id 1 // EOS Uv2 - PATCH_OFFSET(&(entry->tune1_low), 0x027407FF); + PATCH_OFFSET(&(entry->tune0_high), 0x0000EFFF); //process_id 1 // EOS Uv2 + PATCH_OFFSET(&(entry->tune1_high), 0x027407FF); break; case 3: - PATCH_OFFSET(&(entry->tune0_low), 0x0000DFFF); //process_id 0 // EOS UV3 - PATCH_OFFSET(&(entry->tune1_low), 0x027807FF); + PATCH_OFFSET(&(entry->tune0_high), 0x0000DFFF); //process_id 0 // EOS UV3 + PATCH_OFFSET(&(entry->tune1_high), 0x027807FF); break; case 4: - PATCH_OFFSET(&(entry->tune0_low), 0x0000DFDF); //process_id 1 // EOS Uv4 - PATCH_OFFSET(&(entry->tune1_low), 0x027A07FF); + PATCH_OFFSET(&(entry->tune0_high), 0x0000DFDF); //process_id 1 // EOS Uv4 + PATCH_OFFSET(&(entry->tune1_high), 0x027A07FF); break; case 5: - PATCH_OFFSET(&(entry->tune0_low), 0x0000CFDF); // EOS UV5 - PATCH_OFFSET(&(entry->tune1_low), 0x037007FF); + PATCH_OFFSET(&(entry->tune0_high), 0x0000CFDF); // EOS UV5 + PATCH_OFFSET(&(entry->tune1_high), 0x037007FF); break; default: break; @@ -159,91 +159,97 @@ Result GpuVmin(u32 *ptr) { R_SUCCEED(); } - void MemMtcTableAutoAdjust(EristaMtcTable *table) { - if (C.mtcConf != AUTO_ADJ) - return; +void MemMtcTableAutoAdjust(EristaMtcTable *table) { + if (C.mtcConf != AUTO_ADJ) + return; -#define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \ - TABLE->burst_regs.PARAM = VALUE; \ - TABLE->shadow_regs_ca_train.PARAM = VALUE; \ - TABLE->shadow_regs_quse_train.PARAM = VALUE; \ - TABLE->shadow_regs_rdwr_train.PARAM = VALUE; + using namespace pcv::erista; -#define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg)) + #define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \ + TABLE->burst_regs.PARAM = VALUE; \ + TABLE->shadow_regs_ca_train.PARAM = VALUE; \ + TABLE->shadow_regs_quse_train.PARAM = VALUE; \ + TABLE->shadow_regs_rdwr_train.PARAM = VALUE; - 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_r2p, GET_CYCLE_CEIL(tRTP)); - WRITE_PARAM_ALL_REG(table, emc_r2w, R2W); - WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); - WRITE_PARAM_ALL_REG(table, emc_w2p, WTP); - 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(tXP)); - WRITE_PARAM_ALL_REG(table, emc_pdex2rd, GET_CYCLE_CEIL(tXP)); - WRITE_PARAM_ALL_REG(table, emc_txsr, MIN(GET_CYCLE_CEIL(tXSR), (u32)0x3fe)); - WRITE_PARAM_ALL_REG(table, emc_txsrdll, MIN(GET_CYCLE_CEIL(tXSR), (u32)0x3fe)); - WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tSR)); - 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, REFBW); + #define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg)) -#define WRITE_PARAM_BURST_MC_REG(TABLE, PARAM, VALUE) TABLE->burst_mc_regs.PARAM = VALUE; + /* Primary timings. */ +// WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tCK_avg)); + 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_ras, GET_CYCLE_CEIL(tRAS)); + WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(tRPpb)); - constexpr u32 MC_ARB_DIV = 4; - constexpr u32 MC_ARB_SFA = 2; - table->burst_mc_regs.mc_emem_arb_timing_rcd = CEIL(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV) - 2; - table->burst_mc_regs.mc_emem_arb_timing_rp = CEIL(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV) - 1 + MC_ARB_SFA; - table->burst_mc_regs.mc_emem_arb_timing_rc = CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV) - 1; - table->burst_mc_regs.mc_emem_arb_timing_ras = CEIL(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV) - 2; - table->burst_mc_regs.mc_emem_arb_timing_faw = CEIL(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV) - 1; - table->burst_mc_regs.mc_emem_arb_timing_rrd = CEIL(GET_CYCLE_CEIL(tRRD) / MC_ARB_DIV) - 1; - table->burst_mc_regs.mc_emem_arb_timing_rap2pre = CEIL(GET_CYCLE_CEIL(tRTP) / MC_ARB_DIV); - table->burst_mc_regs.mc_emem_arb_timing_wap2pre = CEIL(WTP / MC_ARB_DIV); - table->burst_mc_regs.mc_emem_arb_timing_r2r = CEIL(table->burst_regs.emc_rext / MC_ARB_DIV) - 1 + MC_ARB_SFA; - table->burst_mc_regs.mc_emem_arb_timing_w2w = CEIL(table->burst_regs.emc_wext / MC_ARB_DIV) - 1 + MC_ARB_SFA; - table->burst_mc_regs.mc_emem_arb_timing_r2w = CEIL(R2W / MC_ARB_DIV) - 1 + MC_ARB_SFA; - table->burst_mc_regs.mc_emem_arb_timing_w2r = CEIL(W2R / MC_ARB_DIV) - 1 + MC_ARB_SFA; - table->burst_mc_regs.mc_emem_arb_timing_rfcpb = CEIL(GET_CYCLE_CEIL(tRFCpb) / MC_ARB_DIV); - // table->burst_mc_regs.mc_emem_arb_timing_ccdmw = CEIL(tCCDMW / MC_ARB_DIV) -1 + MC_ARB_SFA; - } + /* Secondary timings. */ + WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD)); + WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(tRFCpb)); + WRITE_PARAM_ALL_REG(table, emc_r2w, R2W); + WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); + WRITE_PARAM_ALL_REG(table, emc_trefbw, REFBW); + + WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(tRFCab)); + WRITE_PARAM_ALL_REG(table, emc_tppd, tPPD); + WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW)); + WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(tRC)); + WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tSR)); + + WRITE_PARAM_ALL_REG(table, emc_tcke, MAX(4u, GET_CYCLE_CEIL(7.5))); + WRITE_PARAM_ALL_REG(table, emc_txsr, GET_CYCLE_CEIL(tXSR)); + WRITE_PARAM_ALL_REG(table, emc_r2p, GET_CYCLE_CEIL(tRTP)); + WRITE_PARAM_ALL_REG(table, emc_w2p, WTP); + WRITE_PARAM_ALL_REG(table, emc_pdex2wr, GET_CYCLE_CEIL(tXP)); + WRITE_PARAM_ALL_REG(table, emc_pdex2rd, GET_CYCLE_CEIL(tXP)); + + constexpr u32 MC_ARB_DIV = 4; + constexpr u32 MC_ARB_SFA = 2; + + table->burst_mc_regs.mc_emem_arb_timing_rcd = u32(CEIL(GET_CYCLE_CEIL(tRCD) / double(MC_ARB_DIV))) - 2; + table->burst_mc_regs.mc_emem_arb_timing_rp = u32(CEIL(GET_CYCLE_CEIL(tRPpb) / double(MC_ARB_DIV))) - 1 + MC_ARB_SFA; + table->burst_mc_regs.mc_emem_arb_timing_rc = u32(CEIL(GET_CYCLE_CEIL(tRC) / double(MC_ARB_DIV))) - 1; + table->burst_mc_regs.mc_emem_arb_timing_ras = u32(CEIL(GET_CYCLE_CEIL(tRAS) / double(MC_ARB_DIV))) - 2; + table->burst_mc_regs.mc_emem_arb_timing_faw = u32(CEIL(GET_CYCLE_CEIL(tFAW) / double(MC_ARB_DIV))) - 1; + table->burst_mc_regs.mc_emem_arb_timing_rrd = u32(CEIL(GET_CYCLE_CEIL(tRRD) / double(MC_ARB_DIV))) - 1; + table->burst_mc_regs.mc_emem_arb_timing_rap2pre = u32(CEIL(GET_CYCLE_CEIL(tRTP) / double(MC_ARB_DIV))); + table->burst_mc_regs.mc_emem_arb_timing_r2w = u32(CEIL(R2W / double(MC_ARB_DIV))); + table->burst_mc_regs.mc_emem_arb_timing_w2r = u32(CEIL(W2R / double(MC_ARB_DIV))); + #undef GET_CYCLE_CEIL +} 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); + if(C.eristaEmcMaxClock != EmcClkOSLimit) { + 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()); + // 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 <= EmcClkOSLimit) + 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)); + + MemMtcTableAutoAdjust(table_list[0]); + PATCH_OFFSET(ptr, C.eristaEmcMaxClock); + + // Handle customize table replacement + // if (C.mtcConf == CUSTOMIZED_ALL) { + // MemMtcCustomizeTable(table_list[0], const_cast(C.eristaMtcTable)); + //} + + R_SUCCEED(); + } else { + R_SUCCEED(); // Skip changing table on default freq } - - if (C.eristaEmcMaxClock <= EmcClkOSLimit) - 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)); - - MemMtcTableAutoAdjust(table_list[0]); - PATCH_OFFSET(ptr, C.eristaEmcMaxClock); - - // Handle customize table replacement - // if (C.mtcConf == CUSTOMIZED_ALL) { - // MemMtcCustomizeTable(table_list[0], const_cast(C.eristaMtcTable)); - //} - - R_SUCCEED(); } Result MemFreqMax(u32 *ptr) { @@ -255,6 +261,42 @@ Result GpuVmin(u32 *ptr) { 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.eristaEmcMaxClock <= EmcClkOSLimit) + // R_SKIP(); + + // int32_t voltAdd = 25*C.EmcDvbShift; + + // #define DVB_VOLT(zero, one, two) std::min(zero+voltAdd, 1050), std::min(one+voltAdd, 1025), std::min(two+voltAdd, 1000), + + // if (C.marikoEmcMaxClock < 1862400) { + // std::memcpy(new_start, default_end, sizeof(emc_dvb_dvfs_table_t)); + // } else if (C.marikoEmcMaxClock < 2131200){ + // emc_dvb_dvfs_table_t oc_table = { 1862400, { 950, 925, 900, } }; + // std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); + // } else if (C.marikoEmcMaxClock < 2227000){ + // emc_dvb_dvfs_table_t oc_table = { 2131200, { 975, 950, 925, } }; + // std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); + // } else { + // emc_dvb_dvfs_table_t oc_table = { 2227000, { DVB_VOLT(1000, 975, 950) } }; + // std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); + // } + // new_start->freq = C.eristaEmcMaxClock; + // /* Max dvfs entry is 32, but HOS doesn't seem to boot if exact freq doesn't exist in dvb table, + // reason why it's like this + // */ + + // R_SUCCEED(); + // } + void Patch(uintptr_t mapped_nso, size_t nso_size) { u32 CpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq); u32 GpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq); @@ -270,6 +312,7 @@ Result GpuVmin(u32 *ptr) { {"MEM Freq Mtc", &MemFreqMtcTable, 0, nullptr, EmcClkOSLimit}, {"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit}, {"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit}, + // {"MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit}, {"MEM Volt", &MemVoltHandler, 2, nullptr, MemVoltHOS}, {"GPU Vmin", &GpuVmin, 0, nullptr, gpuVmin}, }; diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp index 51029ff8..35062b6c 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp @@ -324,7 +324,7 @@ namespace ams::ldr::oc::pcv::mariko constexpr u32 MC_ARB_DIV = 4; constexpr u32 MC_ARB_SFA = 2; - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_cfg, C.marikoEmcMaxClock / (33.3 * 1000) / MC_ARB_DIV); //CYCLES_PER_UPDATE: The number of mcclk cycles per deadline timer update + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_cfg, C.marikoEmcMaxClock / (33.3 * 1000) / MC_ARB_DIV); // CYCLES_PER_UPDATE: The number of mcclk cycles per deadline timer update WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rcd, CEIL(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV) - 2) WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rp, CEIL(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV) - 1 + MC_ARB_SFA) WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rc, CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV) - 1) @@ -333,9 +333,9 @@ namespace ams::ldr::oc::pcv::mariko WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rrd, CEIL(GET_CYCLE_CEIL(tRRD) / MC_ARB_DIV) - 1) WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rap2pre, CEIL(GET_CYCLE_CEIL(tRTP) / MC_ARB_DIV)) WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_wap2pre, CEIL((WTP) / MC_ARB_DIV)) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_r2r, CEIL(table->burst_regs.emc_rext / MC_ARB_DIV) - 1 + MC_ARB_SFA) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_r2w, CEIL((R2W) / MC_ARB_DIV) - 1 + MC_ARB_SFA) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_w2r, CEIL((W2R) / MC_ARB_DIV) - 1 + MC_ARB_SFA) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_r2r, CEIL(table->burst_regs.emc_rext / MC_ARB_DIV) - 1 + MC_ARB_SFA) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_r2w, CEIL((R2W) / MC_ARB_DIV) - 1 + MC_ARB_SFA) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_w2r, CEIL((W2R) / MC_ARB_DIV) - 1 + MC_ARB_SFA) WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rfcpb, CEIL(GET_CYCLE_CEIL(tRFCpb) / MC_ARB_DIV)) } @@ -412,48 +412,70 @@ namespace ams::ldr::oc::pcv::mariko 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; - + 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); + 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 <= EmcClkOSLimit) R_SKIP(); - - int32_t voltAdd = 25*C.marikoEmcDvbShift; - - #define DVB_VOLT(zero, one, two) std::min(zero+voltAdd, 1050), std::min(one+voltAdd, 1025), std::min(two+voltAdd, 1000), - - if (C.marikoEmcMaxClock < 1862400) { + + int32_t voltAdd = 25 * C.EmcDvbShift; + +#define DVB_VOLT(zero, one, two) std::min(zero + voltAdd, 1050), std::min(one + voltAdd, 1025), std::min(two + voltAdd, 1000), + + if (C.marikoEmcMaxClock < 1862400) + { std::memcpy(new_start, default_end, sizeof(emc_dvb_dvfs_table_t)); - } else if (C.marikoEmcMaxClock < 2131200){ - emc_dvb_dvfs_table_t oc_table = { 1862400, { 700, 675, 650, } }; + } + else if (C.marikoEmcMaxClock < 2131200) + { + emc_dvb_dvfs_table_t oc_table = {1862400, { + 700, + 675, + 650, + }}; std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); - } else if (C.marikoEmcMaxClock < 2400000){ - emc_dvb_dvfs_table_t oc_table = { 2131200, { 725, 700, 675, } }; + } + else if (C.marikoEmcMaxClock < 2400000) + { + emc_dvb_dvfs_table_t oc_table = {2131200, { + 725, + 700, + 675, + }}; std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); - } else if (C.marikoEmcMaxClock < 2665600){ - emc_dvb_dvfs_table_t oc_table = { 2400000, { DVB_VOLT(750, 725, 700) } }; + } + else if (C.marikoEmcMaxClock < 2665600) + { + emc_dvb_dvfs_table_t oc_table = {2400000, {DVB_VOLT(750, 725, 700)}}; std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); - } else if (C.marikoEmcMaxClock < 2931200){ - emc_dvb_dvfs_table_t oc_table = { 2665600, { DVB_VOLT(775, 750, 725) } }; + } + else if (C.marikoEmcMaxClock < 2931200) + { + emc_dvb_dvfs_table_t oc_table = {2665600, {DVB_VOLT(775, 750, 725)}}; std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); - } else if (C.marikoEmcMaxClock < 3200000){ - emc_dvb_dvfs_table_t oc_table = { 2931200, { DVB_VOLT(800, 775, 750) } }; + } + else if (C.marikoEmcMaxClock < 3200000) + { + emc_dvb_dvfs_table_t oc_table = {2931200, {DVB_VOLT(800, 775, 750)}}; std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); - } else { - emc_dvb_dvfs_table_t oc_table = { 3200000, { DVB_VOLT(800, 800, 775) } }; + } + else + { + emc_dvb_dvfs_table_t oc_table = {3200000, {DVB_VOLT(800, 800, 775)}}; std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); } new_start->freq = C.marikoEmcMaxClock; /* Max dvfs entry is 32, but HOS doesn't seem to boot if exact freq doesn't exist in dvb table, - reason why it's like this - */ - + reason why it's like this + */ + R_SUCCEED(); } diff --git a/Source/Configurator/src/settings.py b/Source/Configurator/src/settings.py index d97d7984..531e732b 100644 --- a/Source/Configurator/src/settings.py +++ b/Source/Configurator/src/settings.py @@ -6,7 +6,7 @@ kip_download_link="https://github.com/souldbminersmwc/Horizon-OC/releases/latest hoc_clk_download_link="https://github.com/souldbminersmwc/Horizon-OC/releases/latest/download/hoc-clk.zip" nx_ovlloader_link = "https://github.com/ppkantorski/nx-ovlloader/releases/latest/download/nx-ovlloader+.zip" ultrahand_link = "https://github.com/ppkantorski/Ultrahand-Overlay/releases/latest/download/ovlmenu.ovl" -status_monitor_link = "https://github.com/ppkantorski/Status-Monitor-Overlay/releases/latest/download/ Status-Monitor-Overlay.ovl " +status_monitor_link = "https://github.com/ppkantorski/Status-Monitor-Overlay/releases/latest/download/Status-Monitor-Overlay.ovl " saltynx_link = "https://github.com/masagrator/SaltyNX/releases/latest/download/SaltyNX.zip" reversenx_link = "https://github.com/masagrator/ReverseNX-RT/releases/latest/download/ReverseNX-RT-ovl.ovl" diff --git a/Source/Configurator/util/run.bat b/Source/Configurator/util/run.bat index da02eb93..7ed6b82c 100644 --- a/Source/Configurator/util/run.bat +++ b/Source/Configurator/util/run.bat @@ -1,2 +1,2 @@ -python3 eostimingutil.py +python eostimingutil.py diff --git a/Source/OnDeviceConfig/Makefile b/Source/OnDeviceConfig/Makefile new file mode 100644 index 00000000..40faa2c8 --- /dev/null +++ b/Source/OnDeviceConfig/Makefile @@ -0,0 +1,186 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# Project Info +#--------------------------------------------------------------------------------- +TARGET := hoc-configurator +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include +#ROMFS := romfs + +APP_TITLE := HOC Configurator +APP_AUTHOR := Dominatorul +APP_VERSION := 2.0.0 +ICON := icon.jpg + +#--------------------------------------------------------------------------------- +# Compilation Settings +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lnx + +#--------------------------------------------------------------------------------- +# Library Paths +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + +#--------------------------------------------------------------------------------- +# Automatic Build Rules +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +ifeq ($(strip $(CPPFILES)),) + export LD := $(CC) +else + export LD := $(CXX) +endif + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(CONFIG_JSON)),) + jsons := $(wildcard *.json) + ifneq (,$(findstring $(TARGET).json,$(jsons))) + export APP_JSON := $(TOPDIR)/$(TARGET).json + else + ifneq (,$(findstring config.json,$(jsons))) + export APP_JSON := $(TOPDIR)/config.json + endif + endif +else + export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) +endif + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all rebuild + +#--------------------------------------------------------------------------------- +# Automatically clean before build +#--------------------------------------------------------------------------------- +all: clean $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo "Cleaning..." +ifeq ($(strip $(APP_JSON)),) + @rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf +else + @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf +endif + +#--------------------------------------------------------------------------------- +# Optional: Full rebuild command +#--------------------------------------------------------------------------------- +rebuild: clean all + @echo "Rebuilt from scratch." + +#--------------------------------------------------------------------------------- +else +.PHONY: all +DEPENDS := $(OFILES:.o=.d) + +ifeq ($(strip $(APP_JSON)),) + +all : $(OUTPUT).nro + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp +else +$(OUTPUT).nro : $(OUTPUT).elf +endif + +else + +all : $(OUTPUT).nsp + +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm + +$(OUTPUT).nso : $(OUTPUT).elf + +endif + +$(OUTPUT).elf : $(OFILES) +$(OFILES_SRC) : $(HFILES_BIN) + +%.bin.o %_bin.h : %.bin + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) +endif +#--------------------------------------------------------------------------------- diff --git a/Source/OnDeviceConfig/hoc-configurator.elf b/Source/OnDeviceConfig/hoc-configurator.elf new file mode 100644 index 00000000..e3024534 Binary files /dev/null and b/Source/OnDeviceConfig/hoc-configurator.elf differ diff --git a/Source/OnDeviceConfig/hoc-configurator.nacp b/Source/OnDeviceConfig/hoc-configurator.nacp new file mode 100644 index 00000000..35bbf967 Binary files /dev/null and b/Source/OnDeviceConfig/hoc-configurator.nacp differ diff --git a/Source/OnDeviceConfig/hoc-configurator.nro b/Source/OnDeviceConfig/hoc-configurator.nro new file mode 100644 index 00000000..1572f74b Binary files /dev/null and b/Source/OnDeviceConfig/hoc-configurator.nro differ diff --git a/Source/OnDeviceConfig/icon.jpg b/Source/OnDeviceConfig/icon.jpg new file mode 100644 index 00000000..36704fb1 Binary files /dev/null and b/Source/OnDeviceConfig/icon.jpg differ diff --git a/Source/OnDeviceConfig/include/config.hpp b/Source/OnDeviceConfig/include/config.hpp new file mode 100644 index 00000000..903ce858 --- /dev/null +++ b/Source/OnDeviceConfig/include/config.hpp @@ -0,0 +1,20 @@ +/* + * HOC Configurator - Configuration Handler + * Copyright (C) Dominatorul, Souldbminer + */ + +#pragma once +#include + +class Config { +public: + std::string kipPath; + bool autoSave; + + Config(); + + bool loadConfig(); + bool saveConfig(); + bool checkKipExists(); + bool checkAtmosphereExists(); +}; diff --git a/Source/OnDeviceConfig/include/constants.hpp b/Source/OnDeviceConfig/include/constants.hpp new file mode 100644 index 00000000..072699d8 --- /dev/null +++ b/Source/OnDeviceConfig/include/constants.hpp @@ -0,0 +1,143 @@ +/* + * HOC Configurator - Constants + * Copyright (C) Dominatorul, Souldbminer + */ + +#pragma once +#include +#include + +namespace Constants { + // Application info + constexpr const char* APP_VERSION = "2.0.0"; + constexpr const char* APP_NAME = "HOC Configurator"; + constexpr const char* APP_AUTHOR = "Dominatorul"; + + // Paths + constexpr const char* DEFAULT_KIP_PATH = "sdmc:/atmosphere/kips/loader.kip"; + constexpr const char* CONFIG_DIR = "sdmc:/config/hoc-configurator"; + constexpr const char* CONFIG_FILE = "sdmc:/config/hoc-configurator/config.ini"; + constexpr const char* ATMOSPHERE_PATH = "sdmc:/atmosphere"; + constexpr const char* SYSTEM_SETTINGS_INI = "sdmc:/atmosphere/config/system_settings.ini"; + + // Frequency arrays (in kHz) + constexpr uint32_t MARIKO_GPU_FREQS[] = { + 76800, 153600, 230400, 307200, 384000, 460800, 537600, 614400, + 691200, 768000, 844800, 921600, 998400, 1075200, 1152000, 1228800, + 1267200, 1305600, 1344000, 1382400, 1420800, 1459200, 1497600, 1536000 + }; + + constexpr uint32_t ERISTA_GPU_FREQS[] = { + 76800, 153600, 230400, 307200, 384000, 460800, 537600, 614400, + 691200, 768000, 844800, 921600, 998400, 1075200 + }; + + constexpr uint32_t CPU_FREQS[] = { + 1020000, 1122000, 1224000, 1326000, 1428000, 1581000, 1683000, + 1785000, 1887000, 1963500, 2091000, 2193000, 2295000, 2397000, + 2499000, 2601000, 2703000, 2805000, 2907000 + }; + + constexpr uint32_t RAM_FREQS[] = { + 0, 1600000, 1633000, 1666000, 1700000, 1733000, 1766000, 1800000, + 1833000, 1866000, 1900000, 1933000, 1966000, 2000000, 2033000, 2066000, + 2100000, 2133000, 2166000, 2200000, 2233000, 2266000, 2300000 + }; + + // Voltage ranges + constexpr uint32_t MARIKO_GPU_MIN_VOLT = 480; + constexpr uint32_t MARIKO_GPU_MAX_VOLT = 960; + constexpr uint32_t MARIKO_GPU_MAX_VMIN = 700; + + constexpr uint32_t ERISTA_GPU_MIN_VOLT = 700; + constexpr uint32_t ERISTA_GPU_MAX_VOLT = 1000; + constexpr uint32_t ERISTA_GPU_MAX_VMIN = 850; + + constexpr uint32_t MARIKO_CPU_MIN_VMIN = 700; + constexpr uint32_t MARIKO_CPU_MAX_VMIN = 750; + + constexpr uint32_t VOLTAGE_STEP = 5; + constexpr uint32_t GPU_OFFSET_MAX = 50; + + // Thresholds + constexpr uint32_t MARIKO_MEME_THRESHOLD = 1536000; + constexpr uint32_t MARIKO_DANGEROUS_GPU_THRESHOLD = 1382400; + constexpr uint32_t MARIKO_UNSAFE_GPU_THRESHOLD = 1152000; + + constexpr uint32_t ERISTA_DANGEROUS_GPU_THRESHOLD = 1151000; + constexpr uint32_t ERISTA_UNSAFE_GPU_THRESHOLD = 922000; + + // RAM Types + const std::string RAM_TYPES[] = { + "Samsung AA-MGCL/MGCR", + "SK Hynix NEI/NEE/x267", + "Micron WT:B", + "Micron AUT:B", + "Micron WT:F", + "Samsung AM-MGCJ", + "Micron WT:E", + "Samsung AB-MGCL", + "SK Hynix NME", + "Samsung HB-MGCH" + }; + + // Fan curve profiles + namespace FanProfiles { + constexpr const char* V1_ERISTA = "V1_Erista"; + constexpr const char* V2_MARIKO = "V2_Mariko"; + constexpr const char* LITE_MARIKO = "Lite_Mariko"; + constexpr const char* OLED_MARIKO = "OLED_Mariko"; + } + + // PSM (Battery) options + struct PSMOption { + const char* name; + uint32_t value; + }; + + constexpr PSMOption PSM_OPTIONS[] = { + {"1024mA", 0x400}, + {"1280mA", 0x500}, + {"1536mA", 0x600}, + {"1660mA (Lite Default)", 0x67C}, + {"1792mA", 0x700}, + {"2048mA (Default)", 0x800}, + {"2304mA (UNSAFE)", 0x900}, + {"2560mA (UNSAFE)", 0xA00}, + {"2816mA (DANGEROUS)", 0xB00}, + {"3072mA (DANGEROUS)", 0xC00} + }; + + // Memory timing presets + struct TimingPreset { + uint32_t tRCD; + uint32_t tRP; + uint32_t tRAS; + uint32_t tRRD; + uint32_t tRFC; + uint32_t tRTW; + uint32_t tWTR; + uint32_t tREFI; + }; + + // Default timing preset + constexpr TimingPreset TIMING_DEFAULT = {0, 0, 0, 0, 0, 0, 0, 0}; + + // Samsung AA-MGCL/MGCR presets + constexpr TimingPreset TIMING_AAMGCL_CONSERVATIVE = {4, 4, 5, 5, 5, 5, 7, 6}; + constexpr TimingPreset TIMING_AAMGCL_TIGHT = {4, 4, 8, 6, 5, 7, 8, 6}; + + // SK Hynix NEE presets + constexpr TimingPreset TIMING_NEE_CONSERVATIVE = {3, 3, 2, 2, 5, 5, 4, 6}; + constexpr TimingPreset TIMING_NEE_TIGHT = {4, 4, 4, 3, 7, 6, 5, 6}; + + // Micron WT:B presets + constexpr TimingPreset TIMING_WTB_CONSERVATIVE = {4, 4, 5, 5, 2, 6, 5, 6}; + constexpr TimingPreset TIMING_WTB_TIGHT = {6, 6, 7, 7, 2, 6, 5, 6}; + + // UI Constants + constexpr int MAX_VISIBLE_ITEMS = 20; + constexpr int MENU_START_Y = 7; + constexpr int SCREEN_WIDTH = 80; + constexpr int SCREEN_HEIGHT = 45; +} diff --git a/Source/OnDeviceConfig/include/defaults.hpp b/Source/OnDeviceConfig/include/defaults.hpp new file mode 100644 index 00000000..85a760a7 --- /dev/null +++ b/Source/OnDeviceConfig/include/defaults.hpp @@ -0,0 +1,94 @@ +/* + * HOC Configurator - Default Values + * Copyright (C) Dominatorul, Souldbminer + */ + +#pragma once +#include + +class KipHandler; + +class Defaults { +public: + template + static void initDefaults(T& data) { + data.custRev = 0; + data.mtcConf = 0; + data.commonCpuBoostClock = 1785000; + data.commonEmcMemVolt = 1175000; + data.eristaCpuMaxVolt = 1235; + data.eristaEmcMaxClock = 1862400; + data.marikoCpuMaxVolt = 1120; + data.marikoEmcMaxClock = 1996800; + data.marikoEmcVddqVolt = 600000; + data.marikoCpuUV = 0; + data.marikoGpuUV = 0; + data.eristaCpuUV = 0; + data.eristaGpuUV = 0; + data.enableMarikoGpuUnsafeFreqs = 0; + data.enableEristaGpuUnsafeFreqs = 0; + data.enableMarikoCpuUnsafeFreqs = 0; + data.enableEristaCpuUnsafeFreqs = 0; + data.commonGpuVoltOffset = 0; + data.marikoEmcDvbShift = 0; + + // Memory timings + data.t1_tRCD = 0; + data.t2_tRP = 0; + data.t3_tRAS = 0; + data.t4_tRRD = 0; + data.t5_tRFC = 0; + data.t6_tRTW = 0; + data.t7_tWTR = 0; + data.t8_tREFI = 0; + data.mem_burst_latency = 2; + + // Additional voltages + data.marikoCpuVmin = 0; + data.eristaGpuVmin = 0; + data.marikoGpuVmin = 0; + data.marikoGpuVmax = 0; + + // Initialize all GPU voltages to 600 (default safe value) + data.g_volt_76800 = 600; + data.g_volt_153600 = 600; + data.g_volt_230400 = 600; + data.g_volt_307200 = 600; + data.g_volt_384000 = 600; + data.g_volt_460800 = 600; + data.g_volt_537600 = 600; + data.g_volt_614400 = 600; + data.g_volt_691200 = 600; + data.g_volt_768000 = 600; + data.g_volt_844800 = 605; + data.g_volt_921600 = 635; + data.g_volt_998400 = 665; + data.g_volt_1075200 = 695; + data.g_volt_1152000 = 730; + data.g_volt_1228800 = 760; + data.g_volt_1267200 = 785; + data.g_volt_1305600 = 800; + data.g_volt_1344000 = 0; + data.g_volt_1382400 = 0; + data.g_volt_1420800 = 0; + data.g_volt_1459200 = 0; + data.g_volt_1497600 = 0; + data.g_volt_1536000 = 0; + + // Erista GPU voltages + data.g_volt_e_76800 = 700; + data.g_volt_e_153600 = 700; + data.g_volt_e_230400 = 700; + data.g_volt_e_307200 = 700; + data.g_volt_e_384000 = 700; + data.g_volt_e_460800 = 700; + data.g_volt_e_537600 = 700; + data.g_volt_e_614400 = 700; + data.g_volt_e_691200 = 700; + data.g_volt_e_768000 = 700; + data.g_volt_e_844800 = 710; + data.g_volt_e_921600 = 740; + data.g_volt_e_998400 = 770; + data.g_volt_e_1075200 = 800; + } +}; \ No newline at end of file diff --git a/Source/OnDeviceConfig/include/ini_header.hpp b/Source/OnDeviceConfig/include/ini_header.hpp new file mode 100644 index 00000000..bf757970 --- /dev/null +++ b/Source/OnDeviceConfig/include/ini_header.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include + +class IniHandler { +private: + std::string iniPath; + std::map> sections; + +public: + IniHandler(const std::string& path); + + bool load(); + bool save(); + + void setValue(const std::string& section, const std::string& key, const std::string& value); + std::string getValue(const std::string& section, const std::string& key, const std::string& defaultValue = ""); + void removeKey(const std::string& section, const std::string& key); + void removeSection(const std::string& section); + + bool sectionExists(const std::string& section); + bool keyExists(const std::string& section, const std::string& key); +}; \ No newline at end of file diff --git a/Source/OnDeviceConfig/include/kip_handler.hpp b/Source/OnDeviceConfig/include/kip_handler.hpp new file mode 100644 index 00000000..888b343d --- /dev/null +++ b/Source/OnDeviceConfig/include/kip_handler.hpp @@ -0,0 +1,123 @@ +/* + * HOC Configurator - KIP Handler + * Copyright (C) Dominatorul, Souldbminer + */ + +#pragma once +#include +#include +#include +#include "defaults.hpp" + +class KipHandler { +private: + std::string kipPath; + const uint8_t MAGIC[4] = {'C', 'U', 'S', 'T'}; + + struct KipData { + uint32_t custRev; + uint32_t mtcConf; + uint32_t commonCpuBoostClock; + uint32_t commonEmcMemVolt; + uint32_t eristaCpuMaxVolt; + uint32_t eristaEmcMaxClock; + uint32_t marikoCpuMaxVolt; + uint32_t marikoEmcMaxClock; + uint32_t marikoEmcVddqVolt; + uint32_t marikoCpuUV; + uint32_t marikoGpuUV; + uint32_t eristaCpuUV; + uint32_t eristaGpuUV; + uint32_t enableMarikoGpuUnsafeFreqs; + uint32_t enableEristaGpuUnsafeFreqs; + uint32_t enableMarikoCpuUnsafeFreqs; + uint32_t enableEristaCpuUnsafeFreqs; + uint32_t commonGpuVoltOffset; + uint32_t marikoEmcDvbShift; + + // Memory timings + uint32_t t1_tRCD; + uint32_t t2_tRP; + uint32_t t3_tRAS; + uint32_t t4_tRRD; + uint32_t t5_tRFC; + uint32_t t6_tRTW; + uint32_t t7_tWTR; + uint32_t t8_tREFI; + uint32_t mem_burst_latency; + + // Additional voltages + uint32_t marikoCpuVmin; + uint32_t eristaGpuVmin; + uint32_t marikoGpuVmin; + uint32_t marikoGpuVmax; + + // GPU voltages for each frequency (Mariko) + uint32_t g_volt_76800; + uint32_t g_volt_153600; + uint32_t g_volt_230400; + uint32_t g_volt_307200; + uint32_t g_volt_384000; + uint32_t g_volt_460800; + uint32_t g_volt_537600; + uint32_t g_volt_614400; + uint32_t g_volt_691200; + uint32_t g_volt_768000; + uint32_t g_volt_844800; + uint32_t g_volt_921600; + uint32_t g_volt_998400; + uint32_t g_volt_1075200; + uint32_t g_volt_1152000; + uint32_t g_volt_1228800; + uint32_t g_volt_1267200; + uint32_t g_volt_1305600; + uint32_t g_volt_1344000; + uint32_t g_volt_1382400; + uint32_t g_volt_1420800; + uint32_t g_volt_1459200; + uint32_t g_volt_1497600; + uint32_t g_volt_1536000; + + // GPU voltages for each frequency (Erista) + uint32_t g_volt_e_76800; + uint32_t g_volt_e_153600; + uint32_t g_volt_e_230400; + uint32_t g_volt_e_307200; + uint32_t g_volt_e_384000; + uint32_t g_volt_e_460800; + uint32_t g_volt_e_537600; + uint32_t g_volt_e_614400; + uint32_t g_volt_e_691200; + uint32_t g_volt_e_768000; + uint32_t g_volt_e_844800; + uint32_t g_volt_e_921600; + uint32_t g_volt_e_998400; + uint32_t g_volt_e_1075200; + }; + + KipData data; + +public: + KipHandler(const std::string& path) : kipPath(path) { + // Initialize with defaults + Defaults::initDefaults(data); + } + + bool readKip(); + bool writeKip(); + + // Getters + KipData& getData() { return data; } + const KipData& getData() const { return data; } + + // Setters for common values + void setCommonCpuBoostClock(uint32_t val) { data.commonCpuBoostClock = val; } + void setCommonEmcMemVolt(uint32_t val) { data.commonEmcMemVolt = val; } + void setMarikoCpuMaxVolt(uint32_t val) { data.marikoCpuMaxVolt = val; } + void setMarikoEmcMaxClock(uint32_t val) { data.marikoEmcMaxClock = val; } + void setMarikoEmcVddqVolt(uint32_t val) { data.marikoEmcVddqVolt = val; } + + // Utility + std::string getKipPath() const { return kipPath; } + void setKipPath(const std::string& path) { kipPath = path; } +}; diff --git a/Source/OnDeviceConfig/include/ui.hpp b/Source/OnDeviceConfig/include/ui.hpp new file mode 100644 index 00000000..8eb6beff --- /dev/null +++ b/Source/OnDeviceConfig/include/ui.hpp @@ -0,0 +1,91 @@ +/* + * HOC Configurator - UI Handler + * Copyright (C) Dominatorul, Souldbminer + */ + +#pragma once +#include +#include +#include +#include + +// Forward declarations +class KipHandler; +class ValueEditor; + +enum class EditorType { + TOGGLE, + FREQUENCY, + VOLTAGE, + SLIDER, + LIST +}; + +enum class MenuState { + MAIN, + GPU, + CPU, + RAM, + MISC, + ABOUT, + SETTINGS +}; + +class UI { +private: + MenuState currentState; + int selectedIndex; + int scrollOffset; + std::string statusMessage; + std::string kipPath; + bool kipLoaded; + bool autoSave; + KipHandler* kipHandler; + ValueEditor* editor; + + const int MAX_VISIBLE_ITEMS = 20; + + void renderMainMenu(); + void renderGPUMenu(); + void renderCPUMenu(); + void renderRAMMenu(); + void renderMiscMenu(); + void renderAboutMenu(); + void renderSettingsMenu(); + + void handleMainMenuInput(u64 kDown); + void handleGPUMenuInput(u64 kDown); + void handleCPUMenuInput(u64 kDown); + void handleRAMMenuInput(u64 kDown); + void handleMiscMenuInput(u64 kDown); + void handleAboutMenuInput(u64 kDown); + void handleSettingsMenuInput(u64 kDown); + + void drawHeader(); + void drawFooter(); + void drawMenuItem(const std::string& text, bool selected, int y); + void drawText(const std::string& text, int x, int y); + + void showValueEditor(const std::string& title, EditorType type, int currentValue, + std::function callback, + const std::vector& options = {}, + int min = 0, int max = 100, int step = 1); + +public: + UI(); + ~UI(); + + void render(); + void handleInput(u64 kDown); + + void setStatus(const std::string& msg) { statusMessage = msg; } + void setKipPath(const std::string& path) { kipPath = path; } + void setKipLoaded(bool loaded) { kipLoaded = loaded; } + void setAutoSave(bool enabled) { autoSave = enabled; } + void setKipHandler(KipHandler* handler) { kipHandler = handler; } + + std::string getStatus() const { return statusMessage; } + std::string getKipPath() const { return kipPath; } + bool isKipLoaded() const { return kipLoaded; } + bool isAutoSaveEnabled() const { return autoSave; } +}; \ No newline at end of file diff --git a/Source/OnDeviceConfig/include/value_editor.hpp b/Source/OnDeviceConfig/include/value_editor.hpp new file mode 100644 index 00000000..b37dc032 --- /dev/null +++ b/Source/OnDeviceConfig/include/value_editor.hpp @@ -0,0 +1,43 @@ +/* + * HOC Configurator - Value Editor + * Copyright (C) Dominatorul, Souldbminer + */ + +#pragma once +#include +#include +#include +#include +#include "ui.hpp" // This includes EditorType + +class KipHandler; + +struct EditorConfig { + std::string title; + EditorType type; + int currentValue; + int minValue; + int maxValue; + int step; + std::vector options; + std::function onValueChange; +}; + +class ValueEditor { +private: + EditorConfig config; + int selectedValue; + bool active; + +public: + ValueEditor(); + + void show(const EditorConfig& cfg); + void hide(); + bool isActive() const { return active; } + + void handleInput(u64 kDown); + void render(); + + int getSelectedValue() const { return selectedValue; } +}; \ No newline at end of file diff --git a/Source/OnDeviceConfig/source/config.cpp b/Source/OnDeviceConfig/source/config.cpp new file mode 100644 index 00000000..f8512370 --- /dev/null +++ b/Source/OnDeviceConfig/source/config.cpp @@ -0,0 +1,59 @@ +/* + * HOC Configurator - Configuration Implementation + * Copyright (C) Dominatorul, Souldbminer + */ + +#include "config.hpp" +#include +#include + +Config::Config() { + kipPath = "sdmc:/atmosphere/kips/loader.kip"; + autoSave = false; +} + +bool Config::loadConfig() { + std::ifstream file("sdmc:/config/hoc-configurator/config.ini"); + if (!file.is_open()) { + return false; + } + + std::string line; + while (std::getline(file, line)) { + if (line.find("kip_path=") == 0) { + kipPath = line.substr(9); + } else if (line.find("auto_save=") == 0) { + autoSave = (line.substr(10) == "1"); + } + } + + file.close(); + return true; +} + +bool Config::saveConfig() { + // Create directory if it doesn't exist + mkdir("sdmc:/config", 0777); + mkdir("sdmc:/config/hoc-configurator", 0777); + + std::ofstream file("sdmc:/config/hoc-configurator/config.ini"); + if (!file.is_open()) { + return false; + } + + file << "kip_path=" << kipPath << "\n"; + file << "auto_save=" << (autoSave ? "1" : "0") << "\n"; + + file.close(); + return true; +} + +bool Config::checkKipExists() { + struct stat buffer; + return (stat(kipPath.c_str(), &buffer) == 0); +} + +bool Config::checkAtmosphereExists() { + struct stat buffer; + return (stat("sdmc:/atmosphere", &buffer) == 0); +} diff --git a/Source/OnDeviceConfig/source/ini_header.cpp b/Source/OnDeviceConfig/source/ini_header.cpp new file mode 100644 index 00000000..bf757970 --- /dev/null +++ b/Source/OnDeviceConfig/source/ini_header.cpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include + +class IniHandler { +private: + std::string iniPath; + std::map> sections; + +public: + IniHandler(const std::string& path); + + bool load(); + bool save(); + + void setValue(const std::string& section, const std::string& key, const std::string& value); + std::string getValue(const std::string& section, const std::string& key, const std::string& defaultValue = ""); + void removeKey(const std::string& section, const std::string& key); + void removeSection(const std::string& section); + + bool sectionExists(const std::string& section); + bool keyExists(const std::string& section, const std::string& key); +}; \ No newline at end of file diff --git a/Source/OnDeviceConfig/source/kip_handler.cpp b/Source/OnDeviceConfig/source/kip_handler.cpp new file mode 100644 index 00000000..cda730f6 --- /dev/null +++ b/Source/OnDeviceConfig/source/kip_handler.cpp @@ -0,0 +1,254 @@ +/* + * HOC Configurator - KIP Handler Implementation + * Copyright (C) Dominatorul, Souldbminer + */ + +#include "kip_handler.hpp" +#include +#include +#include + +bool KipHandler::readKip() { + std::ifstream file(kipPath, std::ios::binary); + if (!file.is_open()) { + return false; + } + + // Read entire file + file.seekg(0, std::ios::end); + size_t fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector buffer(fileSize); + file.read(reinterpret_cast(buffer.data()), fileSize); + file.close(); + + // Find CUST magic + size_t magicPos = 0; + bool found = false; + for (size_t i = 0; i < fileSize - 4; i++) { + if (memcmp(&buffer[i], MAGIC, 4) == 0) { + magicPos = i + 4; + found = true; + break; + } + } + + if (!found) { + return false; + } + + // Read structure (assuming packed uint32_t structure) + size_t offset = magicPos; + auto readU32 = [&]() -> uint32_t { + uint32_t val; + memcpy(&val, &buffer[offset], sizeof(uint32_t)); + offset += sizeof(uint32_t); + return val; + }; + + data.custRev = readU32(); + data.mtcConf = readU32(); + data.commonCpuBoostClock = readU32(); + data.commonEmcMemVolt = readU32(); + data.eristaCpuMaxVolt = readU32(); + data.eristaEmcMaxClock = readU32(); + data.marikoCpuMaxVolt = readU32(); + data.marikoEmcMaxClock = readU32(); + data.marikoEmcVddqVolt = readU32(); + data.marikoCpuUV = readU32(); + data.marikoGpuUV = readU32(); + data.eristaCpuUV = readU32(); + data.eristaGpuUV = readU32(); + data.enableMarikoGpuUnsafeFreqs = readU32(); + data.enableEristaGpuUnsafeFreqs = readU32(); + data.enableMarikoCpuUnsafeFreqs = readU32(); + data.enableEristaCpuUnsafeFreqs = readU32(); + data.commonGpuVoltOffset = readU32(); + data.marikoEmcDvbShift = readU32(); + + // Memory timings + data.t1_tRCD = readU32(); + data.t2_tRP = readU32(); + data.t3_tRAS = readU32(); + data.t4_tRRD = readU32(); + data.t5_tRFC = readU32(); + data.t6_tRTW = readU32(); + data.t7_tWTR = readU32(); + data.t8_tREFI = readU32(); + data.mem_burst_latency = readU32(); + + // Additional voltages + data.marikoCpuVmin = readU32(); + data.eristaGpuVmin = readU32(); + data.marikoGpuVmin = readU32(); + data.marikoGpuVmax = readU32(); + + // GPU voltages Mariko + data.g_volt_76800 = readU32(); + data.g_volt_153600 = readU32(); + data.g_volt_230400 = readU32(); + data.g_volt_307200 = readU32(); + data.g_volt_384000 = readU32(); + data.g_volt_460800 = readU32(); + data.g_volt_537600 = readU32(); + data.g_volt_614400 = readU32(); + data.g_volt_691200 = readU32(); + data.g_volt_768000 = readU32(); + data.g_volt_844800 = readU32(); + data.g_volt_921600 = readU32(); + data.g_volt_998400 = readU32(); + data.g_volt_1075200 = readU32(); + data.g_volt_1152000 = readU32(); + data.g_volt_1228800 = readU32(); + data.g_volt_1267200 = readU32(); + data.g_volt_1305600 = readU32(); + data.g_volt_1344000 = readU32(); + data.g_volt_1382400 = readU32(); + data.g_volt_1420800 = readU32(); + data.g_volt_1459200 = readU32(); + data.g_volt_1497600 = readU32(); + data.g_volt_1536000 = readU32(); + + // GPU voltages Erista + data.g_volt_e_76800 = readU32(); + data.g_volt_e_153600 = readU32(); + data.g_volt_e_230400 = readU32(); + data.g_volt_e_307200 = readU32(); + data.g_volt_e_384000 = readU32(); + data.g_volt_e_460800 = readU32(); + data.g_volt_e_537600 = readU32(); + data.g_volt_e_614400 = readU32(); + data.g_volt_e_691200 = readU32(); + data.g_volt_e_768000 = readU32(); + data.g_volt_e_844800 = readU32(); + data.g_volt_e_921600 = readU32(); + data.g_volt_e_998400 = readU32(); + data.g_volt_e_1075200 = readU32(); + + return true; +} + +bool KipHandler::writeKip() { + std::fstream file(kipPath, std::ios::in | std::ios::out | std::ios::binary); + if (!file.is_open()) { + return false; + } + + // Read entire file + file.seekg(0, std::ios::end); + size_t fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector buffer(fileSize); + file.read(reinterpret_cast(buffer.data()), fileSize); + + // Find CUST magic + size_t magicPos = 0; + bool found = false; + for (size_t i = 0; i < fileSize - 4; i++) { + if (memcmp(&buffer[i], MAGIC, 4) == 0) { + magicPos = i + 4; + found = true; + break; + } + } + + if (!found) { + file.close(); + return false; + } + + // Write structure + size_t offset = magicPos; + auto writeU32 = [&](uint32_t val) { + memcpy(&buffer[offset], &val, sizeof(uint32_t)); + offset += sizeof(uint32_t); + }; + + writeU32(data.custRev); + writeU32(data.mtcConf); + writeU32(data.commonCpuBoostClock); + writeU32(data.commonEmcMemVolt); + writeU32(data.eristaCpuMaxVolt); + writeU32(data.eristaEmcMaxClock); + writeU32(data.marikoCpuMaxVolt); + writeU32(data.marikoEmcMaxClock); + writeU32(data.marikoEmcVddqVolt); + writeU32(data.marikoCpuUV); + writeU32(data.marikoGpuUV); + writeU32(data.eristaCpuUV); + writeU32(data.eristaGpuUV); + writeU32(data.enableMarikoGpuUnsafeFreqs); + writeU32(data.enableEristaGpuUnsafeFreqs); + writeU32(data.enableMarikoCpuUnsafeFreqs); + writeU32(data.enableEristaCpuUnsafeFreqs); + writeU32(data.commonGpuVoltOffset); + writeU32(data.marikoEmcDvbShift); + + // Memory timings + writeU32(data.t1_tRCD); + writeU32(data.t2_tRP); + writeU32(data.t3_tRAS); + writeU32(data.t4_tRRD); + writeU32(data.t5_tRFC); + writeU32(data.t6_tRTW); + writeU32(data.t7_tWTR); + writeU32(data.t8_tREFI); + writeU32(data.mem_burst_latency); + + // Additional voltages + writeU32(data.marikoCpuVmin); + writeU32(data.eristaGpuVmin); + writeU32(data.marikoGpuVmin); + writeU32(data.marikoGpuVmax); + + // GPU voltages Mariko + writeU32(data.g_volt_76800); + writeU32(data.g_volt_153600); + writeU32(data.g_volt_230400); + writeU32(data.g_volt_307200); + writeU32(data.g_volt_384000); + writeU32(data.g_volt_460800); + writeU32(data.g_volt_537600); + writeU32(data.g_volt_614400); + writeU32(data.g_volt_691200); + writeU32(data.g_volt_768000); + writeU32(data.g_volt_844800); + writeU32(data.g_volt_921600); + writeU32(data.g_volt_998400); + writeU32(data.g_volt_1075200); + writeU32(data.g_volt_1152000); + writeU32(data.g_volt_1228800); + writeU32(data.g_volt_1267200); + writeU32(data.g_volt_1305600); + writeU32(data.g_volt_1344000); + writeU32(data.g_volt_1382400); + writeU32(data.g_volt_1420800); + writeU32(data.g_volt_1459200); + writeU32(data.g_volt_1497600); + writeU32(data.g_volt_1536000); + + // GPU voltages Erista + writeU32(data.g_volt_e_76800); + writeU32(data.g_volt_e_153600); + writeU32(data.g_volt_e_230400); + writeU32(data.g_volt_e_307200); + writeU32(data.g_volt_e_384000); + writeU32(data.g_volt_e_460800); + writeU32(data.g_volt_e_537600); + writeU32(data.g_volt_e_614400); + writeU32(data.g_volt_e_691200); + writeU32(data.g_volt_e_768000); + writeU32(data.g_volt_e_844800); + writeU32(data.g_volt_e_921600); + writeU32(data.g_volt_e_998400); + writeU32(data.g_volt_e_1075200); + + // Write back to file + file.seekp(0, std::ios::beg); + file.write(reinterpret_cast(buffer.data()), fileSize); + file.close(); + + return true; +} \ No newline at end of file diff --git a/Source/OnDeviceConfig/source/main.cpp b/Source/OnDeviceConfig/source/main.cpp new file mode 100644 index 00000000..7f1406ba --- /dev/null +++ b/Source/OnDeviceConfig/source/main.cpp @@ -0,0 +1,108 @@ +/* + * HOC Configurator - Nintendo Switch Homebrew + * Copyright (C) Dominatorul, Souldbminer + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "kip_handler.hpp" +#include "ui.hpp" +#include "config.hpp" +#include "defaults.hpp" + +int main(int argc, char* argv[]) { + // Initialize services + socketInitializeDefault(); + nxlinkStdio(); + + consoleInit(NULL); + + // Configure input + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + PadState pad; + padInitializeDefault(&pad); + + // Initialize configuration + Config config; + config.loadConfig(); + + // Initialize KIP handler + KipHandler* kipHandler = new KipHandler(config.kipPath); + + // Initialize UI + UI ui; + ui.setKipPath(config.kipPath); + ui.setKipHandler(kipHandler); + ui.setAutoSave(config.autoSave); + + // Check if KIP exists and load it + if (config.checkKipExists()) { + if (kipHandler->readKip()) { + ui.setStatus("KIP loaded successfully from " + config.kipPath); + ui.setKipLoaded(true); + } else { + ui.setStatus("ERROR: Failed to parse KIP file!"); + ui.setKipLoaded(false); + } + } else if (config.checkAtmosphereExists()) { + ui.setStatus("Atmosphere found, but KIP not found at: " + config.kipPath); + ui.setKipLoaded(false); + } else { + ui.setStatus("ERROR: Atmosphere not detected! Is your SD card mounted?"); + ui.setKipLoaded(false); + } + + bool running = true; + u64 kDownOld = 0; + int frameCounter = 0; + const int FRAME_DELAY = 3; // Add input delay for better responsiveness + + while (running && appletMainLoop()) { + padUpdate(&pad); + u64 kDown = padGetButtonsDown(&pad); + + // Exit on Plus button + if (kDown & HidNpadButton_Plus) { + running = false; + break; + } + + // Process input with debouncing and frame delay + if (kDown && kDown != kDownOld && frameCounter >= FRAME_DELAY) { + ui.handleInput(kDown); + frameCounter = 0; + } + + // Render UI + ui.render(); + consoleUpdate(NULL); + + kDownOld = kDown; + frameCounter++; + + // Frame limiter - 30 FPS + svcSleepThread(33333333); // ~33ms + } + + // Save config before exit + config.autoSave = ui.isAutoSaveEnabled(); + config.kipPath = ui.getKipPath(); + config.saveConfig(); + + // Cleanup + delete kipHandler; + + consoleExit(NULL); + socketExit(); + + return 0; +} \ No newline at end of file diff --git a/Source/OnDeviceConfig/source/ui.cpp b/Source/OnDeviceConfig/source/ui.cpp new file mode 100644 index 00000000..4efd7255 --- /dev/null +++ b/Source/OnDeviceConfig/source/ui.cpp @@ -0,0 +1,552 @@ +/* + * HOC Configurator - Complete UI Implementation + * Copyright (C) Dominatorul, Souldbminer + */ + +#include "ui.hpp" +#include "kip_handler.hpp" +#include "value_editor.hpp" +#include "constants.hpp" +#include +#include +#include + +UI::UI() : currentState(MenuState::MAIN), selectedIndex(0), scrollOffset(0), + kipLoaded(false), autoSave(false), kipHandler(nullptr), editor(nullptr) { + statusMessage = "Welcome to HOC Configurator"; + editor = new ValueEditor(); +} + +UI::~UI() { + if (editor) delete editor; +} + +void UI::drawHeader() { + printf("\x1b[2J\x1b[1;1H"); + printf("\x1b[47;30m"); + printf("================================================================================\n"); + printf(" HOC Configurator v%s | Made by Dominatorul \n", Constants::APP_VERSION); + printf("================================================================================\n"); + printf("\x1b[0m"); +} + +void UI::drawFooter() { + printf("\x1b[42;1H"); + printf("\x1b[47;30m"); + printf("================================================================================\n"); + std::string truncStatus = statusMessage; + if (truncStatus.length() > 50) { + truncStatus = truncStatus.substr(0, 47) + "..."; + } + printf(" [A] Select [B] Back [+] Exit | %s\n", truncStatus.c_str()); + printf("================================================================================\n"); + printf("\x1b[0m"); +} + +void UI::drawMenuItem(const std::string& text, bool selected, int y) { + printf("\x1b[%d;1H", y); + std::string displayText = text; + if (displayText.length() > 74) { + displayText = displayText.substr(0, 71) + "..."; + } + if (selected) { + printf("\x1b[44;37m > %-76s\x1b[0m\n", displayText.c_str()); + } else { + printf(" %-76s\n", displayText.c_str()); + } +} + +void UI::drawText(const std::string& text, int x, int y) { + printf("\x1b[%d;%dH%s", y, x, text.c_str()); +} + +void UI::showValueEditor(const std::string& title, EditorType type, int currentValue, + std::function callback, const std::vector& options, + int min, int max, int step) { + if (!editor) return; + EditorConfig cfg; + cfg.title = title; + cfg.type = type; + cfg.currentValue = currentValue; + cfg.minValue = min; + cfg.maxValue = max; + cfg.step = step; + cfg.options = options; + cfg.onValueChange = callback; + editor->show(cfg); +} + +void UI::renderMainMenu() { + drawHeader(); + printf("\x1b[5;1H"); + printf("+- Main Menu -------------------------------------------------------------------+\n"); + std::vector menuItems = { + "GPU Settings", "CPU Settings", "RAM Settings", + "Misc Settings", "Settings", "About" + }; + int startY = 7; + for (size_t i = 0; i < menuItems.size(); i++) { + drawMenuItem(menuItems[i], (int)i == selectedIndex, startY + (int)i); + } + printf("\x1b[%d;1H", startY + (int)menuItems.size() + 1); + printf("+-------------------------------------------------------------------------------+\n"); + printf("\n"); + printf(" KIP Path: %s\n", kipPath.c_str()); + printf(" KIP Status: %s\n", kipLoaded ? "\x1b[32mLoaded\x1b[0m" : "\x1b[31mNot Loaded\x1b[0m"); + printf(" Auto-save: %s\n", autoSave ? "\x1b[32mEnabled\x1b[0m" : "\x1b[33mDisabled\x1b[0m"); + drawFooter(); +} + +void UI::renderGPUMenu() { + drawHeader(); + printf("\x1b[5;1H"); + printf("+- GPU Settings ----------------------------------------------------------------+\n"); + + if (!kipHandler || !kipLoaded) { + printf("\n \x1b[31mNo KIP loaded! Go to Settings to load a KIP file.\x1b[0m\n\n"); + printf("+-------------------------------------------------------------------------------+\n"); + drawFooter(); + return; + } + + auto& data = kipHandler->getData(); + std::vector menuItems = { + "Enable Unsafe Frequencies (Mariko): " + std::string(data.enableMarikoGpuUnsafeFreqs ? "ON" : "OFF"), + "Enable Unsafe Frequencies (Erista): " + std::string(data.enableEristaGpuUnsafeFreqs ? "ON" : "OFF"), + "Mariko GPU vMin: " + (data.marikoGpuVmin == 0 ? "Disabled" : std::to_string(data.marikoGpuVmin) + "mV"), + "Mariko GPU vMax: " + (data.marikoGpuVmax == 0 ? "Disabled" : std::to_string(data.marikoGpuVmax) + "mV"), + "Erista GPU vMin: " + (data.eristaGpuVmin == 0 ? "Disabled" : std::to_string(data.eristaGpuVmin) + "mV"), + "Mariko Undervolt: UV" + std::to_string(data.marikoGpuUV), + "Erista Undervolt: UV" + std::to_string(data.eristaGpuUV), + "GPU Volt Offset: " + (data.commonGpuVoltOffset == 0 ? "Disabled" : "-" + std::to_string(data.commonGpuVoltOffset) + "mV") + }; + + int startY = 7; + for (size_t i = 0; i < menuItems.size(); i++) { + drawMenuItem(menuItems[i], (int)i == selectedIndex, startY + (int)i); + } + printf("\x1b[%d;1H", startY + (int)menuItems.size() + 1); + printf("+-------------------------------------------------------------------------------+\n"); + drawFooter(); +} + +void UI::renderCPUMenu() { + drawHeader(); + printf("\x1b[5;1H"); + printf("+- CPU Settings ----------------------------------------------------------------+\n"); + + if (!kipHandler || !kipLoaded) { + printf("\n \x1b[31mNo KIP loaded! Go to Settings to load a KIP file.\x1b[0m\n\n"); + printf("+-------------------------------------------------------------------------------+\n"); + drawFooter(); + return; + } + + auto& data = kipHandler->getData(); + std::vector menuItems = { + "Enable Unsafe Frequencies (Mariko): " + std::string(data.enableMarikoCpuUnsafeFreqs ? "ON" : "OFF"), + "Enable Unsafe Frequencies (Erista): " + std::string(data.enableEristaCpuUnsafeFreqs ? "ON" : "OFF"), + "CPU Boost Frequency: " + std::to_string(data.commonCpuBoostClock / 1000) + " MHz", + "Mariko CPU vMin: " + (data.marikoCpuVmin == 0 ? "Default" : std::to_string(data.marikoCpuVmin) + "mV"), + "Mariko CPU vMax: " + (data.marikoCpuMaxVolt == 0 ? "Disabled" : std::to_string(data.marikoCpuMaxVolt) + "mV"), + "Erista CPU vMax: " + (data.eristaCpuMaxVolt == 0 ? "Disabled" : std::to_string(data.eristaCpuMaxVolt) + "mV"), + "Mariko CPU Undervolt: UV" + std::to_string(data.marikoCpuUV), + "Erista CPU Undervolt: UV" + std::to_string(data.eristaCpuUV) + }; + + int startY = 7; + for (size_t i = 0; i < menuItems.size(); i++) { + drawMenuItem(menuItems[i], (int)i == selectedIndex, startY + (int)i); + } + printf("\x1b[%d;1H", startY + (int)menuItems.size() + 1); + printf("+-------------------------------------------------------------------------------+\n"); + drawFooter(); +} + +void UI::renderRAMMenu() { + drawHeader(); + printf("\x1b[5;1H"); + printf("+- RAM Settings ----------------------------------------------------------------+\n"); + + if (!kipHandler || !kipLoaded) { + printf("\n \x1b[31mNo KIP loaded! Go to Settings to load a KIP file.\x1b[0m\n\n"); + printf("+-------------------------------------------------------------------------------+\n"); + drawFooter(); + return; + } + + auto& data = kipHandler->getData(); + std::vector menuItems = { + "RAM Max Frequency (Mariko): " + std::to_string(data.marikoEmcMaxClock / 1000) + " MHz", + "RAM Max Frequency (Erista): " + std::to_string(data.eristaEmcMaxClock / 1000) + " MHz", + "RAM Primary Voltage (VDD2): " + std::to_string(data.commonEmcMemVolt / 1000) + " mV", + "RAM Secondary Voltage (VDDQ): " + std::to_string(data.marikoEmcVddqVolt / 1000) + " mV", + "SoC DVB Shift: " + std::to_string(data.marikoEmcDvbShift), + "Base Latency: " + std::to_string(data.mem_burst_latency), + "t1 tRCD: " + std::to_string(data.t1_tRCD), + "t2 tRP: " + std::to_string(data.t2_tRP), + "t3 tRAS: " + std::to_string(data.t3_tRAS), + "t4 tRRD: " + std::to_string(data.t4_tRRD), + "t5 tRFC: " + std::to_string(data.t5_tRFC) + }; + + int startY = 7; + int visibleStart = scrollOffset; + int visibleEnd = std::min((int)menuItems.size(), scrollOffset + MAX_VISIBLE_ITEMS); + + for (int i = visibleStart; i < visibleEnd; i++) { + drawMenuItem(menuItems[i], i == selectedIndex, startY + (i - visibleStart)); + } + + printf("\x1b[%d;1H", startY + (visibleEnd - visibleStart) + 1); + printf("+-------------------------------------------------------------------------------+\n"); + drawFooter(); +} + +void UI::renderMiscMenu() { + drawHeader(); + printf("\x1b[5;1H"); + printf("+- Misc Settings ---------------------------------------------------------------+\n"); + std::vector menuItems = { + "Optimize Fan Curve (V1 Erista)", + "Optimize Fan Curve (V2 Mariko)", + "Optimize Fan Curve (Lite)", + "Optimize Fan Curve (OLED)", + "Reset Fan Curve to Default", + "Sleep Mode Battery Fix: Toggle", + "Battery Charge Limit \x1b[31m(DANGEROUS)\x1b[0m" + }; + + int startY = 7; + for (size_t i = 0; i < menuItems.size(); i++) { + drawMenuItem(menuItems[i], (int)i == selectedIndex, startY + (int)i); + } + printf("\x1b[%d;1H", startY + (int)menuItems.size() + 1); + printf("+-------------------------------------------------------------------------------+\n"); + drawFooter(); +} + +void UI::renderAboutMenu() { + drawHeader(); + printf("\x1b[5;1H"); + printf("+- About -----------------------------------------------------------------------+\n"); + printf("| |\n"); + printf("| Horizon OC Configurator v%-50s |\n", Constants::APP_VERSION); + printf("| Nintendo Switch Homebrew Edition |\n"); + printf("| |\n"); + printf("| Contributors: |\n"); + printf("| * Dominatorul - Homebrew port and development |\n"); + printf("| * Souldbminer - Original PC configurator |\n"); + printf("| * Lightos - L4T timings research |\n"); + printf("| * Lightos, Samybigio, Flopsider - Testing |\n"); + printf("| |\n"); + printf("| License: GNU General Public License v2.0 or later |\n"); + printf("| Source: github.com/souldbminersmwc/Horizon-OC |\n"); + printf("| |\n"); + printf("| \x1b[33mWARNING: Improper overclocking can damage your console!\x1b[0m |\n"); + printf("| Use at your own risk. Always test stability before daily use. |\n"); + printf("| |\n"); + printf("+------------------------------------------------------------------------------+\n"); + drawFooter(); +} + +void UI::renderSettingsMenu() { + drawHeader(); + printf("\x1b[5;1H"); + printf("+- Settings --------------------------------------------------------------------+\n"); + std::vector menuItems = { + "Toggle Auto-save: " + std::string(autoSave ? "\x1b[32mON\x1b[0m" : "\x1b[33mOFF\x1b[0m"), + "Save KIP Now", + "Reload KIP", + "Back to Main Menu" + }; + + int startY = 7; + for (size_t i = 0; i < menuItems.size(); i++) { + drawMenuItem(menuItems[i], (int)i == selectedIndex, startY + (int)i); + } + printf("\x1b[%d;1H", startY + (int)menuItems.size() + 1); + printf("+-------------------------------------------------------------------------------+\n"); + printf("\n"); + printf(" Current KIP Path: %s\n", kipPath.c_str()); + printf(" Auto-save Status: %s\n", autoSave ? "\x1b[32mEnabled\x1b[0m" : "\x1b[33mDisabled\x1b[0m"); + drawFooter(); +} + +void UI::render() { + if (editor && editor->isActive()) { + editor->render(); + return; + } + + switch (currentState) { + case MenuState::MAIN: renderMainMenu(); break; + case MenuState::GPU: renderGPUMenu(); break; + case MenuState::CPU: renderCPUMenu(); break; + case MenuState::RAM: renderRAMMenu(); break; + case MenuState::MISC: renderMiscMenu(); break; + case MenuState::ABOUT: renderAboutMenu(); break; + case MenuState::SETTINGS: renderSettingsMenu(); break; + } +} + +void UI::handleInput(u64 kDown) { + if (editor && editor->isActive()) { + editor->handleInput(kDown); + return; + } + + switch (currentState) { + case MenuState::MAIN: handleMainMenuInput(kDown); break; + case MenuState::GPU: handleGPUMenuInput(kDown); break; + case MenuState::CPU: handleCPUMenuInput(kDown); break; + case MenuState::RAM: handleRAMMenuInput(kDown); break; + case MenuState::MISC: handleMiscMenuInput(kDown); break; + case MenuState::ABOUT: handleAboutMenuInput(kDown); break; + case MenuState::SETTINGS: handleSettingsMenuInput(kDown); break; + } +} + +void UI::handleMainMenuInput(u64 kDown) { + if (kDown & HidNpadButton_Down) selectedIndex = (selectedIndex + 1) % 6; + if (kDown & HidNpadButton_Up) selectedIndex = (selectedIndex - 1 + 6) % 6; + + if (kDown & HidNpadButton_A) { + switch (selectedIndex) { + case 0: currentState = MenuState::GPU; break; + case 1: currentState = MenuState::CPU; break; + case 2: currentState = MenuState::RAM; break; + case 3: currentState = MenuState::MISC; break; + case 4: currentState = MenuState::SETTINGS; break; + case 5: currentState = MenuState::ABOUT; break; + } + selectedIndex = 0; + scrollOffset = 0; + } +} + +void UI::handleGPUMenuInput(u64 kDown) { + if (!kipHandler || !kipLoaded) { + if (kDown & HidNpadButton_B) { + currentState = MenuState::MAIN; + selectedIndex = 0; + } + return; + } + + int maxItems = 8; + if (kDown & HidNpadButton_Down) selectedIndex = (selectedIndex + 1) % maxItems; + if (kDown & HidNpadButton_Up) selectedIndex = (selectedIndex - 1 + maxItems) % maxItems; + if (kDown & HidNpadButton_B) { + currentState = MenuState::MAIN; + selectedIndex = 0; + } + + if (kDown & HidNpadButton_A) { + auto& data = kipHandler->getData(); + + switch (selectedIndex) { + case 0: { // Mariko GPU Unsafe Freqs + std::vector opts = {"Disabled (0)", "Enabled (1)"}; + showValueEditor("Enable Unsafe GPU Frequencies (Mariko)", EditorType::LIST, + data.enableMarikoGpuUnsafeFreqs, + [this, &data](int val) { + data.enableMarikoGpuUnsafeFreqs = val; + if (autoSave && kipHandler) kipHandler->writeKip(); + setStatus("Mariko GPU unsafe: " + std::string(val ? "ENABLED" : "DISABLED")); + }, opts); + break; + } + case 1: { // Erista GPU Unsafe Freqs + std::vector opts = {"Disabled (0)", "Enabled (1)"}; + showValueEditor("Enable Unsafe GPU Frequencies (Erista)", EditorType::LIST, + data.enableEristaGpuUnsafeFreqs, + [this, &data](int val) { + data.enableEristaGpuUnsafeFreqs = val; + if (autoSave && kipHandler) kipHandler->writeKip(); + setStatus("Erista GPU unsafe: " + std::string(val ? "ENABLED" : "DISABLED")); + }, opts); + break; + } + case 5: { // Mariko GPU UV + std::vector opts = {"UV0 (No Table)", "UV1 (Regular)", "UV2 (High)", "UV3 (Custom)"}; + showValueEditor("Mariko GPU Undervolt Mode", EditorType::LIST, + data.marikoGpuUV, + [this, &data](int val) { + data.marikoGpuUV = val; + if (autoSave && kipHandler) kipHandler->writeKip(); + setStatus("Mariko GPU UV mode set to UV" + std::to_string(val)); + }, opts); + break; + } + case 6: { // Erista GPU UV + std::vector opts = {"UV0 (No Table)", "UV1 (Regular)", "UV2 (High)", "UV3 (Custom)"}; + showValueEditor("Erista GPU Undervolt Mode", EditorType::LIST, + data.eristaGpuUV, + [this, &data](int val) { + data.eristaGpuUV = val; + if (autoSave && kipHandler) kipHandler->writeKip(); + setStatus("Erista GPU UV mode set to UV" + std::to_string(val)); + }, opts); + break; + } + default: + setStatus("Feature in development"); + break; + } + } +} + +void UI::handleCPUMenuInput(u64 kDown) { + if (!kipHandler || !kipLoaded) { + if (kDown & HidNpadButton_B) { + currentState = MenuState::MAIN; + selectedIndex = 0; + } + return; + } + + int maxItems = 8; + if (kDown & HidNpadButton_Down) selectedIndex = (selectedIndex + 1) % maxItems; + if (kDown & HidNpadButton_Up) selectedIndex = (selectedIndex - 1 + maxItems) % maxItems; + if (kDown & HidNpadButton_B) { + currentState = MenuState::MAIN; + selectedIndex = 0; + } + + if (kDown & HidNpadButton_A) { + auto& data = kipHandler->getData(); + + switch (selectedIndex) { + case 0: { // Mariko CPU Unsafe + std::vector opts = {"Disabled (0)", "Enabled (1)"}; + showValueEditor("Enable Unsafe CPU Frequencies (Mariko)", EditorType::LIST, + data.enableMarikoCpuUnsafeFreqs, + [this, &data](int val) { + data.enableMarikoCpuUnsafeFreqs = val; + if (autoSave && kipHandler) kipHandler->writeKip(); + setStatus("Mariko CPU unsafe: " + std::string(val ? "ENABLED" : "DISABLED")); + }, opts); + break; + } + case 1: { // Erista CPU Unsafe + std::vector opts = {"Disabled (0)", "Enabled (1)"}; + showValueEditor("Enable Unsafe CPU Frequencies (Erista)", EditorType::LIST, + data.enableEristaCpuUnsafeFreqs, + [this, &data](int val) { + data.enableEristaCpuUnsafeFreqs = val; + if (autoSave && kipHandler) kipHandler->writeKip(); + setStatus("Erista CPU unsafe: " + std::string(val ? "ENABLED" : "DISABLED")); + }, opts); + break; + } + default: + setStatus("Feature in development"); + break; + } + } +} + +void UI::handleRAMMenuInput(u64 kDown) { + if (!kipHandler || !kipLoaded) { + if (kDown & HidNpadButton_B) { + currentState = MenuState::MAIN; + selectedIndex = 0; + } + return; + } + + int maxItems = 11; + + if (kDown & HidNpadButton_Down) { + selectedIndex++; + if (selectedIndex >= maxItems) selectedIndex = 0; + if (selectedIndex >= scrollOffset + MAX_VISIBLE_ITEMS) { + scrollOffset = selectedIndex - MAX_VISIBLE_ITEMS + 1; + } + } + + if (kDown & HidNpadButton_Up) { + selectedIndex--; + if (selectedIndex < 0) selectedIndex = maxItems - 1; + if (selectedIndex < scrollOffset) scrollOffset = selectedIndex; + } + + if (kDown & HidNpadButton_B) { + currentState = MenuState::MAIN; + selectedIndex = 0; + scrollOffset = 0; + } + + if (kDown & HidNpadButton_A) { + setStatus("RAM setting (in development)"); + } +} + +void UI::handleMiscMenuInput(u64 kDown) { + int maxItems = 7; + if (kDown & HidNpadButton_Down) selectedIndex = (selectedIndex + 1) % maxItems; + if (kDown & HidNpadButton_Up) selectedIndex = (selectedIndex - 1 + maxItems) % maxItems; + if (kDown & HidNpadButton_B) { + currentState = MenuState::MAIN; + selectedIndex = 0; + } + + if (kDown & HidNpadButton_A) { + setStatus("Misc feature (in development)"); + } +} + +void UI::handleAboutMenuInput(u64 kDown) { + if (kDown & HidNpadButton_B) { + currentState = MenuState::MAIN; + selectedIndex = 0; + } +} + +void UI::handleSettingsMenuInput(u64 kDown) { + int maxItems = 4; + if (kDown & HidNpadButton_Down) selectedIndex = (selectedIndex + 1) % maxItems; + if (kDown & HidNpadButton_Up) selectedIndex = (selectedIndex - 1 + maxItems) % maxItems; + if (kDown & HidNpadButton_B) { + currentState = MenuState::MAIN; + selectedIndex = 0; + } + + if (kDown & HidNpadButton_A) { + switch (selectedIndex) { + case 0: // Toggle Auto-save + autoSave = !autoSave; + setStatus(autoSave ? "Auto-save ENABLED" : "Auto-save DISABLED"); + break; + case 1: // Save KIP Now + if (kipHandler && kipLoaded) { + if (kipHandler->writeKip()) { + setStatus("KIP saved successfully!"); + } else { + setStatus("ERROR: Failed to save KIP!"); + } + } else { + setStatus("ERROR: No KIP loaded!"); + } + break; + case 2: // Reload KIP + if (kipHandler) { + if (kipHandler->readKip()) { + setStatus("KIP reloaded successfully!"); + kipLoaded = true; + } else { + setStatus("ERROR: Failed to reload KIP!"); + kipLoaded = false; + } + } else { + setStatus("ERROR: No KIP handler initialized!"); + } + break; + case 3: // Back to Main + currentState = MenuState::MAIN; + selectedIndex = 0; + break; + } + } +} \ No newline at end of file diff --git a/Source/OnDeviceConfig/source/value_editor.cpp b/Source/OnDeviceConfig/source/value_editor.cpp new file mode 100644 index 00000000..fc9649ac --- /dev/null +++ b/Source/OnDeviceConfig/source/value_editor.cpp @@ -0,0 +1,134 @@ +/* + * HOC Configurator - Value Editor Implementation + * Copyright (C) Dominatorul, Souldbminer + */ + +#include "value_editor.hpp" +#include +#include + +ValueEditor::ValueEditor() : selectedValue(0), active(false) {} + +void ValueEditor::show(const EditorConfig& cfg) { + config = cfg; + selectedValue = cfg.currentValue; + active = true; +} + +void ValueEditor::hide() { + active = false; +} + +void ValueEditor::handleInput(u64 kDown) { + if (!active) return; + + if (kDown & HidNpadButton_B) { + hide(); + return; + } + + if (kDown & HidNpadButton_A) { + if (config.onValueChange) { + config.onValueChange(selectedValue); + } + hide(); + return; + } + + switch (config.type) { + case EditorType::TOGGLE: + if (kDown & (HidNpadButton_Left | HidNpadButton_Right | + HidNpadButton_Up | HidNpadButton_Down)) { + selectedValue = !selectedValue; + } + break; + + case EditorType::LIST: + case EditorType::FREQUENCY: + case EditorType::VOLTAGE: + if (kDown & HidNpadButton_Down) { + selectedValue++; + if (selectedValue >= (int)config.options.size()) { + selectedValue = 0; + } + } + if (kDown & HidNpadButton_Up) { + selectedValue--; + if (selectedValue < 0) { + selectedValue = config.options.size() - 1; + } + } + break; + + case EditorType::SLIDER: + if (kDown & HidNpadButton_Right) { + selectedValue = std::min(selectedValue + config.step, config.maxValue); + } + if (kDown & HidNpadButton_Left) { + selectedValue = std::max(selectedValue - config.step, config.minValue); + } + if (kDown & HidNpadButton_Down) { + selectedValue = std::max(selectedValue - config.step * 5, config.minValue); + } + if (kDown & HidNpadButton_Up) { + selectedValue = std::min(selectedValue + config.step * 5, config.maxValue); + } + break; + } +} + +void ValueEditor::render() { + if (!active) return; + + // Draw editor overlay + printf("\x1b[2J\x1b[1;1H"); + + // Header + printf("\x1b[47;30m"); + printf("===============================================================================\n"); + printf(" %s%-73s \n", config.title.c_str(), ""); + printf("===============================================================================\n"); + printf("\x1b[0m"); + + printf("\x1b[10;1H"); + printf("+- Value Editor ----------------------------------------------------------------+\n"); + printf("| |\n"); + + // Display current value based on type + switch (config.type) { + case EditorType::TOGGLE: + printf("| Current: %-66s |\n", selectedValue ? "Enabled (1)" : "Disabled (0)"); + printf("| |\n"); + printf("| Use D-Pad to toggle |\n"); + break; + + case EditorType::LIST: + case EditorType::FREQUENCY: + case EditorType::VOLTAGE: + if (selectedValue >= 0 && selectedValue < (int)config.options.size()) { + printf("| Current: %-66s |\n", config.options[selectedValue].c_str()); + } + printf("| |\n"); + printf("| Use Up/Down to change value |\n"); + break; + + case EditorType::SLIDER: + printf("| Current: %-66d |\n", selectedValue); + printf("| |\n"); + printf("| Left/Right: +/-%d Up/Down: +/-%d |\n", + config.step, config.step * 5); + printf("| Range: %d - %d%-56s|\n", config.minValue, config.maxValue, ""); + break; + } + + printf("| |\n"); + printf("+------------------------------------------------------------------------------+\n"); + + // Footer + printf("\x1b[42;1H"); + printf("\x1b[47;30m"); + printf("===============================================================================\n"); + printf(" [A] Confirm [B] Cancel \n"); + printf("===============================================================================\n"); + printf("\x1b[0m"); +} diff --git a/dist/atmosphere/kips/hoc.kip b/dist/atmosphere/kips/hoc.kip index 5adda602..d1e61529 100644 Binary files a/dist/atmosphere/kips/hoc.kip and b/dist/atmosphere/kips/hoc.kip differ diff --git a/docs/configurator.html b/docs/configurator.html new file mode 100644 index 00000000..eecd56c7 --- /dev/null +++ b/docs/configurator.html @@ -0,0 +1,140 @@ + + + + + + Horizon OC Configurator + + + + + + + + + + + +
+

Configurator

+

Configure frequencies and voltages to suit your hardware and preferences.

+ +
+ +
+ + + +
+ + +
+ +

Upload loader.kip here

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+ Built with ❤️. All trademarks and logos are the property of their respective owners. +
+ + + + + + diff --git a/docs/configurator.js b/docs/configurator.js new file mode 100644 index 00000000..409cec66 --- /dev/null +++ b/docs/configurator.js @@ -0,0 +1,683 @@ +/* configurator.js + Clean, readable reconstruction of the original minified configurator + - Keeps original behavior (tables, parsing, localStorage, GitHub release fetching) + - Uses modern async/await and DOMContentLoaded guarding +*/ + +const CUST_REV_ADV = 11; + +const CustPlatform = Object.freeze({ + Undefined: 0, + Erista: 1, + Mariko: 2, + All: 3 +}); + +class CustEntry { + constructor(id, name, platform, size, desc = [], defval = 0, range = [0, 1e6], step = 1, zeroable = true) { + this.id = id; + this.name = name; + this.platform = platform; + this.size = size; + this.desc = desc; + this.defval = defval; + this.step = step; + this.zeroable = zeroable; + this.min = range[0]; + this.max = range[1]; + this.value = this.defval; + } + + validate() { + const err = new ErrorToolTip(this.id).clear(); + if (Number.isNaN(this.value) || this.value === undefined) { + err.setMsg("Invalid value: Not a number").show(); + return false; + } + if (!this.zeroable && this.value === 0) { + err.setMsg("Zero is not allowed for this entry").show(); + return false; + } + if (this.value < this.min || this.value > this.max) { + err.setMsg(`Expected range: [${this.min}, ${this.max}], got ${this.value}.`).show(); + return false; + } + if ((this.value % this.step) !== 0) { + err.setMsg(`${this.value} % ${this.step} ≠ 0`).show(); + return false; + } + return true; + } + + getInputElement() { + return document.getElementById(this.id); + } + + updateValueFromElement() { + const el = this.getInputElement(); + this.value = Number(el?.value); + } + + isAvailableFor(platform) { + return platform === CustPlatform.Undefined || this.platform === platform || this.platform === CustPlatform.All; + } + + createElement(containerId = "config-list-basic") { + let input = this.getInputElement(); + if (!input) { + const wrapper = document.createElement("div"); + wrapper.classList.add("grid", "cust-element", "mb-3"); + + input = document.createElement("input"); + input.min = String(this.zeroable ? 0 : this.min); + input.max = String(this.max); + input.id = this.id; + input.type = "number"; + input.step = String(this.step); + input.classList.add("text-black", "w-36"); + + const label = document.createElement("label"); + label.setAttribute("for", this.id); + label.innerHTML = `${this.name}`; + label.classList.add("block", "mb-1"); + + const desc = document.createElement("blockquote"); + desc.innerHTML = "
    " + (this.desc || []).map(d => `
  • ${d}
  • `).join("") + "
"; + desc.setAttribute("for", this.id); + desc.classList.add("text-sm", "text-gray-300"); + + wrapper.appendChild(label); + wrapper.appendChild(input); + wrapper.appendChild(desc); + + const container = document.getElementById(containerId); + if (container) container.appendChild(wrapper); + new ErrorToolTip(this.id).addChangeListener(); + } + input.value = String(this.value); + } + + setElementValue() { + const el = this.getInputElement(); + if (el) el.value = String(this.value); + } + + setElementDefaultValue() { + const el = this.getInputElement(); + if (el) el.value = String(this.defval); + } + + removeElement() { + const el = this.getInputElement(); + if (el && el.parentElement && el.parentElement.parentElement) el.parentElement.parentElement.remove(); + } + + showElement() { + const el = this.getInputElement(); + if (el && el.parentElement && el.parentElement.parentElement) el.parentElement.parentElement.style.removeProperty("display"); + } + + hideElement() { + const el = this.getInputElement(); + if (el && el.parentElement && el.parentElement.parentElement) el.parentElement.parentElement.style.setProperty("display", "none"); + } +} + +class AdvEntry extends CustEntry { + createElement() { + super.createElement("config-list-advanced"); + } +} + +class GpuEntry extends CustEntry { + constructor(id, name, platform = CustPlatform.Mariko, size = 4, desc = ["range: 610 ≤ x ≤ 1000"], defval = 610, range = [610, 1000], step = 5, zeroable = false) { + super(id, name, platform, size, desc, defval, range, step, zeroable); + } + + createElement() { + super.createElement("config-list-gpu"); + } +} + +/* === Tables (reconstructed from original) === + Note: Numeric defaults preserved where feasible. +*/ +const CustTable = [ + new CustEntry("mtcConf", "DRAM Timing", CustPlatform.All, 4, + [ + "0: AUTO_ADJ_ALL: Auto adjust mtc table with LPDDR4 3733 Mbps specs, 8Gb density. Change timing with Advanced Config (Default)", + "1: CUSTOM_ADJ_ALL: Adjust only non-zero preset timings in Advanced Config", + "2: NO_ADJ_ALL: Use 1600 mtc table without adjusting (Timing becomes tighter if you raise dram clock)." + ], 0, [0, 2], 1), + new CustEntry("commonCpuBoostClock", "Boost Clock in kHz", CustPlatform.All, 4, + ["System default: 1785000", "Boost clock will be applied when applications request Boost Mode via performance configuration."], + 1785000, [1020000, 3000000], 1, false), + new CustEntry("commonEmcMemVolt", "EMC Vdd2 Voltage in uV", CustPlatform.All, 4, + ["Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.", "Erista Default: 1125000", "Mariko Default: 1100000", "Official lpddr4(x) range: 1060mV~1175mV (1100mV nominal)", "OCS need high voltage unlike l4t because of not scaling mtc table well. However it is recommended to stay within official limits", "Not enabled by default"], + 0, [1100000, 1250000], 12500), + new CustEntry("eristaCpuMaxVolt", "Erista CPU Max Voltage in mV", CustPlatform.Erista, 4, + ["Acceptable range: 1120 ≤ x ≤ 1300", "L4T Default: 1235"], 1235, [1120, 1300], 1), + new CustEntry("eristaEmcMaxClock", "Erista RAM Max Clock in kHz", CustPlatform.Erista, 4, + ["Values should be ≥ 1600000, and divided evenly by 3200.", "Recommended Clocks: 1862400, 2131200 (JEDEC)", "WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM"], 1862400, [1600000, 2131200], 3200), + new CustEntry("marikoCpuMaxVolt", "Mariko CPU Max Voltage in mV", CustPlatform.Mariko, 4, + ["System default: 1120", "Acceptable range: 1120 ≤ x ≤ 1300", "Changing this value affects cpu voltage calculation"], 1235, [1120, 1300], 5), + new CustEntry("marikoEmcMaxClock", "Mariko RAM Max Clock in kHz", CustPlatform.Mariko, 4, + ["Values should be ≥ 1600000, and divided evenly by 3200.", "Recommended Clocks: 1862400, 2131200, 2400000 (JEDEC)", "Some clocks above 2400Mhz might not boot, because OCS doesn't scale table very well", "WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM."], + 1996800, [1600000, 2502400], 3200), + new CustEntry("marikoEmcVddqVolt", "EMC Vddq (Mariko Only) Voltage in uV", CustPlatform.Mariko, 4, + ["Acceptable range: 550000 ≤ x ≤ 650000", "Value should be divided evenly by 5000", "Default: 600000", "Official lpddr4(x) range: 570mV~650mV (600mV nominal)", "Not enabled by default."], + 0, [550000, 650000], 5000), + new CustEntry("marikoCpuUV", "Enable Mariko CPU Undervolt", CustPlatform.Mariko, 4, + ["Reduce CPU power draw", "0 : Default Table", "1 : Undervolt Level 1 (SLT - CPU speedo < 1650)", "2 : Undervolt Level 1 (SLT - CPU speedo >= 1650)"], 0, [0, 2], 1), + new CustEntry("marikoGpuUV", "Enable Mariko GPU Undervolt", CustPlatform.Mariko, 4, + ["Reduce GPU power draw", "Your GPU might not withstand undervolt, and can hang your console, or crash games", "Undervolting too much will drop GPU performance even if it seems stable", "GPU voltages are dynamic and will change with temperature and gpu speedo", "0 : Default Table", "1 : Undervolt Level 1 (SLT: Aggressive)", "2 : Undervolt Level 2 (HiOPT: Drastic)", "3 : Custom static GPU Voltage Table (Use Gpu Configuration below)"], + 0, [0, 3], 1), + new CustEntry("commonGpuVoltOffset", "GPU Volt Offset", CustPlatform.All, 4, + ["Negative Voltage offset value for gpu dynamic voltage calculation", "For example, value of 10 will decrease 10mV gpu volt from all frequencies", "Default gpu vmin: Erista - 812.5mV / Mariko - 610mV", "Acceptable range: 0 ~ 100"], + 0, [0, 100], 1) +]; + +const AdvTable = [ + new AdvEntry("marikoEmcDvbShift", "Step up Mariko EMC DVB Table", CustPlatform.Mariko, 4, + ["Each number adds 25mV to SoC voltage", "Helps with stability at higher memory clock", "Acceptable range : 0~9"], 0, [0, 9], 1), + new AdvEntry("ramTimingPresetOne", "Primary RAM Timing Preset", CustPlatform.Mariko, 4, + ["WARNING: Unstable timings can corrupt your nand", "Select Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tRCD - tRP - tRAS (tRC = tRP + tRAS)", "0 : Do Not Adjust (2400Mhz: 12 - 12 - 28) (CUST_ADJ only)", "1 : 18 - 18 - 42 (Default timing)", "2 : 17 - 17 - 39", "3 : 16 - 16 - 36", "4 : 15 - 15 - 34", "5 : 14 - 14 - 32", "6 : 13 - 13 - 30"], + 1, [0, 6], 1), + new AdvEntry("ramTimingPresetTwo", "Secondary RAM Timing Preset", CustPlatform.Mariko, 4, + ["WARNING: Unstable timings can corrupt your nand", "Secondary Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tRRD - tFAW", "0 : Do Not Adjust (2400Mhz: 6.6 - 26.6) (CUST_ADJ only)", "1 : 10 - 40 (Default timing) (3733 specs)", "2 : 7.5 - 30 (4266 specs)", "3 : 6 - 24", "4 : 4 - 16", "5 : 3 - 12"], + 1, [0, 5], 1), + new AdvEntry("ramTimingPresetThree", "Secondary RAM Timing Preset", CustPlatform.Mariko, 4, + ["WARNING: Unstable timings can corrupt your nand", "Secondary Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tWR - tRTP", "0 : Do Not Adjust (2400Mhz: ?? - 5) (CUST_ADJ only)", "1 : 18 - 7.5 (Default timing)", "2 : 15 - 7.5", "3 : 15 - 6", "4 : 12 - 6", "5 : 12 - 4", "6 : 8 - 4"], + 1, [0, 6], 1), + new AdvEntry("ramTimingPresetFour", "Secondary RAM Timing Preset", CustPlatform.Mariko, 4, + ["WARNING: Unstable timings can corrupt your nand", "Secondary Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tRFC", "0 : Do Not Adjust (2400Mhz: 93.3) (CUST_ADJ only)", "1 : 140 (Default timing)", "2 : 120", "3 : 100", "4 : 80", "5 : 70", "6 : 60"], + 1, [0, 6], 1), + new AdvEntry("ramTimingPresetFive", "Secondary RAM Timing Preset", CustPlatform.Mariko, 4, + ["WARNING: Unstable timings can corrupt your nand", "Secondary Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tWTR", "0 : Do Not Adjust (2400Mhz: ??) (CUST_ADJ only)", "1 : 10 (Default timing)", "2 : 8", "3 : 6", "4 : 4", "5 : 2", "6 : 1"], + 1, [0, 6], 1), + new AdvEntry("ramTimingPresetSix", "Tertiary RAM Timing Preset", CustPlatform.Mariko, 4, + ["WARNING: Unstable timings can corrupt your nand", "Tertiary Timing Preset for both AUTO_ADJ and CUSTOM_ADJ", "Values are : tREFpb", "0 : Do Not Adjust (2400Mhz: 325) (CUST_ADJ only)", "1 : 488 (Default timing)", "2 : 976", "3 : 1952", "4 : 3256", "5 : MAX"], + 1, [0, 5], 1), + new AdvEntry("ramTimingPresetSeven", "Latency Decrement", CustPlatform.Mariko, 4, + ["WARNING: Unstable timings can corrupt your nand", "Latency decrement for both AUTO_ADJ and CUSTOM_ADJ", "This preset decreases Write/Read related delays. Values are Write - Read", "0 : 0 - 0, Do Not Adjust for CUST_ADJ", "1 : '-2' - '-4'", "2 : '-4' - '-8'", "3 : '-6' - '-12'", "4 : '-8' - '-16'", "5 : '-10' - '-20'", "6 : '-12' - '-24'"], + 0, [0, 6], 1) +]; + +const GpuTable = [ + new GpuEntry("0", "76.8"), + new GpuEntry("1", "153.6"), + new GpuEntry("2", "230.4"), + new GpuEntry("3", "307.2"), + new GpuEntry("4", "384.0"), + new GpuEntry("5", "460.8"), + new GpuEntry("6", "537.6"), + new GpuEntry("7", "614.4"), + new GpuEntry("8", "691.2"), + new GpuEntry("9", "768.0"), + new GpuEntry("10", "844.8"), + new GpuEntry("11", "921.6"), + new GpuEntry("12", "998.4"), + new GpuEntry("13", "1075.2"), + new GpuEntry("14", "1152.0"), + new GpuEntry("15", "1228.8"), + new GpuEntry("16", "1267.2") +]; + +/* ===== ErrorToolTip ===== */ +class ErrorToolTip { + constructor(id, msg) { + this.id = id; + this.msg = msg; + this.element = document.getElementById(id); + if (msg) this.setMsg(msg); + } + + setMsg(msg) { + this.msg = String(msg); + return this; + } + + show() { + if (this.element) this.element.setAttribute("aria-invalid", "true"); + if (this.msg && this.element) { + this.element.setAttribute("title", this.msg); + if (this.element.parentElement) { + this.element.parentElement.setAttribute("data-tooltip", this.msg); + this.element.parentElement.setAttribute("data-placement", "top"); + } + } + return this; + } + + clear() { + if (this.element) this.element.removeAttribute("aria-invalid"); + if (this.element) this.element.removeAttribute("title"); + if (this.element && this.element.parentElement) { + this.element.parentElement.removeAttribute("data-tooltip"); + this.element.parentElement.removeAttribute("data-placement"); + } + return this; + } + + addChangeListener() { + if (!this.element) return; + this.element.addEventListener("change", () => { + const tableEntry = CustTable.find(e => e.id === this.id); + if (tableEntry) { + tableEntry.value = Number(this.element.value); + tableEntry.validate(); + } + }); + } +} + +/* ===== CustStorage ===== */ +class CustStorage { + constructor() { + this.storage = {}; + this.key = "last_saved"; + } + + updateFromTable() { + // helper to update and validate each entry + const updateOne = (entry) => { + entry.updateValueFromElement(); + if (!entry.validate()) { + const el = entry.getInputElement(); + if (el) el.focus(); + throw new Error(`Invalid ${entry.name}`); + } + }; + + CustTable.forEach(updateOne); + AdvTable.forEach(updateOne); + GpuTable.forEach(updateOne); + + this.storage = {}; + let t = Object.fromEntries(CustTable.map(e => [e.id, e.value])); + Object.keys(t).forEach(k => (this.storage[k] = t[k])); + t = Object.fromEntries(AdvTable.map(e => [e.id, e.value])); + Object.keys(t).forEach(k => (this.storage[k] = t[k])); + // GPU table could be included too if desired + } + + setTable() { + const keys = Object.keys(this.storage); + keys.forEach(k => { + const c = CustTable.find(e => e.id === k); + if (c) c.value = this.storage[k]; + }); + keys.forEach(k => { + const a = AdvTable.find(e => e.id === k); + if (a) a.value = this.storage[k]; + }); + + // Set defaults for entries not found + CustTable.filter(e => !keys.includes(e.id)).forEach(e => e.value = e.defval); + AdvTable.filter(e => !keys.includes(e.id)).forEach(e => e.value = e.defval); + + // Validate and set element values + CustTable.forEach(e => { + if (!e.validate()) { + const el = e.getInputElement(); + if (el) el.focus(); + throw new Error(`Invalid ${e.name}`); + } + e.setElementValue(); + }); + AdvTable.forEach(e => { + if (!e.validate()) { + const el = e.getInputElement(); + if (el) el.focus(); + throw new Error(`Invalid ${e.name}`); + } + e.setElementValue(); + }); + GpuTable.forEach(e => { + if (!e.validate()) { + const el = e.getInputElement(); + if (el) el.focus(); + throw new Error(`Invalid ${e.name}`); + } + e.setElementValue(); + }); + } + + save() { + localStorage.setItem(this.key, JSON.stringify(this.storage)); + } + + load() { + const raw = localStorage.getItem(this.key); + if (!raw) return null; + const parsed = JSON.parse(raw); + + // Filter unknown keys and warn + const custIds = CustTable.map(e => e.id); + const ignoredCust = Object.keys(parsed).filter(k => !custIds.includes(k)); + if (ignoredCust.length) console.log("Ignored (cust):", ignoredCust); + Object.keys(parsed).filter(k => custIds.includes(k)).forEach(k => (this.storage[k] = parsed[k])); + + const advIds = AdvTable.map(e => e.id); + const ignoredAdv = Object.keys(parsed).filter(k => !advIds.includes(k)); + if (ignoredAdv.length) console.log("Ignored (adv):", ignoredAdv); + Object.keys(parsed).filter(k => advIds.includes(k)).forEach(k => (this.storage[k] = parsed[k])); + + return this.storage; + } +} + +/* ===== Cust (binary parser/patcher) ===== + This class follows the original structure: + - find magic in buffer using magicLen, mapper sizes + - parse data using size-mapped getters/setters + - produce Blob for download (patched buffer) +*/ +class Cust { + constructor() { + this.storage = new CustStorage(); + this.magic = 1414747459; // original magic + this.magicLen = 4; + this.mapper = { + 2: { get: (offset) => this.view.getUint16(offset, true), set: (offset, v) => this.view.setUint16(offset, v, true) }, + 4: { get: (offset) => this.view.getUint32(offset, true), set: (offset, v) => this.view.setUint32(offset, v, true) } + }; + } + + findMagicOffset() { + this.view = new DataView(this.buffer); + for (let offset = 0; offset < this.view.byteLength; offset += this.magicLen) { + if (this.mapper[this.magicLen].get(offset) === this.magic) { + this.beginOffset = offset; + return; + } + } + throw new Error("Invalid loader.kip file (magic not found)"); + } + + save() { + // update from table (validate & populate storage) + this.storage.updateFromTable(); + + const writeOne = (entry) => { + if (!entry.offset && entry.offset !== 0) { + // If offset is missing in the UI, it's an error in mapping + const el = entry.getInputElement(); + if (el) el.focus(); + throw new Error(`Failed to get offset for ${entry.name}`); + } + const mapper = this.mapper[entry.size]; + if (!mapper) { + const el = entry.getInputElement(); + if (el) el.focus(); + throw new Error(`Unknown size at ${entry.name}`); + } + mapper.set(entry.offset, entry.value); + }; + + // The original iterated over tables and used .offset values stored in entries + CustTable.forEach(writeOne); + AdvTable.forEach(writeOne); + GpuTable.forEach(writeOne); + + // persist storage to localStorage + this.storage.save(); + + // create download link for patched buffer + const blob = new Blob([this.buffer], { type: "application/octet-stream" }); + const url = URL.createObjectURL(blob); + + const a = document.createElement("a"); + a.href = url; + a.download = "loader.kip"; + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); + + // UI: enable load last saved if needed + this.toggleLoadLastSavedBtn(true); + } + + removeHTMLForm() { + CustTable.forEach(e => e.removeElement()); + } + + toggleLoadLastSavedBtn(visible) { + const btn = document.getElementById("load_saved"); + if (!btn) return; + if (visible) { + btn.addEventListener("click", () => { + if (this.storage.load()) this.storage.setTable(); + }); + btn.style.removeProperty("display"); + btn.removeAttribute("disabled"); + } else { + btn.style.setProperty("display", "none"); + } + } + + createHTMLForm() { + // create elements for each table entry + CustTable.forEach(e => e.createElement("config-list-basic")); + + // Add separators/titles for advanced and gpu lists + const advContainer = document.getElementById("config-list-advanced"); + if (advContainer) { + const p = document.createElement("p"); + p.innerHTML = "Advanced configuration"; + advContainer.appendChild(p); + } + const gpuContainer = document.getElementById("config-list-gpu"); + if (gpuContainer) { + const p = document.createElement("p"); + p.innerHTML = "Gpu Volt configuration"; + gpuContainer.appendChild(p); + } + + AdvTable.forEach(e => e.createElement("config-list-advanced")); + GpuTable.forEach(e => e.createElement("config-list-gpu")); + + // Buttons: load default, load saved, save + const loadDefaultBtn = document.getElementById("load_default"); + if (loadDefaultBtn) { + loadDefaultBtn.removeAttribute("disabled"); + loadDefaultBtn.addEventListener("click", () => { + CustTable.forEach(e => e.setElementDefaultValue()); + }); + } + + // show load saved if we have saved storage + this.toggleLoadLastSavedBtn(this.storage.load() !== null); + + const saveBtn = document.getElementById("save"); + if (saveBtn) { + saveBtn.removeAttribute("disabled"); + saveBtn.addEventListener("click", () => { + try { + this.save(); + } catch (err) { + console.error(err); + alert(String(err)); + } + }); + } + } + + initCustTabs() { + // map nav buttons (role="tablist") to platform toggles + const tabButtons = Array.from(document.querySelectorAll('nav[role="tablist"] > button, .cust-platform-tabs > button')); + if (!tabButtons.length) return; + tabButtons.forEach(btn => { + btn.removeAttribute("disabled"); + const plat = Number(btn.getAttribute("data-platform")); + btn.addEventListener("click", (ev) => { + // style toggling + const outlineClass = ["outline"]; + btn.classList.remove(...outlineClass); + tabButtons.filter(x => x !== btn).forEach(x => x.classList.add(...outlineClass)); + // show/hide entries + CustTable.forEach(e => { + if (e.isAvailableFor(plat)) e.showElement(); else e.hideElement(); + }); + }); + }); + } + + parse() { + // beginOffset should already exist + let offset = this.beginOffset + this.magicLen; + // read revision (4-byte) + this.rev = this.mapper[4].get(offset); + if (this.rev !== CUST_REV_ADV) throw new Error(`Unsupported custRev, expected: ${CUST_REV_ADV}, got ${this.rev}`); + offset += 4; + const revEl = document.getElementById("cust_rev"); + if (revEl) revEl.innerHTML = `Cust v${this.rev} is loaded.`; + + const readOne = (entry) => { + entry.offset = offset; + const mapper = this.mapper[entry.size]; + if (!mapper) { + const el = entry.getInputElement(); + if (el) el.focus(); + throw new Error(`Unknown size at ${entry.name}`); + } + entry.value = mapper.get(offset); + offset += entry.size; + entry.validate(); + }; + + CustTable.forEach(readOne); + AdvTable.forEach(readOne); + GpuTable.forEach(readOne); + } + + load(buffer) { + try { + this.buffer = buffer; + this.findMagicOffset(); + this.removeHTMLForm(); + this.parse(); + this.initCustTabs(); + this.createHTMLForm(); + } catch (err) { + console.error(err); + alert(String(err)); + } + } +} + +/* ===== Release fetching classes ===== */ +class ReleaseAsset { + constructor(obj) { + this.downloadUrl = obj?.browser_download_url; + this.updatedAt = obj?.updated_at; + } +} + +class ReleaseInfo { + constructor() { + this.ocLatestApi = "https://api.github.com/repos/Horizon-OC/Horizon-OC/releases/latest"; + } + + async load() { + try { + const json = await this.responseFromApi(this.ocLatestApi); + this.parseOcResponse(json); + } catch (err) { + console.error(err); + alert(String(err)); + } + } + + async responseFromApi(url) { + const res = await fetch(url, { method: "GET", headers: { Accept: "application/json" } }); + if (!res.ok) throw new Error(`Failed to connect to "${url}": ${res.status}`); + return await res.json(); + } + + parseOcResponse(json) { + this.ocVer = json.tag_name; + const assets = json.assets || []; + this.loaderKipAsset = new ReleaseAsset(assets.find(a => a.name.endsWith("hoc.kip")) || {}); + this.sdOutZipAsset = new ReleaseAsset(assets.find(a => a.name.endsWith("dist.zip")) || {}); + } +} + +class DownloadSection { + constructor() { + this.element = document.getElementById("download_btn_grid") || document.getElementById("download"); + } + + async load() { + // Wait until the element is visible in the viewport + for (; !this.isVisible();) { + await new Promise(resolve => setTimeout(resolve, 1000)); + } + const release = new ReleaseInfo(); + await release.load(); + this.update("loader_kip_btn", `hoc.kip ${release.ocVer}
${release.loaderKipAsset.updatedAt}`, release.loaderKipAsset.downloadUrl); + this.update("sdout_zip_btn", `dist.zip ${release.ocVer}
${release.sdOutZipAsset.updatedAt}`, release.sdOutZipAsset.downloadUrl); + } + + isVisible() { + if (!this.element) return true; + const r = this.element.getBoundingClientRect(); + return r.top > 0 && r.left > 0 && r.bottom - r.height < (window.innerHeight || document.documentElement.clientHeight) && r.right - r.width < (window.innerWidth || document.documentElement.clientWidth); + } + + update(id, html, href) { + const el = document.getElementById(id); + if (!el) return; + el.innerHTML = html; + el.removeAttribute("aria-busy"); + if (href) el.setAttribute("href", href); + } +} + +/* ===== Initialization wiring ===== */ +document.addEventListener("DOMContentLoaded", async () => { + // Ensure containers exist (fail gracefully if not) + const ensureEl = (id, createTag = "div") => { + if (!document.getElementById(id)) { + const container = document.createElement(createTag); + container.id = id; + container.classList.add("mt-4"); + // append to config section or body as fallback + const config = document.getElementById("config") || document.body; + config.appendChild(container); + } + }; + + ["config-list-basic", "config-list-advanced", "config-list-gpu"].forEach(id => ensureEl(id)); + + // Wire file input + const fileInput = document.getElementById("file"); + if (fileInput) { + fileInput.addEventListener("change", (ev) => { + const cust = new Cust(); + const target = ev.target; + if (!target || !target.files) return; + const fr = new FileReader(); + fr.readAsArrayBuffer(target.files[0]); + fr.onloadend = (e) => { + if (e.target && e.target.readyState === FileReader.DONE) { + cust.load(e.target.result); + } + }; + }); + } + + // Wire DownloadSection (non-blocking) + (async () => { + try { + const ds = new DownloadSection(); + await ds.load(); + } catch (err) { + console.warn("DownloadSection failed:", err); + } + })(); + + // If HTML had DOMContentLoaded wiring in original minified code, emulate it: + // some initialization that original file did on DOMContentLoaded -> start the DownloadSection (done above) +}); diff --git a/docs/index.html b/docs/index.html index 39723878..1ea49697 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,167 +1,153 @@ - + - Horizon OC - Switch Overclocking Tool + Horizon OC - - -
-
-
+ - -
+ +
+

Horizon OC

+

Upgrade the performance of your Nintendo Switch Console

- -
-

Horizon OC

-

- Upgrade the performance of your Nintendo Switch Console -

-
- -
-

What is Horizon OC?

-

- HOC is a tool to overclock your modded Nintendo Switch Console. - This can help improve framrates, load times and latency in games. + +

+

What is Horizon OC?

+

+ Horizon OC is a tool to overclock your modded Nintendo Switch console. + This can help improve frame rates, load times, and latency in games.

-
-
-

⚡ Performance

-

Overclocking your console can lead to significantly better performance in demanding titles

+
+
+

⚡ Performance

+

Overclocking your console can lead to significantly better performance in demanding titles.

-
-

☣ Safely

-

Horizon OC aims to be as safe as possible, while allowing advanced users to customize it further

+
+

☣ Safety

+

Horizon OC aims to be as safe as possible, while allowing advanced users to customize it further.

-
-

🔧 Customizable

-

Horizon OC comes with a advanced configurator which allows you to customize it to your own needs

+
+

🔧 Customizable

+

Horizon OC comes with an advanced configurator allowing you to tune it for your own needs.

- -
+ +

Get Horizon OC

- Download Latest Release -

Ensure to use the latest version of Atmosphere

+ Download Latest Release +

Ensure you are using the latest version of Atmosphere.

-