diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp index 81220816..722f42e7 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp @@ -1,8 +1,6 @@ /* * Copyright (C) Switch-OC-Suite * - * Copyright (c) 2023 hanai3Bi - * * 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. @@ -30,7 +28,7 @@ volatile CustomizeTable C = { * NO_ADJ_ALL: No timing adjustment for both Erista and Mariko. * CUSTOMIZED_ALL: Replace with values in customized table for both Erista and Mariko. */ -.mtcConf = AUTO_ADJ_ALL, +.mtcConf = CUSTOM_ADJ_ALL, /* Common: * - Boost Clock in kHz: @@ -38,7 +36,7 @@ volatile CustomizeTable C = { * 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, +.commonCpuBoostClock = 2295000, /* - 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) @@ -46,7 +44,7 @@ volatile CustomizeTable C = { * Value should be divided evenly by 12'500. * Not enabled by default. */ -.commonEmcMemVolt = 1175000, // RAM Rated Voltage +.commonEmcMemVolt = 1212500, /* Erista CPU: * - Max Voltage in mV @@ -68,7 +66,7 @@ volatile CustomizeTable C = { * - Max Voltage in mV: * Default voltage: 1120 */ -.marikoCpuMaxVolt = 1120, +.marikoCpuMaxVolt = 1160, /* Mariko EMC(RAM): * - RAM Clock in kHz: @@ -79,39 +77,40 @@ volatile CustomizeTable C = { * - System instabilities * - NAND corruption */ -.marikoEmcMaxClock = 1996800, +.marikoEmcMaxClock = 2400000, /* - EMC Vddq (Mariko Only) Voltage in uV * Range: 550'000 to 650'000 uV - * Value should be divided evenly by 5'000 + * Value should be dicvided evenly by 5'000 * Default: 600'000 * Not enabled by default. * This will not work without sys-clk-OC. */ -.marikoEmcVddqVolt = 0, +.marikoEmcVddqVolt = 640000, -.marikoCpuUV = 0, +.marikoCpuUV = 1, -.marikoGpuUV = 0, +.marikoGpuUV = 2, .commonGpuVoltOffset = 0, -.marikoEmcDvbShift = 0, +.marikoEmcDvbShift = 5, -.ramTimingPresetOne = 0, +.tRAS = 0, +.tRCD = 0, +.tREFpb = 0, +.tRFCpb = 0, +.tRPpb = 0, +.tRRD = 0, +.R2W = 0, +.tWTR = 0, -.ramTimingPresetTwo = 0, +.marikoGpuVoltArray = {635, 635, 635, 635, 635, 635, 635, 635, 635, 635, 635, 635, 660, 685, 715, 745, 765, 785}, -.ramTimingPresetThree = 0, +.marikoCpuHighVoltOffset = 70, -.ramTimingPresetFour = 0, +.marikoB3 = 179, -.ramTimingPresetFive = 0, - -.ramTimingPresetSix = 0, - -.ramTimingPresetSeven = 0, - -.marikoGpuVoltArray = {610, 610, 610, 610, 610, 610, 610, 610, 610, 610, 620, 640, 675, 710, 735, 785, 815}, +.marikoCpuHighUV = 0, /* Advanced Settings: * - Erista CPU DVFS Table: @@ -137,8 +136,6 @@ volatile CustomizeTable C = { { 1887000, { 1235000 }, { 5100873, -279186, 4747 } }, { 1963500, { 1235000 }, { 5100873, -279186, 4747 } }, { 2091000, { 1235000 }, { 5100873, -279186, 4747 } }, - { 2193000, { 1235000 }, { 5100873, -279186, 4747 } }, - { 2295000, { 1235000 }, { 5100873, -279186, 4747 } }, }, /* - Mariko CPU DVFS Table: @@ -164,13 +161,15 @@ volatile CustomizeTable C = { { 1887000, { 1609246, -37515, 27 }, { 1120000 } }, { 1963500, { 1675751, -38635, 27 }, { 1120000 } }, // Appending table - { 2091000, { 1716501, -39395, 27 }, { 1235000 } }, - { 2193000, { 1775132, -40505, 27 }, { 1235000 } }, - { 2295000, { 1866287, -42005, 27 }, { 1235000 } }, - // Unsafe - { 2397000, { 1961107, -43506, 27 }, { 1235000 } }, - { 2499000, { 1754400, -35643, 27 }, { 1235000 } }, - { 2601000, { 1805897, -36331, 27 }, { 1235000 } }, + { 2091000, { 1580725, -33235, 113 }, { 1120000 } }, + { 2193000, { 1580725, -33235, 113 }, { 1235000 } }, + { 2295000, { 1635431, -34095, 113 }, { 1235000 } }, + { 2397000, { 1702903, -34955, 113 }, { 1235000 } }, + { 2499000, { 1754400, -35643, 113 }, { 1235000 } }, + { 2601000, { 1805897, -36331, 113 }, { 1235000 } }, + { 2703000, { 1857394, -37019, 113 }, { 1235000 } }, + { 2805000, { 1908891, -37707, 113 }, { 1235000 } }, + { 2907000, { 1960388, -38395, 113 }, { 1250000 } }, }, .marikoCpuDvfsTableSLT = { @@ -226,47 +225,6 @@ volatile CustomizeTable C = { * 1305600 might not work for some SoCs. */ .marikoGpuDvfsTable = { - { 76800, {}, { 610000, } }, - { 153600, {}, { 610000, } }, - { 230400, {}, { 610000, } }, - { 307200, {}, { 610000, } }, - { 384000, {}, { 610000, } }, - { 460800, {}, { 610000, } }, - { 537600, {}, { 801688, -10900, -163, 298, -10599, 162 } }, - { 614400, {}, { 824214, -5743, -452, 238, -6325, 81 } }, - { 691200, {}, { 848830, -3903, -552, 119, -4030, -2 } }, - { 768000, {}, { 891575, -4409, -584, 0, -2849, 39 } }, - { 844800, {}, { 940071, -5367, -602, -60, -63, -93 } }, - { 921600, {}, { 986765, -6637, -614, -179, 1905, -13 } }, - { 998400, {}, { 1098475, -13529, -497, -179, 3626, 9 } }, - { 1075200, {}, { 1163644, -12688, -648, 0, 1077, 40 } }, - { 1152000, {}, { 1204812, -9908, -830, 0, 1469, 110 } }, - { 1228800, {}, { 1277303, -11675, -859, 0, 3722, 313 } }, - { 1267200, {}, { 1335531, -12567, -867, 0, 3681, 559 } }, - // Appending table - { 1305600, {}, { 1374130, -13725, -859, 0, 4442, 576 } }, -}, - -.marikoGpuDvfsTableSLT = { - { 76800, {}, { 590000, } }, - { 153600, {}, { 590000, } }, - { 230400, {}, { 590000, } }, - { 307200, {}, { 590000, } }, - { 384000, {}, { 590000, } }, - { 460800, {}, { 795089, -11096, -163, 298, -10421, 162 } }, - { 537600, {}, { 795089, -11096, -163, 298, -10421, 162 } }, - { 614400, {}, { 820606, -6285, -452, 238, -6182, 81 } }, - { 691200, {}, { 846289, -4565, -552, 119, -3958, -2 } }, - { 768000, {}, { 888720, -5110, -584, 0, -2849, 39 } }, - { 844800, {}, { 936634, -6089, -602, -60, -99, -93 } }, - { 921600, {}, { 982562, -7373, -614, -179, 1797, -13 } }, - { 998400, {}, { 1090179, -14125, -497, -179, 3518, 9 } }, - { 1075200, {}, { 1155798, -13465, -648, 0, 1077, 40 } }, - { 1152000, {}, { 1198568, -10904, -830, 0, 1469, 110 } }, - { 1228800, {}, { 1269988, -12707, -859, 0, 3722, 313 } }, - { 1267200, {}, { 1308155, -13694, -867, 0, 3681, 559 } }, -}, -.marikoGpuDvfsTableHiOPT = { { 76800, {}, { 590000, } }, { 153600, {}, { 590000, } }, { 230400, {}, { 590000, } }, @@ -284,6 +242,49 @@ volatile CustomizeTable C = { { 1152000, {}, { 1180029, -14534, -830, 0, 1469, 110 } }, { 1228800, {}, { 1248293, -16383, -859, 0, 3722, 313 } }, { 1267200, {}, { 1286399, -17475, -867, 0, 3681, 559 } }, + // Appending table + { 1305600, {}, { 1304130, -13725, -859, 0, 4442, 576 } }, +}, + +.marikoGpuDvfsTableSLT = { + { 76800, {}, { 635000, } }, + { 153600, {}, { 635000, } }, + { 230400, {}, { 635000, } }, + { 307200, {}, { 635000, } }, + { 384000, {}, { 635000, } }, + { 460800, {}, { 635000, } }, + { 537600, {}, { 635000, } }, + { 614400, {}, { 635000, } }, + { 691200, {}, { 635000, } }, + { 768000, {}, { 635000, } }, + { 844800, {}, { 635000, } }, + { 921600, {}, { 635000, } }, + { 998400, {}, { 901575, -4409, -584, 0, -2849, 39 } }, + { 1075200, {}, { 940071, -5367, -602, -60, -63, -93 } }, + { 1152000, {}, { 996765, -6637, -614, -179, 1905, -13 } }, + { 1228800, {}, { 1098475, -13529, -497, -179, 3626, 9 } }, + { 1267200, {}, { 1131060, -13109, -573, -90, 2352, 25 } }, + { 1305600, {}, { 1163644, -12688, -648, 0, 1077, 40 } }, +}, +.marikoGpuDvfsTableHiOPT = { + { 76800, {}, { 550000, } }, + { 153600, {}, { 550000, } }, + { 230400, {}, { 550000, } }, + { 307200, {}, { 550000, } }, + { 384000, {}, { 550000, } }, + { 460800, {}, { 550000, } }, + { 537600, {}, { 550000, } }, + { 614400, {}, { 550000, } }, + { 691200, {}, { 550000, } }, + { 768000, {}, { 801688, -10900, -163, 298, -10599, 162 } }, + { 844800, {}, { 824214, -5743, -452, 238, -6325, 81 } }, + { 921600, {}, { 848830, -3903, -552, 119, -4030, -2 } }, + { 998400, {}, { 901575, -4409, -584, 0, -2849, 39 } }, + { 1075200, {}, { 940071, -5367, -602, -60, -63, -93 } }, + { 1152000, {}, { 996765, -6637, -614, -179, 1905, -13 } }, + { 1228800, {}, { 1098475, -13529, -497, -179, 3626, 9 } }, + { 1267200, {}, { 1131060, -13109, -573, -90, 2352, 25 } }, + { 1305600, {}, { 1163644, -12688, -648, 0, 1077, 40 } }, }, //.eristaMtcTable = const_cast(&EristaMtcTablePlaceholder), @@ -291,4 +292,4 @@ volatile CustomizeTable C = { }; -} \ No newline at end of file +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp index 3bb1a573..c43eb2f1 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp @@ -1,8 +1,6 @@ /* * Copyright (C) Switch-OC-Suite * - * Copyright (c) 2023 hanai3Bi - * * 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. @@ -59,15 +57,20 @@ typedef struct CustomizeTable { u32 commonGpuVoltOffset; // advanced config u32 marikoEmcDvbShift; - u32 ramTimingPresetOne; - u32 ramTimingPresetTwo; - u32 ramTimingPresetThree; - u32 ramTimingPresetFour; - u32 ramTimingPresetFive; - u32 ramTimingPresetSix; - u32 ramTimingPresetSeven; + const u32 tRAS; + const u32 tRCD; + const u32 tREFpb; + const u32 tRFCpb; + const u32 tRPpb; + const double tRRD; + const u32 R2W; + const u32 tWTR; + // - u32 marikoGpuVoltArray[17]; + u32 marikoGpuVoltArray[18]; + u8 marikoCpuHighVoltOffset; + u8 marikoB3; + u8 marikoCpuHighUV; CustomizeCpuDvfsTable eristaCpuDvfsTable; CustomizeCpuDvfsTable marikoCpuDvfsTable; CustomizeCpuDvfsTable marikoCpuDvfsTableSLT; @@ -86,4 +89,4 @@ extern volatile CustomizeTable C; //extern volatile EristaMtcTable EristaMtcTablePlaceholder; //extern volatile MarikoMtcTable MarikoMtcTablePlaceholder; -} \ No newline at end of file +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_table.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_table.hpp index d1667c84..65c2985d 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_table.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_table.hpp @@ -26,7 +26,7 @@ struct MarikoTiming { uint32_t emc_rfc_slr; uint32_t emc_ras; uint32_t emc_rp; - uint32_t emc_r2w; + uint32_t emcR2W; uint32_t emc_w2r; uint32_t emc_r2p; uint32_t emc_w2p; @@ -257,7 +257,7 @@ struct EristaTiming { uint32_t emc_rfc_slr; uint32_t emc_ras; uint32_t emc_rp; - uint32_t emc_r2w; + uint32_t emcR2W; uint32_t emc_w2r; uint32_t emc_r2p; uint32_t emc_w2p; @@ -777,7 +777,7 @@ struct MarikoMtcTable { uint32_t mc_emem_arb_timing_wap2pre; uint32_t mc_emem_arb_timing_r2r; uint32_t mc_emem_arb_timing_w2w; - uint32_t mc_emem_arb_timing_r2w; + uint32_t mc_emem_arb_timingR2W; uint32_t mc_emem_arb_timing_ccdmw; uint32_t mc_emem_arb_timing_w2r; uint32_t mc_emem_arb_timing_rfcpb; @@ -1170,7 +1170,7 @@ struct EristaMtcTable { uint32_t mc_emem_arb_timing_wap2pre; uint32_t mc_emem_arb_timing_r2r; uint32_t mc_emem_arb_timing_w2w; - uint32_t mc_emem_arb_timing_r2w; + uint32_t mc_emem_arb_timingR2W; uint32_t mc_emem_arb_timing_ccdmw; uint32_t mc_emem_arb_timing_w2r; uint32_t mc_emem_arb_timing_rfcpb; 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 b750be0a..84e355c0 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp @@ -1,235 +1,172 @@ -/* - * Copyright (c) 2023 hanai3Bi - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * from GCC preprocessor output - */ +#pragma once - #pragma once +#include "oc_common.hpp" - #include "oc_common.hpp" - 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) - const u8 ramtmarker[4] = {'R', 'A', 'M', 'T'}; - //Preset One - const std::array tRCD_values = {18, 17, 16, 15, 14, 13}; - const std::array tRP_values = {18, 17, 16, 15, 14, 13}; - const std::array tRAS_values = {42, 39, 36, 34, 32, 30}; - - // Preset Two - const std::array tRRD_values = {10, 7.5, 6, 4, 3}; - const std::array tFAW_values = {40, 30, 24, 16, 12}; - - // Preset Three - const std::array tWR_values = {18, 15, 15, 12, 12, 8}; - 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, 8, 6, 4, 2, 1}; - - // Preset Six - const std::array tREFpb_values = {488, 976, 1952, 3256, 9999}; - - const u32 TIMING_PRESET_ONE = C.ramTimingPresetOne; - const u32 TIMING_PRESET_TWO = C.ramTimingPresetTwo; - const u32 TIMING_PRESET_THREE = C.ramTimingPresetThree; - 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; - - // tRFCpb (refresh cycle time per bank) in ns for 8Gb density - const u32 tRFCpb = !TIMING_PRESET_FOUR ? 140 : tRFC_values[TIMING_PRESET_FOUR-1]; - - // tRFCab (refresh cycle time all banks) in ns for 8Gb density - const u32 tRFCab = !TIMING_PRESET_FOUR ? 280 : 2*tRFCpb; - - // tRAS (row active time) in ns - const u32 tRAS = !TIMING_PRESET_ONE ? 42 : tRAS_values[TIMING_PRESET_ONE-1]; - - // tRPpb (row precharge time per bank) in ns - const u32 tRPpb = !TIMING_PRESET_ONE ? 18 : tRP_values[TIMING_PRESET_ONE-1]; - - // tRPab (row precharge time all banks) in ns - const u32 tRPab = !TIMING_PRESET_ONE ? 21 : tRPpb + 3; - - // tRC (ACTIVATE-ACTIVATE command period same bank) in ns - const u32 tRC = tRPpb + tRAS; - - // 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; - // WRITE command to first DQS transition(max) (tCK) - const double tDQSS_max = 1.25; - // DQ-to-DQS offset(max) (ns) - const double tDQS2DQ_max = 0.8; - // DQS_t, DQS_c to DQ skew total, per group, per access (DBI Disabled) - const double tDQSQ = 0.18; - - // Write-to-Read delay - const u32 tWTR = !TIMING_PRESET_FIVE ? 10 : tWTR_values[TIMING_PRESET_FIVE-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]; - - // Read to refresh delay - const u32 tR2REF = tRTP + tRPpb; - - // tRCD (RAS-CAS delay) in ns - const u32 tRCD = !TIMING_PRESET_ONE ? 18 : tRCD_values[TIMING_PRESET_ONE-1]; - - // tRRD (Active bank-A to Active bank-B) in ns - const double tRRD = !TIMING_PRESET_TWO ? 10. : tRRD_values[TIMING_PRESET_TWO-1]; - - // tREFpb (average refresh interval per bank) in ns for 8Gb density - const u32 tREFpb = !TIMING_PRESET_SIX ? 488 : tREFpb_values[TIMING_PRESET_SIX-1]; - // tREFab (average refresh interval all 8 banks) in ns for 8Gb density - // const u32 tREFab = tREFpb * 8; - - // tPDEX2WR, tPDEX2RD (timing delay from exiting powerdown mode to a write/read command) in ns - // const u32 tPDEX2 = 10; - // Exit power-down to next valid command delay - const double tXP = 10; - - // Delay from valid command to CKE input LOW in ns - const double tCMDCKE = 1.75; - - // tACT2PDEN (timing delay from an activate, MRS or EMRS command to power-down entry) in ns - // Valid clock and CS requirement after CKE input LOW after MRW command - const u32 tMRWCKEL = 14; - - // Valid CS requirement after CKE input LOW - const double tCKELCS = 5; - - // Valid CS requirement before CKE input HIGH - const double tCSCKEH = 1.75; - - // tXSR (SELF REFRESH exit to next valid command delay) in ns - const double tXSR = tRFCab + 7.5; - - // tCKE (minimum pulse width(HIGH and LOW pulse width)) in ns - const double tCKE = 7.5; - - // Minimum self refresh time (entry to exit) - const u32 tSR = 15; - - // tFAW (Four-bank Activate Window) in ns - const u32 tFAW = !TIMING_PRESET_TWO ? 40 : tFAW_values[TIMING_PRESET_TWO-1]; - - // Valid Clock requirement before CKE Input HIGH in ns - const double tCKCKEH = 1.75; - - // p78 The first valid data is available RL × t CK + t DQSCK + t DQSQ - //const u32 QUSE = RL + CEIL(tDQSCK_min/tCK_avg + tDQSQ); - - namespace pcv::erista { - // tCK_avg (average clock period) in ns - const double tCK_avg = 1000'000. / C.eristaEmcMaxClock; - - // Write Latency - const u32 WL = 14 - 2*TIMING_PRESET_SEVEN; - // Read Latency - const u32 RL = 32 - 4*TIMING_PRESET_SEVEN; - - // 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; - - // Delay Time From WRITE-to-READ - const u32 W2R = WL + BL/2 + 1 + CEIL(tWTR/tCK_avg) - 6; - - // write-to-precharge time for commands to the same bank in cycles - const u32 WTP = WL + BL/2 + 1 + CEIL(tWR/tCK_avg) - 8; - - // #_of_rows per die for 8Gb density - const u32 numOfRows = 65536; - // {REFRESH, REFRESH_LO} = max[(tREF/#_of_rows) / (emc_clk_period) - 64, (tREF/#_of_rows) / (emc_clk_period) * 97%] - // emc_clk_period = dram_clk / 2; - // 1600 MHz: 5894, but N' set to 6176 (~4.8% margin) - const u32 REFRESH = 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); - - // Write With Auto Precharge to to Power-Down Entry - const u32 WTPDEN = WTP + 1 + CEIL(tDQSS_max/tCK_avg) + CEIL(tDQS2DQ_max/tCK_avg) + 6; - - // Additional time after t XP hasexpired until the MRR commandmay be issued - const double tMRRI = tRCD + 3 * tCK_avg; - - // tPDEX2MRR (timing delay from exiting powerdown mode to MRR command) in ns - const double tPDEX2MRR = tXP + tMRRI; - } - namespace pcv::mariko { - // tCK_avg (average clock period) in ns - const double tCK_avg = 1000'000. / C.marikoEmcMaxClock; - // Write Latency - const u32 WL = 14 - 2*TIMING_PRESET_SEVEN; - // Read Latency - const u32 RL = 32 - 4*TIMING_PRESET_SEVEN; - - // 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)); - - // Delay Time From WRITE-to-READ - const u32 W2R = WL + BL/2 + 1 + CEIL(tWTR/tCK_avg); - - // write-to-precharge time for commands to the same bank in cycles - const u32 WTP = WL + BL/2 + 1 + CEIL(tWR/tCK_avg); - - // Read-To-MRW delay - const u32 RTM = RL + BL/2 + CEIL(tDQSCK_max/tCK_avg) + FLOOR(tRPST) + CEIL(7.5/tCK_avg); - - // Write-To-MRW/MRR delay - const u32 WTM = WL + 1 + BL/2 + CEIL(7.5/tCK_avg); - - // Read With AP-To-MRW/MRR delay - const u32 RATM = RTM + CEIL(tRTP/tCK_avg) - 8; - - // Write With AP-To-MRW/MRR delay - const u32 WATM = WTM + CEIL(tWR/tCK_avg); - - // #_of_rows per die for 8Gb density - const u32 numOfRows = 65536; - // {REFRESH, REFRESH_LO} = max[(tREF/#_of_rows) / (emc_clk_period) - 64, (tREF/#_of_rows) / (emc_clk_period) * 97%] - // emc_clk_period = dram_clk / 2; - // 1600 MHz: 5894, but N' set to 6176 (~4.8% margin) - const u32 REFRESH = 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); - - // Write With Auto Precharge to to Power-Down Entry - const u32 WTPDEN = WTP + 1 + CEIL(tDQSS_max/tCK_avg) + CEIL(tDQS2DQ_max/tCK_avg) + 6; - - // Additional time after t XP hasexpired until the MRR commandmay be issued - const double tMRRI = tRCD + 3 * tCK_avg; - - // tPDEX2MRR (timing delay from exiting powerdown mode to MRR command) in ns - const double tPDEX2MRR = tXP + tMRRI; - } - } - \ No newline at end of file +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) + // Burst Length + const u32 BL = 16; + + // tRFCab (refresh cycle time all banks) in ns for 8Gb density + const u32 tRFCab = 2*C.tRFCpb; + + // C.tRAS (row active time) in ns + // C.tRPpb (row precharge time per bank) in ns + // tRPab (row precharge time all banks) in ns + const u32 tRPab = C.tRPpb + 3; + + // tRC (ACTIVATE-ACTIVATE command period same bank) in ns + const u32 tRC = C.tRPpb + C.tRAS; + + // 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; + // WRITE command to first DQS transition(max) (tCK) + const double tDQSS_max = 1.25; + // DQ-to-DQS offset(max) (ns) + const double tDQS2DQ_max = 0.8; + // DQS_t, DQS_c to DQ skew total, per group, per access (DBI Disabled) + const double tDQSQ = 0.18; + + // Write-to-Read delay + + // Internal READ-to-PRE-CHARGE command delay in ns + const double tRTP = 7.5; + + // write recovery time + const u32 tWR = 18; + + // Read to refresh delay + const u32 tR2REF = tRTP + C.tRPpb; + + // C.tRCD (RAS-CAS delay) in ns + + // C.tRRD (Active bank-A to Active bank-B) in ns + + // C.tREFpb (average refresh interval per bank) in ns for 8Gb density + // tREFab (average refresh interval all 8 banks) in ns for 8Gb density + // const u32 tREFab = C.tREFpb * 8; + + // tPDEX2WR, tPDEX2RD (timing delay from exiting powerdown mode to a write/read command) in ns + // const u32 tPDEX2 = 10; + // Exit power-down to next valid command delay + const double tXP = 10; + + // Delay from valid command to CKE input LOW in ns + const double tCMDCKE = 1.75; + + // tACT2PDEN (timing delay from an activate, MRS or EMRS command to power-down entry) in ns + // Valid clock and CS requirement after CKE input LOW after MRW command + const u32 tMRWCKEL = 14; + + // Valid CS requirement after CKE input LOW + const double tCKELCS = 5; + + // Valid CS requirement before CKE input HIGH + const double tCSCKEH = 1.75; + + // tXSR (SELF REFRESH exit to next valid command delay) in ns + const double tXSR = tRFCab + 7.5; + + // tCKE (minimum pulse width(HIGH and LOW pulse width)) in ns + const double tCKE = 7.5; + + // Minimum self refresh time (entry to exit) + const u32 tSR = 15; + + // tFAW (Four-bank Activate Window) in ns + const u32 tFAW = 40; + + // Valid Clock requirement before CKE Input HIGH in ns + const double tCKCKEH = 1.75; + + // p78 The first valid data is available RL × t CK + t DQSCK + t DQSQ + //const u32 QUSE = RL + CEIL(tDQSCK_min/tCK_avg + tDQSQ); + + namespace pcv::erista { + // tCK_avg (average clock period) in ns + const double tCK_avg = 1000'000. / C.eristaEmcMaxClock; + + // Write Latency + const u32 WL = 14; + // Read Latency + const u32 RL = 32; + + // minimum number of cycles from any read command to any write command, irrespective of bank + // Delay Time From WRITE-to-READ + const u32 W2R = WL + BL/2 + 1 + CEIL(C.tWTR/tCK_avg) - 6; + + // write-to-precharge time for commands to the same bank in cycles + const u32 WTP = WL + BL/2 + 1 + CEIL(tWR/tCK_avg) - 8; + + // #_of_rows per die for 8Gb density + const u32 numOfRows = 65536; + // {REFRESH, REFRESH_LO} = max[(tREF/#_of_rows) / (emc_clk_period) - 64, (tREF/#_of_rows) / (emc_clk_period) * 97%] + // emc_clk_period = dram_clk / 2; + // 1600 MHz: 5894, but N' set to 6176 (~4.8% margin) + const u32 REFRESH = MIN((u32)65472, u32(std::ceil((double(C.tREFpb) * C.eristaEmcMaxClock / numOfRows * 1.048 / 2 - 64))) / 4 * 4); + const u32 REFBW = MIN((u32)65536, REFRESH+64); + + // Write With Auto Precharge to to Power-Down Entry + const u32 WTPDEN = WTP + 1 + CEIL(tDQSS_max/tCK_avg) + CEIL(tDQS2DQ_max/tCK_avg) + 6; + + // Additional time after t XP hasexpired until the MRR commandmay be issued + const double tMRRI = C.tRCD + 3 * tCK_avg; + + // tPDEX2MRR (timing delay from exiting powerdown mode to MRR command) in ns + const double tPDEX2MRR = tXP + tMRRI; + } + namespace pcv::mariko { + // tCK_avg (average clock period) in ns + const double tCK_avg = 1000'000. / C.marikoEmcMaxClock; + // Write Latency + const u32 WL = 14; + // Read Latency + const u32 RL = 32; + + // minimum number of cycles from any read command to any write command, irrespective of bank + + // Delay Time From WRITE-to-READ + const u32 W2R = WL + BL/2 + 1 + CEIL(C.tWTR/tCK_avg); + + // write-to-precharge time for commands to the same bank in cycles + const u32 WTP = WL + BL/2 + 1 + CEIL(tWR/tCK_avg); + + // Read-To-MRW delay + const u32 RTM = RL + BL/2 + CEIL(tDQSCK_max/tCK_avg) + FLOOR(tRPST) + CEIL(7.5/tCK_avg); + + // Write-To-MRW/MRR delay + const u32 WTM = WL + 1 + BL/2 + CEIL(7.5/tCK_avg); + + // Read With AP-To-MRW/MRR delay + const u32 RATM = RTM + CEIL(tRTP/tCK_avg) - 8; + + // Write With AP-To-MRW/MRR delay + const u32 WATM = WTM + CEIL(tWR/tCK_avg); + + // #_of_rows per die for 8Gb density + const u32 numOfRows = 65536; + // {REFRESH, REFRESH_LO} = max[(tREF/#_of_rows) / (emc_clk_period) - 64, (tREF/#_of_rows) / (emc_clk_period) * 97%] + // emc_clk_period = dram_clk / 2; + // 1600 MHz: 5894, but N' set to 6176 (~4.8% margin) + const u32 REFRESH = MIN((u32)65472, u32(std::ceil((double(C.tREFpb) * C.marikoEmcMaxClock / numOfRows * 1.048 / 2 - 64))) / 4 * 4); + const u32 REFBW = MIN((u32)65536, REFRESH+64); + + // Write With Auto Precharge to to Power-Down Entry + const u32 WTPDEN = WTP + 1 + CEIL(tDQSS_max/tCK_avg) + CEIL(tDQS2DQ_max/tCK_avg) + 6; + + // Additional time after t XP hasexpired until the MRR commandmay be issued + const double tMRRI = C.tRCD + 3 * tCK_avg; + + // tPDEX2MRR (timing delay from exiting powerdown mode to MRR command) in ns + const double tPDEX2MRR = tXP + tMRRI; + } +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_test.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_test.cpp index c0cb1c75..834ce058 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_test.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_test.cpp @@ -77,13 +77,13 @@ Result Test_PcvDvfsTable() { // Customized table default assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.eristaCpuDvfsTable)) == 19); - assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoCpuDvfsTable)) == 21); - assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoCpuDvfsTableSLT)) == 22); + assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoCpuDvfsTable)) == 22); + assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoCpuDvfsTableSLT)) == 25); assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.eristaGpuDvfsTable)) == 12); - assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoGpuDvfsTable)) == 17); - assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoGpuDvfsTableSLT)) == 17); - assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoGpuDvfsTableHiOPT)) == 17); + assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoGpuDvfsTable)) == 18); + assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoGpuDvfsTableSLT)) == 18); + assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoGpuDvfsTableHiOPT)) == 18); constexpr size_t limit = ams::ldr::oc::pcv::DvfsTableEntryLimit; cvb_entry_t customized_table[limit] = {}; diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp index c79a247f..59cf3ba2 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp @@ -1,8 +1,6 @@ /* * Copyright (C) Switch-OC-Suite * - * Copyright (c) 2023 hanai3Bi - * * 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. @@ -114,11 +112,11 @@ void SafetyCheck() { sValidator validators[] = { { C.commonCpuBoostClock, 1020'000, 3000'000, true }, - { C.commonEmcMemVolt, 1100'000, 1250'000 }, + { C.commonEmcMemVolt, 1000'000, 1350'000 }, { C.eristaCpuMaxVolt, 1100, 1300 }, { C.eristaEmcMaxClock, 1600'000, 2131'200 }, - { C.marikoCpuMaxVolt, 1100, 1300 }, - { C.marikoEmcMaxClock, 1600'000, 2800'000 }, + { C.marikoCpuMaxVolt, 800, 1160 }, + { C.marikoEmcMaxClock, 1600'000, 3200'000 }, { C.marikoEmcVddqVolt, 550'000, 650'000 }, { eristaCpuDvfsMaxFreq, 1785'000, 3000'000 }, { marikoCpuDvfsMaxFreq, 1785'000, 3000'000 }, @@ -143,4 +141,4 @@ void Patch(uintptr_t mapped_nso, size_t nso_size) { #endif } -} \ No newline at end of file +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp index 47a0a940..d5fef376 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp @@ -1,8 +1,6 @@ /* * Copyright (C) Switch-OC-Suite * - * Copyright (c) 2023 hanai3Bi - * * 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. 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 73310beb..ac08f8d7 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp @@ -1,8 +1,6 @@ /* * Copyright (C) Switch-OC-Suite * - * Copyright (c) 2023 hanai3Bi - * * 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. @@ -87,16 +85,16 @@ void MemMtcTableAutoAdjust(EristaMtcTable* table) { 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_r2w, R2W); + WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(C.tRFCpb)); + WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(C.tRAS)); + WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(C.tRPpb)); + WRITE_PARAM_ALL_REG(table, emcR2W, C.R2W); WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); 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_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_rd_rcd, GET_CYCLE_CEIL(C.tRCD)); + WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(C.tRCD)); + WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(C.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)); @@ -123,19 +121,19 @@ void MemMtcTableAutoAdjust(EristaMtcTable* table) { 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_rcd = CEIL(GET_CYCLE_CEIL(C.tRCD) / MC_ARB_DIV) - 2; + table->burst_mc_regs.mc_emem_arb_timing_rp = CEIL(GET_CYCLE_CEIL(C.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_ras = CEIL(GET_CYCLE_CEIL(C.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_rrd = CEIL(GET_CYCLE_CEIL(C.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_timingR2W = CEIL(C.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_rfcpb = CEIL(GET_CYCLE_CEIL(C.tRFCpb) / MC_ARB_DIV); //table->burst_mc_regs.mc_emem_arb_timing_ccdmw = CEIL(tCCDMW / MC_ARB_DIV) -1 + MC_ARB_SFA; } @@ -146,72 +144,58 @@ void MemMtcTableCustomAdjust(EristaMtcTable* table) { constexpr u32 MC_ARB_DIV = 4; constexpr u32 MC_ARB_SFA = 2; - if (TIMING_PRESET_ONE) { - WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(tRC)); - 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_trpab, GET_CYCLE_CEIL(tRPab)); - 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_pdex2mrr, GET_CYCLE_CEIL(tPDEX2MRR)); + WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(tRC)); + WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(C.tRAS)); + WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(C.tRPpb)); + WRITE_PARAM_ALL_REG(table, emc_trpab, GET_CYCLE_CEIL(tRPab)); + WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(C.tRCD)); + WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(C.tRCD)); + WRITE_PARAM_ALL_REG(table, emc_pdex2mrr, GET_CYCLE_CEIL(tPDEX2MRR)); - 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_rc = CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV - 1); - 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_ras = CEIL(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV - 2); - } + table->burst_mc_regs.mc_emem_arb_timing_rcd = CEIL(GET_CYCLE_CEIL(C.tRCD) / MC_ARB_DIV - 2); + 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_rp = CEIL(GET_CYCLE_CEIL(C.tRPpb) / MC_ARB_DIV - 1 + MC_ARB_SFA); + table->burst_mc_regs.mc_emem_arb_timing_ras = CEIL(GET_CYCLE_CEIL(C.tRAS) / MC_ARB_DIV - 2); - if (TIMING_PRESET_TWO) { - WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW)); - WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD)); + WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW)); + WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(C.tRRD)); - 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_faw = CEIL(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV) - 1; + table->burst_mc_regs.mc_emem_arb_timing_rrd = CEIL(GET_CYCLE_CEIL(C.tRRD) / MC_ARB_DIV) - 1; - if (TIMING_PRESET_THREE) { - 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_rw2pden, WTPDEN); + 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_rw2pden, WTPDEN); - 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_rap2pre = CEIL(GET_CYCLE_CEIL(tRTP) / MC_ARB_DIV); + table->burst_mc_regs.mc_emem_arb_timing_wap2pre = CEIL(WTP / MC_ARB_DIV); - if (TIMING_PRESET_FOUR) { - 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_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_rfc, GET_CYCLE_CEIL(tRFCab)); + WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(C.tRFCpb)); + 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)); - 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_rfcpb = CEIL(GET_CYCLE_CEIL(C.tRFCpb) / MC_ARB_DIV); - if (TIMING_PRESET_FIVE) { - WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); + WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); - 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_w2r = CEIL(W2R / MC_ARB_DIV) - 1 + MC_ARB_SFA; - if (TIMING_PRESET_SIX) { - 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_trefbw, REFBW); - } + 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_trefbw, REFBW); - if (TIMING_PRESET_SEVEN) { - 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_rw2pden, WTPDEN); - - table->burst_mc_regs.mc_emem_arb_timing_wap2pre = CEIL(WTP / MC_ARB_DIV); - 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; - } + WRITE_PARAM_ALL_REG(table, emcR2W, C.R2W); + WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); + WRITE_PARAM_ALL_REG(table, emc_w2p, WTP); + WRITE_PARAM_ALL_REG(table, emc_rw2pden, WTPDEN); + + table->burst_mc_regs.mc_emem_arb_timing_wap2pre = CEIL(WTP / MC_ARB_DIV); + table->burst_mc_regs.mc_emem_arb_timingR2W = CEIL(C.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; u32 DA_TURNS = 0; - DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_r2w / 2) << 16; //R2W TURN + DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timingR2W / 2) << 16; //C.R2W TURN DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_w2r / 2) << 24; //W2R TURN WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_da_turns, DA_TURNS); u32 DA_COVERS = 0; 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 00e3b245..404cd0f9 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp @@ -1,8 +1,6 @@ /* * Copyright (C) Switch-OC-Suite * - * Copyright (c) 2023 hanai3Bi - * * 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. @@ -74,12 +72,16 @@ Result CpuVoltDfll(u32* ptr) { if (C.marikoCpuUV) { if (C.marikoCpuUV == 1) { PATCH_OFFSET(&(entry->tune0_low), 0x0000FF90); //process_id 0 - } else if (C.marikoCpuUV == 2) { + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x00000000); + } + else if (C.marikoCpuUV == 2) { PATCH_OFFSET(&(entry->tune0_low), 0x0000FFA0); //process_id 1 + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x00000000); } - PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); - PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); - PATCH_OFFSET(&(entry->tune1_high), 0x00000000); } R_SUCCEED(); @@ -184,10 +186,10 @@ void MemMtcTableAutoAdjust(MarikoMtcTable* table, const MarikoMtcTable* ref) { 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_r2w, R2W); + WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(C.tRFCpb)); + WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(C.tRAS)); + WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(C.tRPpb)); + WRITE_PARAM_ALL_REG(table, emcR2W, C.R2W); WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); WRITE_PARAM_ALL_REG(table, emc_r2p, GET_CYCLE_CEIL(tRTP)); WRITE_PARAM_ALL_REG(table, emc_w2p, WTP); @@ -196,9 +198,9 @@ void MemMtcTableAutoAdjust(MarikoMtcTable* table, const MarikoMtcTable* ref) { WRITE_PARAM_ALL_REG(table, emc_tratm, RATM); WRITE_PARAM_ALL_REG(table, emc_twatm, WATM); //WRITE_PARAM_ALL_REG(table, emc_tr2ref, GET_CYCLE_CEIL(tR2REF)); - 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_rd_rcd, GET_CYCLE_CEIL(C.tRCD)); + WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(C.tRCD)); + WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(C.tRRD)); WRITE_PARAM_ALL_REG(table, emc_rext, 26); WRITE_PARAM_ALL_REG(table, emc_refresh, REFRESH); WRITE_PARAM_ALL_REG(table, emc_pre_refresh_req_cnt, REFRESH / 4); @@ -238,21 +240,21 @@ void MemMtcTableAutoAdjust(MarikoMtcTable* table, const MarikoMtcTable* ref) { 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_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_rcd, CEIL(GET_CYCLE_CEIL(C.tRCD) / MC_ARB_DIV) - 2) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rp, CEIL(GET_CYCLE_CEIL(C.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) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_ras, CEIL(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV) - 2) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_ras, CEIL(GET_CYCLE_CEIL(C.tRAS) / MC_ARB_DIV) - 2) WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_faw, CEIL(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV) - 1) - 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_rrd, CEIL(GET_CYCLE_CEIL(C.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_timingR2W, CEIL((C.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)) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rfcpb, CEIL(GET_CYCLE_CEIL(C.tRFCpb) / MC_ARB_DIV)) u32 DA_TURNS = 0; - DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_r2w / 2) << 16; //R2W TURN + DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timingR2W / 2) << 16; //C.R2W TURN DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_w2r / 2) << 24; //W2R TURN WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_da_turns, DA_TURNS); u32 DA_COVERS = 0; @@ -318,7 +320,7 @@ void MemMtcTableAutoAdjust(MarikoMtcTable* table, const MarikoMtcTable* ref) { table->pllmb_ss_ctrl1 = 0x0b55fe01; table->pllmb_ss_ctrl2 = 0x10170b55; - table->dram_timings.t_rp = tRPpb; + table->dram_timings.t_rp = C.tRPpb; table->dram_timings.t_rfc = tRFCab; //table->dram_timings.rl = 32; @@ -332,79 +334,64 @@ void MemMtcTableCustomAdjust(MarikoMtcTable* table) { constexpr u32 MC_ARB_DIV = 4; constexpr u32 MC_ARB_SFA = 2; - if (TIMING_PRESET_ONE) { - WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(tRC)); - 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_trpab, GET_CYCLE_CEIL(tRPab)); - 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_pdex2mrr,GET_CYCLE_CEIL(tPDEX2MRR)); + WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(tRC)); + WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(C.tRAS)); + WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(C.tRPpb)); + WRITE_PARAM_ALL_REG(table, emc_trpab, GET_CYCLE_CEIL(tRPab)); + WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(C.tRCD)); + WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(C.tRCD)); + WRITE_PARAM_ALL_REG(table, emc_pdex2mrr,GET_CYCLE_CEIL(tPDEX2MRR)); - 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_rc = CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV) - 1; - 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_ras = CEIL(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV) - 2; + table->burst_mc_regs.mc_emem_arb_timing_rcd = CEIL(GET_CYCLE_CEIL(C.tRCD) / MC_ARB_DIV) - 2; + 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_rp = CEIL(GET_CYCLE_CEIL(C.tRPpb) / MC_ARB_DIV) - 1 + MC_ARB_SFA; + table->burst_mc_regs.mc_emem_arb_timing_ras = CEIL(GET_CYCLE_CEIL(C.tRAS) / MC_ARB_DIV) - 2; - } - - if (TIMING_PRESET_TWO) { - WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW)); - WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD)); + WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW)); + WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(C.tRRD)); - 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_faw = CEIL(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV) - 1; + table->burst_mc_regs.mc_emem_arb_timing_rrd = CEIL(GET_CYCLE_CEIL(C.tRRD) / MC_ARB_DIV) - 1; - if (TIMING_PRESET_THREE) { - 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_tratm, RATM); - WRITE_PARAM_ALL_REG(table, emc_twatm, WATM); - WRITE_PARAM_ALL_REG(table, emc_rw2pden, WTPDEN); + 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_tratm, RATM); + WRITE_PARAM_ALL_REG(table, emc_twatm, WATM); + WRITE_PARAM_ALL_REG(table, emc_rw2pden, WTPDEN); - 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_rap2pre = CEIL(GET_CYCLE_CEIL(tRTP) / MC_ARB_DIV); + table->burst_mc_regs.mc_emem_arb_timing_wap2pre = CEIL(WTP / MC_ARB_DIV); - if (TIMING_PRESET_FOUR) { - 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_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_rfc, GET_CYCLE_CEIL(tRFCab)); + WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(C.tRFCpb)); + 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)); - 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_rfcpb = CEIL(GET_CYCLE_CEIL(C.tRFCpb) / MC_ARB_DIV); - if (TIMING_PRESET_FIVE) { - WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); + WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); - 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_w2r = CEIL(W2R / MC_ARB_DIV) - 1 + MC_ARB_SFA; - if (TIMING_PRESET_SIX) { - 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_trefbw, REFBW); - } + 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_trefbw, REFBW); - if (TIMING_PRESET_SEVEN) { - 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_trtm, RTM); - WRITE_PARAM_ALL_REG(table, emc_twtm, WTM); - WRITE_PARAM_ALL_REG(table, emc_tratm, RATM); - WRITE_PARAM_ALL_REG(table, emc_twatm, WATM); - WRITE_PARAM_ALL_REG(table, emc_rw2pden, WTPDEN); + WRITE_PARAM_ALL_REG(table, emcR2W, C.R2W); + WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); + WRITE_PARAM_ALL_REG(table, emc_w2p, WTP); + WRITE_PARAM_ALL_REG(table, emc_trtm, RTM); + WRITE_PARAM_ALL_REG(table, emc_twtm, WTM); + WRITE_PARAM_ALL_REG(table, emc_tratm, RATM); + WRITE_PARAM_ALL_REG(table, emc_twatm, WATM); + WRITE_PARAM_ALL_REG(table, emc_rw2pden, WTPDEN); - table->burst_mc_regs.mc_emem_arb_timing_wap2pre = CEIL(WTP / MC_ARB_DIV); - 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_wap2pre = CEIL(WTP / MC_ARB_DIV); + table->burst_mc_regs.mc_emem_arb_timingR2W = CEIL(C.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; u32 DA_TURNS = 0; - DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_r2w / 2) << 16; //R2W TURN + DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timingR2W / 2) << 16; //C.R2W TURN DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_w2r / 2) << 24; //W2R TURN WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_da_turns, DA_TURNS); u32 DA_COVERS = 0; @@ -626,4 +613,4 @@ void Patch(uintptr_t mapped_nso, size_t nso_size) { } } -} \ No newline at end of file +} diff --git a/Source/Atmosphere/stratosphere/loader/source/patch.py b/Source/Atmosphere/stratosphere/loader/source/patch.py new file mode 100644 index 00000000..7ea5d7c1 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/patch.py @@ -0,0 +1,74 @@ +#!python3 + +import sys +import os + +def file_replace_str(file_path, search_replace_list): + assert file_path + assert search_replace_list + with open(file_path, "r") as f: + content = f.read() + + for entry in search_replace_list: + (search, replace) = entry + if search in content: + content = content.replace(search, replace) + else: + assert replace in content, f"Pattern \"{search}\" not found" + + with open(file_path, "w") as f: + f.write(content) + + +dir_path = os.path.dirname(__file__) + +os.chdir(dir_path) +os.system("git reset --hard") + +ldr_process_creation = os.path.join(dir_path, "ldr_process_creation.cpp") +file_replace_str(ldr_process_creation, + [("""#include "ldr_ro_manager.hpp" + +namespace ams::ldr {""", +"""#include "ldr_ro_manager.hpp" +#include "oc/oc_loader.hpp" + +namespace ams::ldr {"""), + (""" NsoHeader g_nso_headers[Nso_Count]; + + Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {""", + """ NsoHeader g_nso_headers[Nso_Count]; + + /* Pcv/Ptm check cache. */ + bool g_is_pcv; + bool g_is_ptm; + + Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {"""), + (""" R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId()); + + /* Validate the kernel capabilities. */""", + """ R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId()); + + /* Check if nca is pcv or ptm */ + g_is_pcv = meta->aci->program_id == ncm::SystemProgramId::Pcv; + g_is_ptm = meta->aci->program_id == ncm::SystemProgramId::Ptm; + + /* Validate the kernel capabilities. */"""), + (""" LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size); + }""", + """ LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size); + + /* Apply pcv and ptm patches. */ + if (g_is_pcv) + oc::pcv::Patch(map_address, nso_size); + if (g_is_ptm) + oc::ptm::Patch(map_address, nso_size); + }""")]) + +ldr_meta = os.path.join(dir_path, "ldr_meta.cpp") +file_replace_str(ldr_meta, + [(""" Result ValidateAcidSignature(Meta *meta) { + /* Loader did not check signatures prior to 10.0.0. */""", + """ Result ValidateAcidSignature(Meta *meta) { + R_SUCCEED(); + /* Loader did not check signatures prior to 10.0.0. */""")]) diff --git a/Source/ReverseNX-RT.diff b/Source/ReverseNX-RT.diff new file mode 100644 index 00000000..626ae888 --- /dev/null +++ b/Source/ReverseNX-RT.diff @@ -0,0 +1,287 @@ +diff --git a/Overlay/Makefile b/Overlay/Makefile +index 9656834..3b2ebd5 100644 +--- a/Overlay/Makefile ++++ b/Overlay/Makefile +@@ -38,7 +38,7 @@ include $(DEVKITPRO)/libnx/switch_rules + # NACP building is skipped as well. + #--------------------------------------------------------------------------------- + APP_TITLE := ReverseNX-RT +-APP_VERSION := 1.1.1 ++APP_VERSION := 1.1.1-OC + + TARGET := ReverseNX-RT-ovl + BUILD := build +diff --git a/Overlay/source/main.cpp b/Overlay/source/main.cpp +index 810295c..2b3aa52 100644 +--- a/Overlay/source/main.cpp ++++ b/Overlay/source/main.cpp +@@ -1,7 +1,202 @@ + #define TESLA_INIT_IMPL // If you have more than one file using the tesla header, only define this in the main one + #include // The Tesla Header ++#include + #include "SaltyNX.h" + ++class ModeSync { ++public: ++ enum ReverseNXMode { ++ ReverseNX_NotValid = 0, ++ ReverseNX_SystemDefault = 0, ++ ReverseNX_Handheld, ++ ReverseNX_Docked, ++ ReverseNX_Undefined, ++ }; ++ ++ void SetMode(bool isDefault, bool setDock, char* descBuf, size_t bufSize) { ++ auto changeHandler = [this](ReverseNXMode newMode) { ++ if (this->currentMode == ReverseNX_Undefined || this->currentMode != newMode) { ++ if (R_FAILED(this->ipc->SetMode(newMode))) { ++ this->ipc->ipcStatus = Ipc::IpcStatus_ConnectFailed; ++ return; ++ } ++ this->currentMode = newMode; ++ } ++ }; ++ ++ tsl::hlp::ScopeGuard updateBufGuard([&] { this->ipc->UpdateStatusDesc(descBuf, bufSize); }); ++ ++ if (isDefault) { ++ changeHandler(ReverseNX_SystemDefault); ++ return; ++ } ++ ++ changeHandler(setDock ? ReverseNX_Docked : ReverseNX_Handheld); ++ } ++ ++ ModeSync() { ++ this->ipc = new Ipc; ++ this->ipc->Init(); ++ } ++ ++ ~ModeSync() { ++ this->ipc->Exit(); ++ delete this->ipc; ++ } ++ ++protected: ++ struct Ipc { ++ #define API_VER 2 ++ #define SERVICE_NAME "sysclkOC" ++ ++ enum SysClkIpcCmd { ++ SysClkIpcCmd_GetApiVersion = 0, ++ SysClkIpcCmd_GetConfigValues = 9, ++ SysClkIpcCmd_SetReverseNXRTMode = 11, ++ }; ++ ++ enum SysClkConfigValue { ++ SysClkConfigValue_SyncReverseNXMode = 4, ++ SysClkConfigValue_EnumMax = 8, ++ }; ++ ++ struct SysClkConfigValueList { ++ uint64_t values[SysClkConfigValue_EnumMax]; ++ }; ++ ++ enum IpcStatus { ++ IpcStatus_OK, ++ ++ IpcStatus_Unknown, ++ IpcStatus_NotRunning, ++ IpcStatus_InitFailed, ++ IpcStatus_ConnectFailed, ++ IpcStatus_UnsupportedVer, ++ ++ IpcStatus_Count, ++ }; ++ ++ static constexpr const char* IpcStatusStr[IpcStatus_Count] = { ++ "", ++ ++ "Unknown", ++ "Err: Not running", ++ "Err: Failed to init", ++ "Err: Failed to connect", ++ "Err: Unsupported version", ++ }; ++ ++ Result Init() { ++ Result rc = 0; ++ ++ rc = IpcInitialize(); ++ ++ rc = GetStatus(); ++ ++ return rc; ++ } ++ ++ void Exit() { ++ if (--refCnt == 0) ++ serviceClose(&service); ++ } ++ ++ bool IsServiceRunning() { ++ Handle handle; ++ SmServiceName name = smEncodeName(SERVICE_NAME); ++ if (R_FAILED(smRegisterService(&handle, name, false, 1))) { ++ return true; ++ } ++ svcCloseHandle(handle); ++ smUnregisterService(name); ++ return false; ++ } ++ ++ Result IpcInitialize(void) { ++ Result rc = 0; ++ refCnt++; ++ ++ if (serviceIsActive(&service)) ++ return 0; ++ ++ rc = smGetService(&service, SERVICE_NAME); ++ ++ if (R_FAILED(rc)) { ++ this->ipcStatus = IpcStatus_InitFailed; ++ rc = this->ipcStatus; ++ this->Exit(); ++ return rc; ++ } ++ ++ return rc; ++ } ++ ++ Result GetApiVersion(u32* outVer) { ++ return serviceDispatchOut(&service, SysClkIpcCmd_GetApiVersion, *outVer); ++ } ++ ++ Result GetConfigValues(SysClkConfigValueList* outConfigValues) { ++ return serviceDispatchOut(&service, SysClkIpcCmd_GetConfigValues, *outConfigValues); ++ } ++ ++ Result SetMode(ReverseNXMode mode) { ++ return serviceDispatchIn(&service, SysClkIpcCmd_SetReverseNXRTMode, mode); ++ } ++ ++ Result GetStatus() { ++ if (!IsServiceRunning()) { ++ this->ipcStatus = IpcStatus_NotRunning; ++ return this->ipcStatus; ++ } ++ ++ tsl::hlp::ScopeGuard exitSrvGuard([&] { this->Exit(); }); ++ ++ uint32_t api_ver; ++ if (R_FAILED(GetApiVersion(&api_ver))) { ++ this->ipcStatus = IpcStatus_ConnectFailed; ++ return this->ipcStatus; ++ } ++ ++ if (api_ver != API_VER) { ++ this->ipcStatus = IpcStatus_UnsupportedVer; ++ return this->ipcStatus; ++ } ++ ++ SysClkConfigValueList* list = new SysClkConfigValueList; ++ tsl::hlp::ScopeGuard listGuard([&] { delete list; }); ++ ++ if (R_FAILED(GetConfigValues(list))) { ++ this->ipcStatus = IpcStatus_ConnectFailed; ++ return this->ipcStatus; ++ } ++ ++ exitSrvGuard.dismiss(); ++ ++ shouldSync = bool(list->values[SysClkConfigValue_SyncReverseNXMode]); ++ this->ipcStatus = IpcStatus_OK; ++ return this->ipcStatus; ++ } ++ ++ void UpdateStatusDesc(char* buffer, size_t size) { ++ snprintf(buffer, size, ++ "Mode Sync: %s%s", ++ IpcStatusStr[ipcStatus], ++ (ipcStatus == IpcStatus_OK) ? ( ++ shouldSync ? "ON" : "OFF" ++ ) : "" ++ ); ++ } ++ ++ Service service = {}; ++ std::atomic refCnt = 0; ++ IpcStatus ipcStatus = IpcStatus_Unknown; ++ bool shouldSync = false; ++ }; ++ ++ Ipc* ipc = nullptr; ++ ReverseNXMode currentMode = ReverseNX_Undefined; ++}; ++ + bool* def = 0; + bool* isDocked = 0; + bool* pluginActive = 0; +@@ -17,6 +212,7 @@ bool plugin = false; + char DockedChar[32]; + char SystemChar[32]; + char PluginChar[36]; ++char SysclkChar[0x40]; + uint64_t PID = 0; + Handle remoteSharedMemory = 1; + SharedMemory _sharedmemory = {}; +@@ -73,7 +269,7 @@ bool CheckPort () { + + class GuiTest : public tsl::Gui { + public: +- GuiTest(u8 arg1, u8 arg2, bool arg3) { } ++ GuiTest(ModeSync* p) : modeSync(p) { } + + // Called when this Gui gets loaded to create the UI + // Allocate all elements on the heap. libtesla will make sure to clean them up when not needed anymore +@@ -112,6 +308,7 @@ public: + else { + renderer->drawString(SystemChar, false, x, y+40, 20, renderer->a(0xFFFF)); + renderer->drawString(DockedChar, false, x, y+60, 20, renderer->a(0xFFFF)); ++ renderer->drawString(SysclkChar, false, x, y+80, 20, renderer->a(0xFFFF)); + } + } + }), 100); +@@ -190,6 +387,8 @@ public: + + if (_def) sprintf(SystemChar, "Controlled by system: Yes"); + else sprintf(SystemChar, "Controlled by system: No"); ++ ++ modeSync->SetMode(_def, _isDocked, SysclkChar, sizeof(SysclkChar)); + } + else i++; + } +@@ -200,6 +399,8 @@ public: + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { + return false; // Return true here to singal the inputs have been consumed + } ++ ++ ModeSync* modeSync; + }; + + class OverlayTest : public tsl::Overlay { +@@ -248,9 +449,11 @@ public: + + }); + ++ modeSync = new ModeSync; + } // Called at the start to initialize all services necessary for this Overlay + + virtual void exitServices() override { ++ delete modeSync; + shmemClose(&_sharedmemory); + fsdevUnmountDevice("sdmc"); + } // Callet at the end to clean up all services previously initialized +@@ -260,8 +463,10 @@ public: + virtual void onHide() override {} // Called before overlay wants to change from visible to invisible state + + virtual std::unique_ptr loadInitialGui() override { +- return initially(1, 2, true); // Initial Gui to load. It's possible to pass arguments to it's constructor like this ++ return initially(modeSync); // Initial Gui to load. It's possible to pass arguments to it's constructor like this + } ++ ++ ModeSync* modeSync = nullptr; + }; + + int main(int argc, char **argv) { diff --git a/Source/TinyMemBenchNX/source/main.c b/Source/TinyMemBenchNX/source/main.c index 47100705..fe9bfe63 100644 --- a/Source/TinyMemBenchNX/source/main.c +++ b/Source/TinyMemBenchNX/source/main.c @@ -903,7 +903,7 @@ int main(int argc, char* argv[]) printf("TinyMemBenchNX v0.4.11\n\ (based on tinymembench-pthread, a multi-thread fork of simple benchmark for memory throughput and latency)\n\n"); - printf("Copyright (c) 2011-2016 Siarhei Siamashka\n"); + printf("Copyright (c) 2021-2022 KazushiMe\n"); printf("Copyright (c) 2023 hanai3Bi\n"); printf("\n"); diff --git a/Source/sys-clk-OC(deprecated)/.gitignore b/Source/sys-clk-OC(deprecated)/.gitignore new file mode 100644 index 00000000..53c37a16 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/LICENSE b/Source/sys-clk-OC(deprecated)/LICENSE new file mode 100644 index 00000000..72648e0e --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/LICENSE @@ -0,0 +1,7 @@ +-------------------------------------------------------------------------- +"THE BEER-WARE LICENSE" (Revision 42): +, , +wrote this file. As long as you retain this notice you can do whatever you +want with this stuff. If you meet any of us some day, and you think this +stuff is worth it, you can buy us a beer in return. - The sys-clk authors +-------------------------------------------------------------------------- diff --git a/Source/sys-clk-OC(deprecated)/README.md b/Source/sys-clk-OC(deprecated)/README.md new file mode 100644 index 00000000..c5b48d42 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/README.md @@ -0,0 +1,224 @@ +# sys-clk-OC + +Switch sysmodule allowing you to set cpu/gpu clocks according to the running application and docked state. + + +## Clock table (MHz) + +official means HOS official, unless specified + +### CPU clocks + +* 2397 → OC max for Mariko (with CPU UV) +* 2295 → OC max for Mariko +* 2193 +* 2091 → OC max for Erista +* 1963 → official and safe max for Mariko +* 1887 +* 1785 → official boost mode, safe max for Erista +* 1683 +* 1581 +* 1428 +* 1326 +* 1224 → sdev OC +* 1122 +* 1020 → official docked & handheld +* 918 +* 816 +* 714 +* 612 + +### GPU clocks + +* 1305 → N/A +* 1267 → official max for Mariko (Tegra X1+ official max) +* 1228 → recommended max for Hiopt +* 1152 +* 1075 → recommended max for SLT +* 998 → safe max for Mariko, max for Erista (Tegra X1 official max) +* 921 → safe max for Erista +* 844 +* 768 → official docked +* 691 +* 614 → recommended Mariko max for handheld +* 537 +* 460 → max handheld +* 384 → official handheld +* 307 → official handheld +* 230 +* 153 +* 76 → boost mode + +### MEM clocks + +From Hekate Minerva module [sys_sdrammtc.c](https://github.com/CTCaer/hekate/blob/master/modules/hekate_libsys_minerva/sys_sdrammtc.c#L65) + +- 3200 → l4t and closed version max for Mariko +- 2931 +- 2665 +- 2502 → max for Mariko +- 2400 +- 2361 → l4t and closed version max for Erista +- 2131 → JEDEC. max for Erista and official max for Mariko. lpddr4(x) official max +- 2099 +- 2064 +- 1996 → OCS mariko default +- 1932 +- 1894 +- 1862 → JEDEC. official max for Erista; OCS erista default +- 1795 +- 1728 +- 1600 → official docked & official boost mode +- 1331 → JEDEC. official handheld +- 1065 +- 800 +- 665 + +## Capping + +To protect the battery from excessive strain, clocks requested from config may be capped before applying, depending on your current profile: + +### Erista (Safe) +| | Handheld | Charging (USB) | Charging (Official) | Docked | +|:-------:|:--------:|:--------------:|:-------------------:|:------:| +| **MEM** | - | - | - | - | +| **CPU** | 1785 | 1785 | 1785 | 1785 | +| **GPU** | 460 | 768 | 921 | 921 | + + +### Erista (Unsafe allowed) +| | Handheld | Charging (USB) | Charging (Official) | Docked | +|:-------:|:--------:|:--------------:|:-------------------:|:------:| +| **MEM** | - | - | - | - | +| **CPU** | 1785 | 1785 | - | - | +| **GPU** | 460 | 768 | - | - | + + +### Mariko (Safe) +| | Handheld | Charging (USB) | Charging (Official) | Docked | +|:-------:|:--------:|:--------------:|:-------------------:|:------:| +| **MEM** | - | - | - | - | +| **CPU** | 1963 | 1963 | 1963 | 1963 | +| **GPU** | 768 | 921 | 998 | 998 | + + +### Mariko (Unsafe allowed) +| | Handheld | Charging (USB) | Charging (Official) | Docked | +|:-------:|:--------:|:--------------:|:-------------------:|:------:| +| **MEM** | - | - | - | - | +| **CPU** | 1963 | 1963 | - | - | +| **GPU** | 768 | 921 | - | - | + + +## Installation + +The following instructions assumes you have a Nintendo Switch running Atmosphère, updated to at least the latest stable version. +Copy the `atmosphere`, and `switch` folders at the root of your sdcard, overwriting files if prompted. Also copy the `config` folder if you're not updating, to include default settings. + +**Note:** sys-clk-overlay requires to have [Tesla](https://gbatemp.net/threads/tesla-the-nintendo-switch-overlay-menu.557362/) installed and running + +## Relevant files + +* Config file allows one to set custom clocks per docked state and title id, described below + + `/config/sys-clk-oc/config.ini` + +* Log file where the logs are written if enabled + + `/config/sys-clk-oc/log.txt` + +* Log flag file enables log writing if file exists + + `/config/sys-clk-oc/log.flag` + +* CSV file where the title id, profile, clocks and temperatures are written if enabled + + `/config/sys-clk-oc/context.csv` + +* sys-clk overlay (accessible from anywhere by invoking the [Tesla menu](https://gbatemp.net/threads/tesla-the-nintendo-switch-overlay-menu.557362/)) + + `/switch/.overlays/sys-clk-overlay.ovl` + +* sys-clk core sysmodule + + `/atmosphere/contents/00FF0000636C6BFF/exefs.nsp` + `/atmosphere/contents/00FF0000636C6BFF/flags/boot2.flag` + +## Config + +Presets can be customized by adding them to the ini config file located at `/config/sys-clk/config.ini`, using the following template for each app + +``` +[Application Title ID] +docked_cpu= +docked_gpu= +docked_mem= +handheld_charging_cpu= +handheld_charging_gpu= +handheld_charging_mem= +handheld_charging_usb_cpu= +handheld_charging_usb_gpu= +handheld_charging_usb_mem= +handheld_charging_official_cpu= +handheld_charging_official_gpu= +handheld_charging_official_mem= +handheld_cpu= +handheld_gpu= +handheld_mem= +governor_config= +``` + +* Replace `Application Title ID` with the title id of the game/application you're interested in customizing. +A list of games title id can be found in the [Switchbrew wiki](https://switchbrew.org/wiki/Title_list/Games). +* Frequencies are expressed in mhz, and will be scaled to the nearest possible values, described in the clock table below. +* If any key is omitted, value is empty or set to 0, it will be ignored, and stock clocks will apply. +* If charging, sys-clk will look for the frequencies in that order, picking the first found + 1. Charger specific config (USB or Official) `handheld_charging_usb_X` or `handheld_charging_official_X` + 2. Non specific charging config `handheld_charging_X` + 3. Handheld config `handheld_X` + +### Example 1: Zelda BOTW + +* Overclock CPU when docked or charging + +Leads to a smoother framerate overall (ex: in the korok forest) + +``` +[01007EF00011E000] +docked_cpu=1224 +handheld_charging_cpu=1224 +handheld_mem=1600 +``` + +### Example 2: Picross + +* Underclocks on handheld to save battery + +``` +[0100BA0003EEA000] +handheld_cpu=816 +handheld_gpu=153 +``` + +### Advanced + +The `[values]` section allows you to alter timings in sys-clk, you should not need to edit any of these unless you know what you are doing. Possible values are: + +| Key | Desc | Default | +|:------------------------:|-------------------------------------------------------------------------------|:---------:| +|**allow_unsafe_freq** | Allow unsafe frequencies (CPU > 1963.5 MHz, GPU > 921.6 MHz) | OFF | +|**uncapped_clocks** | Remove CPU/GPU clock cappings | OFF | +|**temp_log_interval_ms** | Defines how often sys-clk log temperatures, in milliseconds (`0` to disable) | 0 ms | +|**csv_write_interval_ms** | Defines how often sys-clk writes to the CSV, in milliseconds (`0` to disable) | 0 ms | +|**poll_interval_ms** | Defines how fast sys-clk checks and applies profiles, in milliseconds | 500 ms | + +Only available for prior to Switch OC Suite 1.9.0 + +| Key | Desc | Default | +|:------------------------:|-------------------------------------------------------------------------------|:---------:| +|**auto_cpu_boost** | Auto-boost CPU when system Core #3 utilization ≥ 95% | OFF | +|**sync_reversenx_mode** | Sync nominal profile (mode) with ReverseNX (-Tool and -RT) | ON | +|**charging_current** | Charging current limit (100 mA - 2000 mA) | 2000 mA | +|**charging_limit_perc** | Charging limit (20% - 100%) | 100%(OFF) | +|**governor_experimental** | CPU & GPU frequency governor (Experimental) | OFF | +|**governor_handheld_only**| Use governor only on Handheld Profile | OFF | \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/build.sh b/Source/sys-clk-OC(deprecated)/build.sh new file mode 100644 index 00000000..5a49e38a --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/build.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -e + +ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +DIST_DIR="$ROOT_DIR/dist" +CORES=$(nproc --all) + +if [[ -n "$1" ]]; then + DIST_DIR="$1" +fi + +echo "DIST_DIR: $DIST_DIR" +echo "CORES: $CORES" + +echo "*** sysmodule ***" +TITLE_ID="00FF0000636C6BFF" + +pushd "$ROOT_DIR/sysmodule" +make -j$CORES +popd > /dev/null + +mkdir -p "$DIST_DIR/atmosphere/contents/$TITLE_ID/flags" +cp -vf "$ROOT_DIR/sysmodule/out/sys-clk-OC.nsp" "$DIST_DIR/atmosphere/contents/$TITLE_ID/exefs.nsp" +>"$DIST_DIR/atmosphere/contents/$TITLE_ID/flags/boot2.flag" + +echo "*** overlay ***" +pushd "$ROOT_DIR/overlay" +make -j$CORES +popd > /dev/null + +mkdir -p "$DIST_DIR/switch/.overlays" +cp -vf "$ROOT_DIR/overlay/out/sys-clk-overlay.ovl" "$DIST_DIR/switch/.overlays/sys-clk-overlay.ovl" + +echo "*** assets ***" +mkdir -p "$DIST_DIR/config/sys-clk-oc" +cp -vf "$ROOT_DIR/config.ini.template" "$DIST_DIR/config/sys-clk-oc/config.ini.template" +>"$DIST_DIR/config/sys-clk-oc/log.flag" +cp -vf "$ROOT_DIR/README.md" "$DIST_DIR/README.md" diff --git a/Source/sys-clk-OC(deprecated)/common/include/cpp_util.hpp b/Source/sys-clk-OC(deprecated)/common/include/cpp_util.hpp new file mode 100644 index 00000000..34e5621f --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/include/cpp_util.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +template +class ScopeGuard { +public: + ScopeGuard(F&& f) + : f(f), engaged(true) {}; + + ~ScopeGuard() { + if (engaged) + f(); + }; + + ScopeGuard(ScopeGuard&& rhs) + : f(std::move(rhs.f)) {}; + + void dismiss() { engaged = false; } + +private: + F f; + bool engaged; +}; + +struct MakeScopeExit { + template + ScopeGuard operator+=(F&& f) { + return ScopeGuard(std::move(f)); + }; +}; + +#define STRING_CAT2(x, y) x##y +#define STRING_CAT(x, y) STRING_CAT2(x, y) +#define SCOPE_GUARD MakeScopeExit() += [&]() __attribute__((always_inline)) +#define SCOPE_EXIT auto STRING_CAT(scope_exit_, __LINE__) = SCOPE_GUARD \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/common/include/sysclk.h b/Source/sys-clk-OC(deprecated)/common/include/sysclk.h new file mode 100644 index 00000000..d278083e --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/include/sysclk.h @@ -0,0 +1,28 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#ifdef __cplusplus +#include "cpp_util.hpp" +extern "C" { +#endif + +#include "sysclk/ipc.h" +#include "sysclk/clocks.h" +#include "sysclk/apm.h" +#include "sysclk/config.h" +#include "sysclk/errors.h" +#include "sysclk/i2c.h" +#include "sysclk/psm_ext.h" + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/common/include/sysclk/apm.h b/Source/sys-clk-OC(deprecated)/common/include/sysclk/apm.h new file mode 100644 index 00000000..694e1aff --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/include/sysclk/apm.h @@ -0,0 +1,22 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include "clocks.h" + +typedef struct { + uint32_t id; + uint32_t cpu_hz; + uint32_t gpu_hz; + uint32_t mem_hz; +} SysClkApmConfiguration; + +extern SysClkApmConfiguration sysclk_g_apm_configurations[]; \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/common/include/sysclk/client/ipc.h b/Source/sys-clk-OC(deprecated)/common/include/sysclk/client/ipc.h new file mode 100644 index 00000000..0f6c37f9 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/include/sysclk/client/ipc.h @@ -0,0 +1,50 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "types.h" +#include "../config.h" +#include "../clocks.h" +#include "../ipc.h" + +bool sysclkIpcRunning(); +Result sysclkIpcInitialize(void); +void sysclkIpcExit(void); + +Result sysclkIpcGetAPIVersion(u32* out_ver); +Result sysclkIpcGetVersionString(char* out, size_t len); +Result sysclkIpcGetCurrentContext(SysClkContext* out_context); +Result sysclkIpcGetProfileCount(u64 tid, u8* out_count); +Result sysclkIpcSetEnabled(bool enabled); +Result sysclkIpcExitCmd(); +Result sysclkIpcSetOverride(SysClkModule module, u32 hz); +Result sysclkIpcGetProfiles(u64 tid, SysClkTitleProfileList* out_profiles); +Result sysclkIpcSetProfiles(u64 tid, SysClkTitleProfileList* profiles); +Result sysclkIpcGetConfigValues(SysClkConfigValueList* out_configValues); +Result sysclkIpcSetConfigValues(SysClkConfigValueList* configValues); +Result sysclkIpcSetReverseNXRTMode(ReverseNXMode mode); +Result sysclkIpcGetFrequencyTable(SysClkModule module, SysClkProfile profile, SysClkFrequencyTable* out_table); +Result sysclkIpcGetIsMariko(bool* out_is_mariko); +Result sysclkIpcGetBatteryChargingDisabledOverride(bool* out_is_true); +Result sysclkIpcSetBatteryChargingDisabledOverride(bool toggle_true); + +static inline Result sysclkIpcRemoveOverride(SysClkModule module) +{ + return sysclkIpcSetOverride(module, 0); +} + +#ifdef __cplusplus +} +#endif diff --git a/Source/sys-clk-OC(deprecated)/common/include/sysclk/client/types.h b/Source/sys-clk-OC(deprecated)/common/include/sysclk/client/types.h new file mode 100644 index 00000000..2e67315a --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/include/sysclk/client/types.h @@ -0,0 +1,29 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#ifdef __SWITCH__ + +#include +#include + +#else + +#define R_FAILED(res) ((res) != 0) +#define R_SUCCEEDED(res) ((res) == 0) + +typedef std::uint32_t Result; +typedef std::uint32_t u32; +typedef std::int32_t s32; +typedef std::uint64_t u64; +typedef std::uint8_t u8; + +#endif diff --git a/Source/sys-clk-OC(deprecated)/common/include/sysclk/clocks.h b/Source/sys-clk-OC(deprecated)/common/include/sysclk/clocks.h new file mode 100644 index 00000000..3fc0bce6 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/include/sysclk/clocks.h @@ -0,0 +1,183 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include +#include +#include + +typedef enum +{ + SysClkProfile_Handheld = 0, + SysClkProfile_HandheldCharging, + SysClkProfile_HandheldChargingUSB, + SysClkProfile_HandheldChargingOfficial, + SysClkProfile_Docked, + SysClkProfile_EnumMax +} SysClkProfile; + +typedef enum +{ + SysClkModule_CPU = 0, + SysClkModule_GPU, + SysClkModule_MEM, + SysClkModule_EnumMax +} SysClkModule; + +typedef enum +{ + SysClkThermalSensor_SOC = 0, + SysClkThermalSensor_PCB, + SysClkThermalSensor_Skin, + SysClkThermalSensor_EnumMax +} SysClkThermalSensor; + +typedef struct +{ + uint8_t enabled; + uint64_t applicationId; + SysClkProfile profile; + uint32_t freqs[SysClkModule_EnumMax]; + uint32_t overrideFreqs[SysClkModule_EnumMax]; + uint32_t temps[SysClkThermalSensor_EnumMax]; + uint32_t perfConfId; +} SysClkContext; + +typedef enum +{ + ReverseNX_NotFound = 0, + ReverseNX_SystemDefault = 0, + ReverseNX_Handheld, + ReverseNX_Docked, +} ReverseNXMode; + +typedef struct +{ + bool systemCoreBoostCPU; + bool batteryChargingDisabledOverride; + SysClkProfile realProfile; +} SysClkOcExtra; + +#define FREQ_TABLE_MAX_ENTRY_COUNT 31 + +typedef struct +{ + uint32_t freq[FREQ_TABLE_MAX_ENTRY_COUNT]; +} SysClkFrequencyTable; + +typedef enum { + SysClkOcGovernorConfig_AllDisabled = 0, + SysClkOcGovernorConfig_CPU_Shift = 0, + SysClkOcGovernorConfig_CPUOnly = 1 << SysClkOcGovernorConfig_CPU_Shift, + SysClkOcGovernorConfig_CPU = 1 << SysClkOcGovernorConfig_CPU_Shift, + SysClkOcGovernorConfig_GPU_Shift = 1, + SysClkOcGovernorConfig_GPUOnly = 1 << SysClkOcGovernorConfig_GPU_Shift, + SysClkOcGovernorConfig_GPU = 1 << SysClkOcGovernorConfig_GPU_Shift, + SysClkOcGovernorConfig_AllEnabled = 3, + SysClkOcGovernorConfig_Default = 3, + SysClkOcGovernorConfig_Mask = 3, +} SysClkOcGovernorConfig; + +inline bool GetGovernorEnabled(SysClkOcGovernorConfig config, SysClkModule module) { + switch (module) { + case SysClkModule_CPU: + return (config >> SysClkOcGovernorConfig_CPU_Shift) & 1; + case SysClkModule_GPU: + return (config >> SysClkOcGovernorConfig_GPU_Shift) & 1; + case SysClkModule_MEM: + return false; + default: + return config != SysClkOcGovernorConfig_AllDisabled; + } +} + +inline SysClkOcGovernorConfig ToggleGovernor(SysClkOcGovernorConfig prev, SysClkModule module, bool state) { + uint8_t shift; + switch (module) { + case SysClkModule_CPU: + shift = SysClkOcGovernorConfig_CPU_Shift; + break; + case SysClkModule_GPU: + shift = SysClkOcGovernorConfig_GPU_Shift; + break; + case SysClkModule_MEM: + return prev; + default: + return state ? SysClkOcGovernorConfig_AllEnabled : SysClkOcGovernorConfig_AllDisabled; + } + return (SysClkOcGovernorConfig)((prev & ~(1 << shift)) | state << shift); +} + +typedef struct +{ + union { + uint32_t mhz[(size_t)SysClkProfile_EnumMax * (size_t)SysClkModule_EnumMax]; + uint32_t mhzMap[SysClkProfile_EnumMax][SysClkModule_EnumMax]; + }; + SysClkOcGovernorConfig governorConfig; +} SysClkTitleProfileList; + +#define SYSCLK_GLOBAL_PROFILE_TID 0xA111111111111111 + +extern uint32_t g_freq_table_mem_hz[]; +extern uint32_t g_freq_table_cpu_hz[]; +extern uint32_t g_freq_table_gpu_hz[]; + +#define SYSCLK_ENUM_VALID(n, v) ((v) < n##_EnumMax) + +static inline const char* sysclkFormatModule(SysClkModule module, bool pretty) +{ + switch(module) + { + case SysClkModule_CPU: + return pretty ? "CPU" : "cpu"; + case SysClkModule_GPU: + return pretty ? "GPU" : "gpu"; + case SysClkModule_MEM: + return pretty ? "Memory" : "mem"; + default: + return NULL; + } +} + +static inline const char* sysclkFormatThermalSensor(SysClkThermalSensor thermSensor, bool pretty) +{ + switch(thermSensor) + { + case SysClkThermalSensor_SOC: + return pretty ? "SOC" : "soc"; + case SysClkThermalSensor_PCB: + return pretty ? "PCB" : "pcb"; + case SysClkThermalSensor_Skin: + return pretty ? "Skin" : "skin"; + default: + return NULL; + } +} + +static inline const char* sysclkFormatProfile(SysClkProfile profile, bool pretty) +{ + switch(profile) + { + case SysClkProfile_Docked: + return pretty ? "Docked" : "docked"; + case SysClkProfile_Handheld: + return pretty ? "Handheld" : "handheld"; + case SysClkProfile_HandheldCharging: + return pretty ? "Charging" : "handheld_charging"; + case SysClkProfile_HandheldChargingUSB: + return pretty ? "USB Charger" : "handheld_charging_usb"; + case SysClkProfile_HandheldChargingOfficial: + return pretty ? "Official Charger" : "handheld_charging_official"; + default: + return NULL; + } +} diff --git a/Source/sys-clk-OC(deprecated)/common/include/sysclk/config.h b/Source/sys-clk-OC(deprecated)/common/include/sysclk/config.h new file mode 100644 index 00000000..e45b8fa9 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/include/sysclk/config.h @@ -0,0 +1,111 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include +#include + +const uint32_t CHARGING_CURRENT_MA_LIMIT = 2000; + +typedef enum { + SysClkConfigValue_PollingIntervalMs = 0, + SysClkConfigValue_TempLogIntervalMs, + SysClkConfigValue_CsvWriteIntervalMs, + SysClkConfigValue_AutoCPUBoost, + SysClkConfigValue_SyncReverseNXMode, + SysClkConfigValue_AllowUnsafeFrequencies, + SysClkConfigValue_ChargingCurrentLimit, + SysClkConfigValue_ChargingLimitPercentage, + SysClkConfigValue_GovernorExperimental, + SysClkConfigValue_GovernorHandheldOnly, + SysClkConfigValue_EnumMax, +} SysClkConfigValue; + +typedef struct { + uint64_t values[SysClkConfigValue_EnumMax]; +} SysClkConfigValueList; + +static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pretty) +{ + switch(val) + { + case SysClkConfigValue_PollingIntervalMs: + return pretty ? "Polling Interval (ms)" : "poll_interval_ms"; + case SysClkConfigValue_TempLogIntervalMs: + return pretty ? "Temperature logging interval (ms)" : "temp_log_interval_ms"; + case SysClkConfigValue_CsvWriteIntervalMs: + return pretty ? "CSV write interval (ms)" : "csv_write_interval_ms"; + case SysClkConfigValue_AutoCPUBoost: + return pretty ? "Auto CPU Boost" : "auto_cpu_boost"; + case SysClkConfigValue_SyncReverseNXMode: + return pretty ? "Sync ReverseNX Mode Sync" : "sync_reversenx_mode"; + case SysClkConfigValue_AllowUnsafeFrequencies: + return pretty ? "Allow Unsafe Frequencies" : "allow_unsafe_freq"; + case SysClkConfigValue_ChargingCurrentLimit: + return pretty ? "Charging Current Limit (mA)" : "charging_current"; + case SysClkConfigValue_ChargingLimitPercentage: + return pretty ? "Charging Limit (%%)" : "charging_limit_perc"; + case SysClkConfigValue_GovernorExperimental: + return pretty ? "Frequency Governor (Experimental)" : "governor_experimental"; + case SysClkConfigValue_GovernorHandheldOnly: + return pretty ? "Frequency Governor Handheld Only" : "governor_handheld_only"; + default: + return NULL; + } +} + +static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val) +{ + switch(val) + { + case SysClkConfigValue_PollingIntervalMs: + return 500ULL; + case SysClkConfigValue_TempLogIntervalMs: + case SysClkConfigValue_CsvWriteIntervalMs: + case SysClkConfigValue_AllowUnsafeFrequencies: + case SysClkConfigValue_GovernorExperimental: + case SysClkConfigValue_GovernorHandheldOnly: + case SysClkConfigValue_AutoCPUBoost: + return 0ULL; + case SysClkConfigValue_SyncReverseNXMode: + return 1ULL; + case SysClkConfigValue_ChargingCurrentLimit: + return 2000ULL; + case SysClkConfigValue_ChargingLimitPercentage: + return 100ULL; + default: + return 0ULL; + } +} + +static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t input) +{ + switch(val) + { + case SysClkConfigValue_PollingIntervalMs: + return input > 0; + case SysClkConfigValue_TempLogIntervalMs: + case SysClkConfigValue_CsvWriteIntervalMs: + return true; + case SysClkConfigValue_AutoCPUBoost: + case SysClkConfigValue_SyncReverseNXMode: + case SysClkConfigValue_AllowUnsafeFrequencies: + case SysClkConfigValue_GovernorExperimental: + case SysClkConfigValue_GovernorHandheldOnly: + return (input & 0x1) == input; + case SysClkConfigValue_ChargingCurrentLimit: + return (input >= 100 && input <= CHARGING_CURRENT_MA_LIMIT && input % 100 == 0); + case SysClkConfigValue_ChargingLimitPercentage: + return (input <= 100 && input >= 20); + default: + return false; + } +} \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/common/include/sysclk/errors.h b/Source/sys-clk-OC(deprecated)/common/include/sysclk/errors.h new file mode 100644 index 00000000..cc9130ba --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/include/sysclk/errors.h @@ -0,0 +1,22 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#define SYSCLK_ERROR_MODULE 388 +#define SYSCLK_ERROR(desc) ((SYSCLK_ERROR_MODULE & 0x1FF) | (SysClkError_##desc & 0x1FFF)<<9) + +typedef enum +{ + SysClkError_Generic = 0, + SysClkError_ConfigNotLoaded = 1, + SysClkError_ConfigSaveFailed = 2, + SysClkError_InternalFrequencyTableError = 3, +} SysClkError; diff --git a/Source/sys-clk-OC(deprecated)/common/include/sysclk/i2c.h b/Source/sys-clk-OC(deprecated)/common/include/sysclk/i2c.h new file mode 100644 index 00000000..1a85dd36 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/include/sysclk/i2c.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +// To use i2c service, sm and i2c should be intialized via smInitialize() and i2cInitialize(). + +Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val); + +Result I2cRead_OutU8(I2cDevice dev, u8 reg, u8 *out); +Result I2cRead_OutU16(I2cDevice dev, u8 reg, u16 *out); + +// Max17050 fuel gauge +float I2c_Max17050_GetBatteryCurrent(); + +const u8 MAX17050_CURRENT_REG = 0x0A; + +// Buck Converter +typedef enum I2c_BuckConverter_Reg { + I2c_Max77620_SD1VOLT_REG = 0x17, // Used for Erista DDR VDDQ+VDD2 / Mariko VDD2 + I2c_Max77621_VOLT_REG = 0x00, + I2c_Max77812_CPUVOLT_REG = 0x26, + I2c_Max77812_GPUVOLT_REG = 0x23, + I2c_Max77812_MEMVOLT_REG = 0x25, // Master 3 (GPU 1 + 2, DRAM 3, CPU 4), used for Mariko VDDQ +} I2c_BuckConverter_Reg; + +typedef struct I2c_BuckConverter_Domain { + I2cDevice device; + I2c_BuckConverter_Reg reg; + u8 volt_mask; + u32 uv_step; + u32 uv_min; + u32 uv_max; + u8 por_val; +} I2c_BuckConverter_Domain; + +const I2c_BuckConverter_Domain I2c_Erista_CPU = { I2cDevice_Max77621Cpu, I2c_Max77621_VOLT_REG, 0x7F, 6250, 606250, 1400000, }; +const I2c_BuckConverter_Domain I2c_Erista_GPU = { I2cDevice_Max77621Gpu, I2c_Max77621_VOLT_REG, 0x7F, 6250, 606250, 1400000, }; +const I2c_BuckConverter_Domain I2c_Erista_DRAM = { I2cDevice_Max77620Pmic, I2c_Max77620_SD1VOLT_REG, 0x7F, 12500, 600000, 1250000, }; +const I2c_BuckConverter_Domain I2c_Mariko_CPU = { I2cDevice_Max77812_2, I2c_Max77812_CPUVOLT_REG, 0xFF, 5000, 250000, 1525000, 0x78 }; +const I2c_BuckConverter_Domain I2c_Mariko_GPU = { I2cDevice_Max77812_2, I2c_Max77812_GPUVOLT_REG, 0xFF, 5000, 250000, 1525000, 0x78 }; +const I2c_BuckConverter_Domain I2c_Mariko_DRAM_VDDQ = { I2cDevice_Max77812_2, I2c_Max77812_MEMVOLT_REG, 0xFF, 5000, 250000, 650000, 0x78 }; +const I2c_BuckConverter_Domain I2c_Mariko_DRAM_VDD2 = { I2cDevice_Max77620Pmic, I2c_Max77620_SD1VOLT_REG, 0x7F, 12500, 600000, 1250000, }; + +u32 I2c_BuckConverter_GetMvOut(const I2c_BuckConverter_Domain* domain); +Result I2c_BuckConverter_SetMvOut(const I2c_BuckConverter_Domain* domain, u32 mvolt); + +// Bq24193 Battery management +u32 I2c_Bq24193_Convert_Raw_mA(u8 raw); +u8 I2c_Bq24193_Convert_mA_Raw(u32 ma); + +Result I2c_Bq24193_GetFastChargeCurrentLimit(u32 *ma); +Result I2c_Bq24193_SetFastChargeCurrentLimit(u32 ma); + +const u32 MA_RANGE_MIN = 512; +const u32 MA_RANGE_MAX = 4544; + +const u8 BQ24193_CHARGE_CURRENT_CONTROL_REG = 0x2; \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/common/include/sysclk/ipc.h b/Source/sys-clk-OC(deprecated)/common/include/sysclk/ipc.h new file mode 100644 index 00000000..2c09701e --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/include/sysclk/ipc.h @@ -0,0 +1,55 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include +#include "clocks.h" + +#define SYSCLK_IPC_API_VERSION 2 +#define SYSCLK_IPC_SERVICE_NAME "sysclkOC" + +enum SysClkIpcCmd +{ + SysClkIpcCmd_GetApiVersion = 0, + SysClkIpcCmd_GetVersionString = 1, + SysClkIpcCmd_GetCurrentContext = 2, + SysClkIpcCmd_Exit = 3, + SysClkIpcCmd_GetProfileCount = 4, + SysClkIpcCmd_GetProfiles = 5, + SysClkIpcCmd_SetProfiles = 6, + SysClkIpcCmd_SetEnabled = 7, + SysClkIpcCmd_SetOverride = 8, + SysClkIpcCmd_GetConfigValues = 9, + SysClkIpcCmd_SetConfigValues = 10, + SysClkIpcCmd_SetReverseNXRTMode = 11, + SysClkIpcCmd_GetFrequencyTable = 12, + SysClkIpcCmd_GetIsMariko = 13, + SysClkIpcCmd_GetBatteryChargingDisabledOverride = 14, + SysClkIpcCmd_SetBatteryChargingDisabledOverride = 15, +}; + +typedef struct +{ + uint64_t tid; + SysClkTitleProfileList profiles; +} SysClkIpc_SetProfiles_Args; + +typedef struct +{ + SysClkModule module; + uint32_t hz; +} SysClkIpc_SetOverride_Args; + +typedef struct +{ + SysClkModule module; + SysClkProfile profile; +} SysClkIpc_GetFrequencyTable_Args; diff --git a/Source/sys-clk-OC(deprecated)/common/include/sysclk/psm_ext.h b/Source/sys-clk-OC(deprecated)/common/include/sysclk/psm_ext.h new file mode 100644 index 00000000..7065efbc --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/include/sysclk/psm_ext.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +typedef enum { + PsmPDC_NewPDO = 1, //Received new Power Data Object + PsmPDC_NoPD = 2, //No Power Delivery source is detected + PsmPDC_AcceptedRDO = 3 //Received and accepted Request Data Object +} PsmChargeInfoPDC; //BM92T series + +typedef enum { + PsmPowerRole_Sink = 1, + PsmPowerRole_Source = 2 +} PsmPowerRole; + +const char* PsmPowerRoleToStr(PsmPowerRole role); + +typedef enum { + PsmInfoChargerType_None = 0, + PsmInfoChargerType_PD = 1, + PsmInfoChargerType_TypeC_1500mA = 2, + PsmInfoChargerType_TypeC_3000mA = 3, + PsmInfoChargerType_DCP = 4, + PsmInfoChargerType_CDP = 5, + PsmInfoChargerType_SDP = 6, + PsmInfoChargerType_Apple_500mA = 7, + PsmInfoChargerType_Apple_1000mA = 8, + PsmInfoChargerType_Apple_2000mA = 9 +} PsmInfoChargerType; + +const char* PsmInfoChargerTypeToStr(PsmInfoChargerType type); + +typedef enum { + PsmFlags_NoHub = BIT(0), //If hub is disconnected + PsmFlags_Rail = BIT(8), //At least one Joy-con is charging from rail + PsmFlags_SPDSRC = BIT(12), //OTG + PsmFlags_ACC = BIT(16) //Accessory +} PsmChargeInfoFlags; + +typedef struct { + int32_t InputCurrentLimit; //Input (Sink) current limit in mA + int32_t VBUSCurrentLimit; //Output (Source/VBUS/OTG) current limit in mA + int32_t ChargeCurrentLimit; //Battery charging current limit in mA (512mA when Docked, 768mA when BatteryTemperature < 17.0 C) + int32_t ChargeVoltageLimit; //Battery charging voltage limit in mV (3952mV when BatteryTemperature >= 51.0 C) + int32_t unk_x10; //Possibly an emum, getting the same value as PowerRole in all tested cases + int32_t unk_x14; //Possibly flags + PsmChargeInfoPDC PDCState; //Power Delivery Controller State + int32_t BatteryTemperature; //Battery temperature in milli C + int32_t RawBatteryCharge; //Raw battery charged capacity per cent-mille (i.e. 100% = 100000 pcm) + int32_t VoltageAvg; //Voltage avg in mV (more in Notes) + int32_t BatteryAge; //Battery age (capacity full / capacity design) per cent-mille (i.e. 100% = 100000 pcm) + PsmPowerRole PowerRole; + PsmInfoChargerType ChargerType; + int32_t ChargerVoltageLimit; //Charger and external device voltage limit in mV + int32_t ChargerCurrentLimit; //Charger and external device current limit in mA + PsmChargeInfoFlags Flags; //Unknown flags +} PsmChargeInfo; + +typedef enum { + Psm_EnableBatteryCharging = 2, + Psm_DisableBatteryCharging = 3, + Psm_EnableFastBatteryCharging = 10, + Psm_DisableFastBatteryCharging = 11, + Psm_GetBatteryChargeInfoFields = 17, +} IPsmServerCmd; + +bool PsmIsChargerConnected(const PsmChargeInfo* info); +bool PsmIsCharging(const PsmChargeInfo* info); + +typedef enum { + PsmBatteryState_Discharging, + PsmBatteryState_ChargingPaused, + PsmBatteryState_FastCharging +} PsmBatteryState; + +PsmBatteryState PsmGetBatteryState(const PsmChargeInfo* info); +const char* PsmGetBatteryStateIcon(const PsmChargeInfo* info); \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/common/src/apm_profile_table.c b/Source/sys-clk-OC(deprecated)/common/src/apm_profile_table.c new file mode 100644 index 00000000..7439ff1a --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/src/apm_profile_table.c @@ -0,0 +1,32 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include + +SysClkApmConfiguration sysclk_g_apm_configurations[] = { + {0x00010000, 1020000000, 384000000, 1600000000}, + {0x00010001, 1020000000, 768000000, 1600000000}, + {0x00010002, 1224000000, 691200000, 1600000000}, + {0x00020000, 1020000000, 230400000, 1600000000}, + {0x00020001, 1020000000, 307200000, 1600000000}, + {0x00020002, 1224000000, 230400000, 1600000000}, + {0x00020003, 1020000000, 307000000, 1331200000}, + {0x00020004, 1020000000, 384000000, 1331200000}, + {0x00020005, 1020000000, 307200000, 1065600000}, + {0x00020006, 1020000000, 384000000, 1065600000}, + {0x92220007, 1020000000, 460800000, 1600000000}, + {0x92220008, 1020000000, 460800000, 1331200000}, + {0x92220009, 1785000000, 76800000, 1600000000}, + {0x9222000A, 1785000000, 76800000, 1331200000}, + {0x9222000B, 1020000000, 76800000, 1600000000}, + {0x9222000C, 1020000000, 76800000, 1331200000}, + {0, 0, 0, 0}, +}; + diff --git a/Source/sys-clk-OC(deprecated)/common/src/client/ipc.c b/Source/sys-clk-OC(deprecated)/common/src/client/ipc.c new file mode 100644 index 00000000..9dacffcc --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/src/client/ipc.c @@ -0,0 +1,145 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include +#include +#include +#include + +static Service g_sysclkSrv; +static atomic_size_t g_refCnt; + +bool sysclkIpcRunning() +{ + Handle handle; + bool running = R_FAILED(smRegisterService(&handle, smEncodeName(SYSCLK_IPC_SERVICE_NAME), false, 1)); + + if (!running) + { + smUnregisterService(smEncodeName(SYSCLK_IPC_SERVICE_NAME)); + } + + return running; +} + +Result sysclkIpcInitialize(void) +{ + Result rc = 0; + + g_refCnt++; + + if (serviceIsActive(&g_sysclkSrv)) + return 0; + + rc = smGetService(&g_sysclkSrv, SYSCLK_IPC_SERVICE_NAME); + + if (R_FAILED(rc)) sysclkIpcExit(); + + return rc; +} + +void sysclkIpcExit(void) +{ + if (--g_refCnt == 0) + { + serviceClose(&g_sysclkSrv); + } +} + +Result sysclkIpcGetAPIVersion(u32* out_ver) +{ + return serviceDispatchOut(&g_sysclkSrv, SysClkIpcCmd_GetApiVersion, *out_ver); +} + +Result sysclkIpcGetVersionString(char* out, size_t len) +{ + return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_GetVersionString, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = {{out, len}}, + ); +} + +Result sysclkIpcGetCurrentContext(SysClkContext* out_context) +{ + return serviceDispatchOut(&g_sysclkSrv, SysClkIpcCmd_GetCurrentContext, *out_context); +} + +Result sysclkIpcGetProfileCount(u64 tid, u8* out_count) +{ + return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetProfileCount, tid, *out_count); +} + +Result sysclkIpcSetEnabled(bool enabled) +{ + u8 enabledRaw = (u8)enabled; + return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetEnabled, enabledRaw); +} + +Result sysclkIpcSetOverride(SysClkModule module, u32 hz) +{ + SysClkIpc_SetOverride_Args args = { + .module = module, + .hz = hz + }; + return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetOverride, args); +} + +Result sysclkIpcGetProfiles(u64 tid, SysClkTitleProfileList* out_profiles) +{ + return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetProfiles, tid, *out_profiles); +} + +Result sysclkIpcSetProfiles(u64 tid, SysClkTitleProfileList* profiles) +{ + SysClkIpc_SetProfiles_Args args; + args.tid = tid; + memcpy(&args.profiles, profiles, sizeof(SysClkTitleProfileList)); + return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetProfiles, args); +} + +Result sysclkIpcGetConfigValues(SysClkConfigValueList* out_configValues) +{ + return serviceDispatchOut(&g_sysclkSrv, SysClkIpcCmd_GetConfigValues, *out_configValues); +} + +Result sysclkIpcSetConfigValues(SysClkConfigValueList* configValues) +{ + return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetConfigValues, *configValues); +} + +Result sysclkIpcSetReverseNXRTMode(ReverseNXMode mode) +{ + return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetReverseNXRTMode, mode); +} + +Result sysclkIpcGetFrequencyTable(SysClkModule module, SysClkProfile profile, SysClkFrequencyTable* out_table) +{ + SysClkIpc_GetFrequencyTable_Args args = { + .module = module, + .profile = profile, + }; + return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetFrequencyTable, args, *out_table); +} + +Result sysclkIpcGetIsMariko(bool* out_is_mariko) +{ + return serviceDispatchOut(&g_sysclkSrv, SysClkIpcCmd_GetIsMariko, *out_is_mariko); +} + +Result sysclkIpcGetBatteryChargingDisabledOverride(bool* out_is_true) +{ + return serviceDispatchOut(&g_sysclkSrv, SysClkIpcCmd_GetBatteryChargingDisabledOverride, *out_is_true); +} + +Result sysclkIpcSetBatteryChargingDisabledOverride(bool toggle_true) +{ + return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetBatteryChargingDisabledOverride, toggle_true); +} diff --git a/Source/sys-clk-OC(deprecated)/common/src/i2c.c b/Source/sys-clk-OC(deprecated)/common/src/i2c.c new file mode 100644 index 00000000..54f1121c --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/src/i2c.c @@ -0,0 +1,186 @@ +#include + +Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) { + // ams::fatal::srv::StopSoundTask::StopSound() + // I2C Bus Communication Reference: https://www.ti.com/lit/an/slva704/slva704.pdf + struct { + u8 reg; + u8 val; + } __attribute__((packed)) cmd; + + I2cSession _session; + Result res = i2cOpenSession(&_session, dev); + if (res) + return res; + + cmd.reg = reg; + cmd.val = val; + res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + i2csessionClose(&_session); + return res; +} + +Result I2cRead_OutU8(I2cDevice dev, u8 reg, u8 *out) { + struct { u8 reg; } __attribute__((packed)) cmd; + struct { u8 val; } __attribute__((packed)) rec; + + I2cSession _session; + Result res = i2cOpenSession(&_session, dev); + if (res) + return res; + + cmd.reg = reg; + res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + if (res) { + i2csessionClose(&_session); + return res; + } + + res = i2csessionReceiveAuto(&_session, &rec, sizeof(rec), I2cTransactionOption_All); + i2csessionClose(&_session); + if (res) { + return res; + } + + *out = rec.val; + return 0; +} + +Result I2cRead_OutU16(I2cDevice dev, u8 reg, u16 *out) { + struct { u8 reg; } __attribute__((packed)) cmd; + struct { u16 val; } __attribute__((packed)) rec; + + I2cSession _session; + Result res = i2cOpenSession(&_session, dev); + if (res) + return res; + + cmd.reg = reg; + res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + if (res) { + i2csessionClose(&_session); + return res; + } + + res = i2csessionReceiveAuto(&_session, &rec, sizeof(rec), I2cTransactionOption_All); + i2csessionClose(&_session); + if (res) { + return res; + } + + *out = rec.val; + return 0; +} + +float I2c_Max17050_GetBatteryCurrent() { + u16 val; + Result res = I2cRead_OutU16(I2cDevice_Max17050, MAX17050_CURRENT_REG, &val); + if (res) + return 0.f; + + const float SenseResistor = 5.; // in uOhm + const float CGain = 1.99993; + return (s16)val * (1.5625 / (SenseResistor * CGain)); +} + +u32 I2c_BuckConverter_MultiplierToMvOut(const I2c_BuckConverter_Domain* domain, u8 multiplier) { + return (domain->uv_min + domain->uv_step * multiplier) / 1000; +} + +u8 I2c_BuckConverter_MvOutToMultiplier(const I2c_BuckConverter_Domain* domain, u32 mvolt) { + u32 uvolt = mvolt * 1000; + if (uvolt < domain->uv_min) + uvolt = domain->uv_min; + if (uvolt > domain->uv_max) + uvolt = domain->uv_max; + + return (uvolt - domain->uv_min) / domain->uv_step; +} + +u32 I2c_BuckConverter_GetMvOut(const I2c_BuckConverter_Domain* domain) { + u8 val; + // Retry 5 times if received POR value + for (int i = 0; i < 5; i++) { + if (R_FAILED(I2cRead_OutU8(domain->device, domain->reg, &val))) + return 0u; + + // Wait 1us + svcSleepThread(1E3); + + if (!domain->por_val || val != domain->por_val) + break; + } + return I2c_BuckConverter_MultiplierToMvOut(domain, val & domain->volt_mask); +} + +Result I2c_BuckConverter_SetMvOut(const I2c_BuckConverter_Domain* domain, u32 mvolt) { + u8 val; + Result res = I2cRead_OutU8(domain->device, domain->reg, &val); + if (R_FAILED(res)) + return res; + + u8 multiplier = I2c_BuckConverter_MvOutToMultiplier(domain, mvolt); + val &= ~domain->volt_mask; + val |= multiplier & domain->volt_mask; + + res = I2cSet_U8(domain->device, domain->reg, val); + if (R_FAILED(res)) + return res; + + // 5ms Ramp delay + svcSleepThread(5E6); + u8 new_val; + res = I2cRead_OutU8(domain->device, domain->reg, &new_val); + if (R_FAILED(res)) + return res; + if (new_val != val) + return -1; + + return 0; +} + +u8 I2c_Bq24193_Convert_mA_Raw(u32 ma) { + // Adjustment is required + u8 raw = 0; + + if (ma > MA_RANGE_MAX) // capping + ma = MA_RANGE_MAX; + + bool pct20 = ma <= (MA_RANGE_MIN - 64); + if (pct20) { + ma = ma * 5; + raw |= 0x1; + } + + ma -= ma % 100; // round to 100 + ma -= (MA_RANGE_MIN - 64); // ceiling + raw |= (ma >> 6) << 2; + + return raw; +}; + +u32 I2c_Bq24193_Convert_Raw_mA(u8 raw) { + // No adjustment is allowed + u32 ma = (((raw >> 2)) << 6) + MA_RANGE_MIN; + + bool pct20 = raw & 1; + if (pct20) + ma = ma * 20 / 100; + + return ma; +}; + +Result I2c_Bq24193_GetFastChargeCurrentLimit(u32 *ma) { + u8 raw; + Result res = I2cRead_OutU8(I2cDevice_Bq24193, BQ24193_CHARGE_CURRENT_CONTROL_REG, &raw); + if (res) + return res; + + *ma = I2c_Bq24193_Convert_Raw_mA(raw); + return 0; +} + +Result I2c_Bq24193_SetFastChargeCurrentLimit(u32 ma) { + u8 raw = I2c_Bq24193_Convert_mA_Raw(ma); + return I2cSet_U8(I2cDevice_Bq24193, BQ24193_CHARGE_CURRENT_CONTROL_REG, raw); +} \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/common/src/psm_ext.c b/Source/sys-clk-OC(deprecated)/common/src/psm_ext.c new file mode 100644 index 00000000..c7c8a6dd --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/common/src/psm_ext.c @@ -0,0 +1,50 @@ +#include + +const char* PsmPowerRoleToStr(PsmPowerRole role) { + switch (role) { + case PsmPowerRole_Sink: return "Sink"; + case PsmPowerRole_Source: return "Source"; + default: return "Unknown"; + } +} + +const char* PsmInfoChargerTypeToStr(PsmInfoChargerType type) { + switch (type) { + case PsmInfoChargerType_None: return "None"; + case PsmInfoChargerType_PD: return "USB-C PD"; + case PsmInfoChargerType_TypeC_1500mA: + case PsmInfoChargerType_TypeC_3000mA: return "USB-C"; + case PsmInfoChargerType_DCP: return "USB DCP"; + case PsmInfoChargerType_CDP: return "USB CDP"; + case PsmInfoChargerType_SDP: return "USB SDP"; + case PsmInfoChargerType_Apple_500mA: + case PsmInfoChargerType_Apple_1000mA: + case PsmInfoChargerType_Apple_2000mA: return "Apple"; + default: return "Unknown"; + } +} + +bool PsmIsChargerConnected(const PsmChargeInfo* info) { + return info->ChargerType != PsmInfoChargerType_None; +} + +bool PsmIsCharging(const PsmChargeInfo* info) { + return PsmIsChargerConnected(info) && ((info->unk_x14 >> 8) & 1); +} + +PsmBatteryState PsmGetBatteryState(const PsmChargeInfo* info) { + if (!PsmIsChargerConnected(info)) + return PsmBatteryState_Discharging; + if (!PsmIsCharging(info)) + return PsmBatteryState_ChargingPaused; + return PsmBatteryState_FastCharging; +} + +const char* PsmGetBatteryStateIcon(const PsmChargeInfo* info) { + switch (PsmGetBatteryState(info)) { + case PsmBatteryState_Discharging: return "\u25c0"; // ◀ + case PsmBatteryState_ChargingPaused:return "| |"; + case PsmBatteryState_FastCharging: return "\u25b6"; // ▶ + default: return "?"; + } +} diff --git a/Source/sys-clk-OC(deprecated)/config.ini.template b/Source/sys-clk-OC(deprecated)/config.ini.template new file mode 100644 index 00000000..c47bbbfd --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/config.ini.template @@ -0,0 +1,16 @@ +[values] +; Defines how often sys-clk log temperatures, in milliseconds (set 0 to disable) +temp_log_interval_ms=0 +; Defines how often sys-clk writes to the CSV, in milliseconds (set 0 to disable) +csv_write_interval_ms=0 + +; Example #1: BOTW +; Overclock CPU when docked +;[01007EF00011E000] +;docked_cpu=1224 + +; Example #2: Picross +; Underclock to save battery +;[0100BA0003EEA000] +;handheld_cpu=816 +;handheld_gpu=153 \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/overlay/.gitignore b/Source/sys-clk-OC(deprecated)/overlay/.gitignore new file mode 100644 index 00000000..36a52c92 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/.gitignore @@ -0,0 +1,2 @@ +/out +/build diff --git a/Source/sys-clk-OC(deprecated)/overlay/Makefile b/Source/sys-clk-OC(deprecated)/overlay/Makefile new file mode 100644 index 00000000..6ee9e8d4 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/Makefile @@ -0,0 +1,154 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". +#--------------------------------------------------------------------------------- +TARGET := sys-clk-overlay +BUILD := build +OUTDIR := out +RESOURCES := res +SOURCES := src src/ui/gui src/ui/elements ../common/src ../common/src/client +DATA := data +INCLUDES := ../common/include +EXEFS_SRC := exefs_src + +APP_TITLE := Switch-OC-Suite +NO_ICON := 1 + +#--------------------------------------------------------------------------------- +# version control constants +#--------------------------------------------------------------------------------- +TARGET_VERSION := 1.8.3 +APP_VERSION := $(TARGET_VERSION) + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +DEFINES := -DDISABLE_IPC -DTARGET="\"$(TARGET)\"" -DTARGET_VERSION="\"$(TARGET_VERSION)\"" + +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-exceptions -std=gnu++20 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) $(TOPDIR)/lib/tesla + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(OUTDIR)/$(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)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +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) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @[ -d $(OUTDIR) ] || mkdir -p $(OUTDIR) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).ovl $(TARGET).nacp $(TARGET).nso $(TARGET).elf $(OUTDIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- + +all: $(OUTPUT).ovl + +$(OUTPUT).ovl: $(OUTPUT).elf $(OUTPUT).nacp + @elf2nro $< $@ --nacp=$(OUTPUT).nacp + @echo "built ... $(notdir $(OUTPUT).ovl)" + +$(OUTPUT).elf: $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/.github/FUNDING.yml b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/.github/FUNDING.yml new file mode 100644 index 00000000..c5522058 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +patreon: werwolv +custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=KP7XRJAND9KWU&source=url +github: WerWolv diff --git a/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/.gitignore b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/.gitignore new file mode 100644 index 00000000..8641b216 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/.gitignore @@ -0,0 +1,12 @@ +debug +release +lib +*.bz2 + +example/build/ + +*.elf + +*.nacp + +*.ovl diff --git a/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/.gitrepo b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/.gitrepo new file mode 100644 index 00000000..293fd621 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme +; +[subrepo] + remote = https://github.com/WerWolv/libtesla + branch = master + commit = 779b4ead7df6b277b947a535544aa519785c437e + parent = 3b9fed9f6b06581a0748fcfbcf992929919c01b5 + method = merge + cmdver = 0.4.5 diff --git a/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/LICENSE b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/README.md b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/README.md new file mode 100644 index 00000000..5782d161 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/README.md @@ -0,0 +1,32 @@ +# libtesla + +

+ +

+ +libtesla is the interface between the Tesla overlay loader and user-made Overlays. It handles all layer creation, UI creation, drawing and input management. +It's main goal is to make sure all overlays look and feel similar and don't differenciate themselves from the switch's native overlays. + +## Screenshots + +
+ + +
+ +`Overlays do NOT show up on Screenshots. These pictures were taken using a capture card` + +## Example + +An example for how to use libtesla can be found here: https://github.com/WerWolv/libtesla/tree/master/example +To create your own Overlay, please consider creating a new repository using the official Tesla overlay template: https://github.com/WerWolv/Tesla-Template + +**Please Note:** While it is possible to create overlays without libtesla, it's highly recommended to not do so. libtesla handles showing and hiding of overlays, button combo detection, layer creation and a lot more. Not using it will lead to an inconsistent user experience when using multiple different overlays ultimately making it worse for the end user. If something's missing, please consider opening a PR here. + +## Credits + +- **switchbrew** for nx-hbloader which is used as basis for overlay loading +- **kardch** for the amazing icon +- **All the devs on AtlasNX, RetroNX and Switchbrew** for their feedback +- **All overlay devs** for making something awesome out of this :) + diff --git a/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/example/Makefile b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/example/Makefile new file mode 100644 index 00000000..cdb011ae --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/example/Makefile @@ -0,0 +1,209 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +# +# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .json +# - config.json +# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead +# of a homebrew executable (.nro). This is intended to be used for sysmodules. +# NACP building is skipped as well. +#--------------------------------------------------------------------------------- + +APP_TITLE := Tesla Example +APP_VERSION := 1.3.0 + +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := ../include + +NO_ICON := 1 + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +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-exceptions -std=c++20 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +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)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +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 + +#--------------------------------------------------------------------------------- +all: $(BUILD) + + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @rm -fr $(BUILD) $(TARGET).ovl $(TARGET).nro $(TARGET).nacp $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).ovl + +$(OUTPUT).ovl : $(OUTPUT).elf $(OUTPUT).nacp + @elf2nro $< $@ $(NROFLAGS) + @echo "built ... $(notdir $(OUTPUT).ovl)" + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/example/source/main.cpp b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/example/source/main.cpp new file mode 100644 index 00000000..bf4c2522 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/example/source/main.cpp @@ -0,0 +1,100 @@ +#define TESLA_INIT_IMPL // If you have more than one file using the tesla header, only define this in the main one +#include // The Tesla Header + + +class GuiSecondary : public tsl::Gui { +public: + GuiSecondary() {} + + virtual tsl::elm::Element* createUI() override { + auto *rootFrame = new tsl::elm::OverlayFrame("Tesla Example", "v1.3.2 - Secondary Gui"); + + rootFrame->setContent(new tsl::elm::DebugRectangle(tsl::Color{ 0x8, 0x3, 0x8, 0xF })); + + return rootFrame; + } +}; + +class GuiTest : public tsl::Gui { +public: + GuiTest(u8 arg1, u8 arg2, bool arg3) { } + + // Called when this Gui gets loaded to create the UI + // Allocate all elements on the heap. libtesla will make sure to clean them up when not needed anymore + virtual tsl::elm::Element* createUI() override { + // A OverlayFrame is the base element every overlay consists of. This will draw the default Title and Subtitle. + // If you need more information in the header or want to change it's look, use a HeaderOverlayFrame. + auto frame = new tsl::elm::OverlayFrame("Tesla Example", "v1.3.2"); + + // A list that can contain sub elements and handles scrolling + auto list = new tsl::elm::List(); + + // List Items + list->addItem(new tsl::elm::CategoryHeader("List items")); + + auto *clickableListItem = new tsl::elm::ListItem("Clickable List Item", "..."); + clickableListItem->setClickListener([](u64 keys) { + if (keys & HidNpadButton_A) { + tsl::changeTo(); + return true; + } + + return false; + }); + + list->addItem(clickableListItem); + list->addItem(new tsl::elm::ListItem("Default List Item")); + list->addItem(new tsl::elm::ListItem("Default List Item with an extra long name to trigger truncation and scrolling")); + list->addItem(new tsl::elm::ToggleListItem("Toggle List Item", true)); + + // Custom Drawer, a element that gives direct access to the renderer + list->addItem(new tsl::elm::CategoryHeader("Custom Drawer", true)); + list->addItem(new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { + renderer->drawCircle(x + 40, y + 40, 20, true, renderer->a(0xF00F)); + renderer->drawCircle(x + 50, y + 50, 20, true, renderer->a(0xF0F0)); + renderer->drawRect(x + 130, y + 30, 60, 40, renderer->a(0xFF00)); + renderer->drawString("Hello :)", false, x + 250, y + 70, 20, renderer->a(0xFF0F)); + renderer->drawRect(x + 40, y + 90, 300, 10, renderer->a(0xF0FF)); + }), 100); + + // Track bars + list->addItem(new tsl::elm::CategoryHeader("Track bars")); + list->addItem(new tsl::elm::TrackBar("\u2600")); + list->addItem(new tsl::elm::StepTrackBar("\uE13C", 20)); + list->addItem(new tsl::elm::NamedStepTrackBar("\uE132", { "Selection 1", "Selection 2", "Selection 3" })); + + // Add the list to the frame for it to be drawn + frame->setContent(list); + + // Return the frame to have it become the top level element of this Gui + return frame; + } + + // Called once every frame to update values + virtual void update() override { + + } + + // Called once every frame to handle inputs not handled by other UI elements + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { + return false; // Return true here to signal the inputs have been consumed + } +}; + +class OverlayTest : public tsl::Overlay { +public: + // libtesla already initialized fs, hid, pl, pmdmnt, hid:sys and set:sys + virtual void initServices() override {} // Called at the start to initialize all services necessary for this Overlay + virtual void exitServices() override {} // Called at the end to clean up all services previously initialized + + virtual void onShow() override {} // Called before overlay wants to change from invisible to visible state + virtual void onHide() override {} // Called before overlay wants to change from visible to invisible state + + virtual std::unique_ptr loadInitialGui() override { + return initially(1, 2, true); // Initial Gui to load. It's possible to pass arguments to it's constructor like this + } +}; + +int main(int argc, char **argv) { + return tsl::loop(argc, argv); +} diff --git a/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/include/stb_truetype.h b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/include/stb_truetype.h new file mode 100644 index 00000000..f2fbea59 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/include/stb_truetype.h @@ -0,0 +1,5011 @@ +// stb_truetype.h - v1.24 - public domain +// authored from 2009-2020 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/include/tesla.hpp b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/include/tesla.hpp new file mode 100644 index 00000000..0d85e596 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/lib/tesla/include/tesla.hpp @@ -0,0 +1,3676 @@ +/** + * Copyright (C) 2020 werwolv + * + * This file is part of libtesla. + * + * libtesla is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * libtesla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libtesla. If not, see . + */ + +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Define this makro before including tesla.hpp in your main file. If you intend +// to use the tesla.hpp header in more than one source file, only define it once! +// #define TESLA_INIT_IMPL + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" + +#ifdef TESLA_INIT_IMPL + #define STB_TRUETYPE_IMPLEMENTATION +#endif +#include "stb_truetype.h" + +#pragma GCC diagnostic pop + +#define ELEMENT_BOUNDS(elem) elem->getX(), elem->getY(), elem->getWidth(), elem->getHeight() + +#define ASSERT_EXIT(x) if (R_FAILED(x)) std::exit(1) +#define ASSERT_FATAL(x) if (Result res = x; R_FAILED(res)) fatalThrow(res) + +#define PACKED __attribute__((packed)) +#define ALWAYS_INLINE inline __attribute__((always_inline)) + +/// Evaluates an expression that returns a result, and returns the result if it would fail. +#define TSL_R_TRY(resultExpr) \ + ({ \ + const auto result = resultExpr; \ + if (R_FAILED(result)) { \ + return result; \ + } \ + }) + +using namespace std::literals::string_literals; +using namespace std::literals::chrono_literals; + +namespace tsl { + + // Constants + + namespace cfg { + + constexpr u32 ScreenWidth = 1920; ///< Width of the Screen + constexpr u32 ScreenHeight = 1080; ///< Height of the Screen + + extern u16 LayerWidth; ///< Width of the Tesla layer + extern u16 LayerHeight; ///< Height of the Tesla layer + extern u16 LayerPosX; ///< X position of the Tesla layer + extern u16 LayerPosY; ///< Y position of the Tesla layer + extern u16 FramebufferWidth; ///< Width of the framebuffer + extern u16 FramebufferHeight; ///< Height of the framebuffer + extern u64 launchCombo; ///< Overlay activation key combo + + } + + /** + * @brief RGBA4444 Color structure + */ + struct Color { + + union { + struct { + u16 r: 4, g: 4, b: 4, a: 4; + } PACKED; + u16 rgba; + }; + + constexpr inline Color(u16 raw): rgba(raw) {} + constexpr inline Color(u8 r, u8 g, u8 b, u8 a): r(r), g(g), b(b), a(a) {} + }; + + namespace style { + constexpr u32 ListItemDefaultHeight = 70; ///< Standard list item height + constexpr u32 TrackBarDefaultHeight = 90; ///< Standard track bar height + constexpr u8 ListItemHighlightSaturation = 6; ///< Maximum saturation of Listitem highlights + constexpr u8 ListItemHighlightLength = 22; ///< Maximum length of Listitem highlights + + namespace color { + constexpr Color ColorFrameBackground = { 0x0, 0x0, 0x0, 0xD }; ///< Overlay frame background color + constexpr Color ColorTransparent = { 0x0, 0x0, 0x0, 0x0 }; ///< Transparent color + constexpr Color ColorHighlight = { 0x0, 0xF, 0xD, 0xF }; ///< Greenish highlight color + constexpr Color ColorFrame = { 0x7, 0x7, 0x7, 0xF }; ///< Outer boarder color + constexpr Color ColorHandle = { 0x5, 0x5, 0x5, 0xF }; ///< Track bar handle color + constexpr Color ColorText = { 0xF, 0xF, 0xF, 0xF }; ///< Standard text color + constexpr Color ColorDescription = { 0xA, 0xA, 0xA, 0xF }; ///< Description text color + constexpr Color ColorHeaderBar = { 0xC, 0xC, 0xC, 0xF }; ///< Category header rectangle color + constexpr Color ColorClickAnimation = { 0x0, 0x2, 0x2, 0xF }; ///< Element click animation color + } + } + + // Declarations + + /** + * @brief Direction in which focus moved before landing on + * the currently focused element + */ + enum class FocusDirection { + None, ///< Focus was placed on the element programatically without user input + Up, ///< Focus moved upwards + Down, ///< Focus moved downwards + Left, ///< Focus moved from left to rigth + Right ///< Focus moved from right to left + }; + + /** + * @brief Current input controll mode + * + */ + enum class InputMode { + Controller, ///< Input from controller + Touch, ///< Touch input + TouchScroll ///< Moving/scrolling touch input + }; + + class Overlay; + namespace elm { class Element; } + + namespace impl { + + /** + * @brief Overlay launch parameters + */ + enum class LaunchFlags : u8 { + None = 0, ///< Do nothing special at launch + CloseOnExit = BIT(0) ///< Close the overlay the last Gui gets poped from the stack + }; + + [[maybe_unused]] static constexpr LaunchFlags operator|(LaunchFlags lhs, LaunchFlags rhs) { + return static_cast(u8(lhs) | u8(rhs)); + } + + /** + * @brief Combo key mapping + */ + struct KeyInfo { + u64 key; + const char* name; + const char* glyph; + }; + + /** + * @brief Combo key mappings + * + * Ordered as they should be displayed + */ + constexpr std::array KEYS_INFO = {{ + { HidNpadButton_L, "L", "\uE0A4" }, { HidNpadButton_R, "R", "\uE0A5" }, + { HidNpadButton_ZL, "ZL", "\uE0A6" }, { HidNpadButton_ZR, "ZR", "\uE0A7" }, + { HidNpadButton_AnySL, "SL", "\uE0A8" }, { HidNpadButton_AnySR, "SR", "\uE0A9" }, + { HidNpadButton_Left, "DLEFT", "\uE07B" }, { HidNpadButton_Up, "DUP", "\uE079" }, { HidNpadButton_Right, "DRIGHT", "\uE07C" }, { HidNpadButton_Down, "DDOWN", "\uE07A" }, + { HidNpadButton_A, "A", "\uE0A0" }, { HidNpadButton_B, "B", "\uE0A1" }, { HidNpadButton_X, "X", "\uE0A2" }, { HidNpadButton_Y, "Y", "\uE0A3" }, + { HidNpadButton_StickL, "LS", "\uE08A" }, { HidNpadButton_StickR, "RS", "\uE08B" }, + { HidNpadButton_Minus, "MINUS", "\uE0B6" }, { HidNpadButton_Plus, "PLUS", "\uE0B5" } + }}; + + } + + [[maybe_unused]] static void goBack(); + + [[maybe_unused]] static void setNextOverlay(const std::string& ovlPath, std::string args = ""); + + template + int loop(int argc, char** argv); + + // Helpers + + namespace hlp { + + /** + * @brief Wrapper for service initialization + * + * @param f wrapped function + */ + template + static inline void doWithSmSession(F f) { + smInitialize(); + f(); + smExit(); + } + + /** + * @brief Wrapper for sd card access using stdio + * @note Consider using raw fs calls instead as they are faster and need less space + * + * @param f wrapped function + */ + template + static inline void doWithSDCardHandle(F f) { + fsdevMountSdmc(); + f(); + fsdevUnmountDevice("sdmc"); + } + + /** + * @brief Guard that will execute a passed function at the end of the current scope + * + * @param f wrapped function + */ + template + class ScopeGuard { + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + private: + F f; + bool canceled = false; + public: + ALWAYS_INLINE ScopeGuard(F f) : f(std::move(f)) { } + ALWAYS_INLINE ~ScopeGuard() { if (!canceled) { f(); } } + void dismiss() { canceled = true; } + }; + + /** + * @brief libnx hid:sys shim that gives or takes away frocus to or from the process with the given aruid + * + * @param enable Give focus or take focus + * @param aruid Aruid of the process to focus/unfocus + * @return Result Result + */ + static Result hidsysEnableAppletToGetInput(bool enable, u64 aruid) { + const struct { + u8 permitInput; + u64 appletResourceUserId; + } in = { enable != 0, aruid }; + + return serviceDispatchIn(hidsysGetServiceSession(), 503, in); + } + + static Result viAddToLayerStack(ViLayer *layer, ViLayerStack stack) { + const struct { + u32 stack; + u64 layerId; + } in = { stack, layer->layer_id }; + + return serviceDispatchIn(viGetSession_IManagerDisplayService(), 6000, in); + } + + /** + * @brief Toggles focus between the Tesla overlay and the rest of the system + * + * @param enabled Focus Tesla? + */ + static void requestForeground(bool enabled) { + u64 applicationAruid = 0, appletAruid = 0; + + for (u64 programId = 0x0100000000001000UL; programId < 0x0100000000001020UL; programId++) { + pmdmntGetProcessId(&appletAruid, programId); + + if (appletAruid != 0) + hidsysEnableAppletToGetInput(!enabled, appletAruid); + } + + pmdmntGetApplicationProcessId(&applicationAruid); + hidsysEnableAppletToGetInput(!enabled, applicationAruid); + + hidsysEnableAppletToGetInput(true, 0); + } + + /** + * @brief Splits a string at the given delimeters + * + * @param str String to split + * @param delim Delimeter + * @return Vector containing the split tokens + */ + static std::vector split(const std::string& str, char delim = ' ') { + std::vector out; + + std::size_t current, previous = 0; + current = str.find(delim); + while (current != std::string::npos) { + out.push_back(str.substr(previous, current - previous)); + previous = current + 1; + current = str.find(delim, previous); + } + out.push_back(str.substr(previous, current - previous)); + + return out; + } + + namespace ini { + + /** + * @brief Ini file type + */ + using IniData = std::map>; + + /** + * @brief Tesla config file + */ + static const char* CONFIG_FILE = "/config/tesla/config.ini"; + + /** + * @brief Parses a ini string + * + * @param str String to parse + * @return Parsed data + */ + static IniData parseIni(const std::string &str) { + IniData iniData; + + auto lines = split(str, '\n'); + + std::string lastHeader = ""; + for (auto& line : lines) { + line.erase(std::remove_if(line.begin(), line.end(), ::isspace), line.end()); + + if (line[0] == '[' && line[line.size() - 1] == ']') { + lastHeader = line.substr(1, line.size() - 2); + iniData.emplace(lastHeader, std::map{}); + } + else if (auto keyValuePair = split(line, '='); keyValuePair.size() == 2) { + iniData[lastHeader].emplace(keyValuePair[0], keyValuePair[1]); + } + } + + return iniData; + } + + /** + * @brief Unparses ini data into a string + * + * @param iniData Ini data + * @return Ini string + */ + static std::string unparseIni(IniData const &iniData) { + std::string string; + bool addSectionGap = false; + for (auto §ion : iniData) { + if (addSectionGap) + string += "\n"; + string += "["s + section.first + "]\n"s; + for (auto &keyValue : section.second) { + string += keyValue.first + "="s + keyValue.second + "\n"s; + } + } + return string; + } + + /** + * @brief Read Tesla settings file + * + * @return Settings data + */ + static IniData readOverlaySettings() { + /* Open Sd card filesystem. */ + FsFileSystem fsSdmc; + if (R_FAILED(fsOpenSdCardFileSystem(&fsSdmc))) + return {}; + hlp::ScopeGuard fsGuard([&] { fsFsClose(&fsSdmc); }); + + /* Open config file. */ + FsFile fileConfig; + if (R_FAILED(fsFsOpenFile(&fsSdmc, CONFIG_FILE, FsOpenMode_Read, &fileConfig))) + return {}; + hlp::ScopeGuard fileGuard([&] { fsFileClose(&fileConfig); }); + + /* Get config file size. */ + s64 configFileSize; + if (R_FAILED(fsFileGetSize(&fileConfig, &configFileSize))) + return {}; + + /* Read and parse config file. */ + std::string configFileData(configFileSize, '\0'); + u64 readSize; + Result rc = fsFileRead(&fileConfig, 0, configFileData.data(), configFileSize, FsReadOption_None, &readSize); + if (R_FAILED(rc) || readSize != static_cast(configFileSize)) + return {}; + + return parseIni(configFileData); + } + + /** + * @brief Replace Tesla settings file with new data + * + * @param iniData new data + */ + static void writeOverlaySettings(IniData const &iniData) { + /* Open Sd card filesystem. */ + FsFileSystem fsSdmc; + if (R_FAILED(fsOpenSdCardFileSystem(&fsSdmc))) + return; + hlp::ScopeGuard fsGuard([&] { fsFsClose(&fsSdmc); }); + + /* Open config file. */ + FsFile fileConfig; + if (R_FAILED(fsFsOpenFile(&fsSdmc, CONFIG_FILE, FsOpenMode_Write, &fileConfig))) + return; + hlp::ScopeGuard fileGuard([&] { fsFileClose(&fileConfig); }); + + std::string iniString = unparseIni(iniData); + + fsFileWrite(&fileConfig, 0, iniString.c_str(), iniString.length(), FsWriteOption_Flush); + } + + /** + * @brief Merge and save changes into Tesla settings file + * + * @param changes setting values to add or update + */ + static void updateOverlaySettings(IniData const &changes) { + hlp::ini::IniData iniData = hlp::ini::readOverlaySettings(); + for (auto §ion : changes) { + for (auto &keyValue : section.second) { + iniData[section.first][keyValue.first] = keyValue.second; + } + } + writeOverlaySettings(iniData); + } + + } + + /** + * @brief Decodes a key string into it's key code + * + * @param value Key string + * @return Key code + */ + static u64 stringToKeyCode(const std::string &value) { + for (auto &keyInfo : impl::KEYS_INFO) { + if (strcasecmp(value.c_str(), keyInfo.name) == 0) + return keyInfo.key; + } + return 0; + } + + /** + * @brief Decodes a combo string into key codes + * + * @param value Combo string + * @return Key codes + */ + static u64 comboStringToKeys(const std::string &value) { + u64 keyCombo = 0x00; + for (std::string key : hlp::split(value, '+')) { + keyCombo |= hlp::stringToKeyCode(key); + } + return keyCombo; + } + + /** + * @brief Encodes key codes into a combo string + * + * @param keys Key codes + * @return Combo string + */ + static std::string keysToComboString(u64 keys) { + std::string str; + for (auto &keyInfo : impl::KEYS_INFO) { + if (keys & keyInfo.key) { + if (!str.empty()) + str.append("+"); + str.append(keyInfo.name); + } + } + return str; + } + + } + + // Renderer + + namespace gfx { + + extern "C" u64 __nx_vi_layer_id; + + struct ScissoringConfig { + s32 x, y, w, h; + }; + + /** + * @brief Manages the Tesla layer and draws raw data to the screen + */ + class Renderer final { + public: + Renderer& operator=(Renderer&) = delete; + + friend class tsl::Overlay; + + /** + * @brief Handles opacity of drawn colors for fadeout. Pass all colors through this function in order to apply opacity properly + * + * @param c Original color + * @return Color with applied opacity + */ + static Color a(const Color &c) { + return (c.rgba & 0x0FFF) | (static_cast(c.a * Renderer::s_opacity) << 12); + } + + /** + * @brief Enables scissoring, discarding of any draw outside the given boundaries + * + * @param x x pos + * @param y y pos + * @param w Width + * @param h Height + */ + inline void enableScissoring(s32 x, s32 y, s32 w, s32 h) { + this->m_scissoringStack.emplace(x, y, w, h); + } + + /** + * @brief Disables scissoring + */ + inline void disableScissoring() { + this->m_scissoringStack.pop(); + } + + + // Drawing functions + + /** + * @brief Draw a single pixel onto the screen + * + * @param x X pos + * @param y Y pos + * @param color Color + */ + inline void setPixel(s32 x, s32 y, Color color) { + if (x < 0 || y < 0 || x >= cfg::FramebufferWidth || y >= cfg::FramebufferHeight) + return; + + u32 offset = this->getPixelOffset(x, y); + + if (offset != UINT32_MAX) + static_cast(this->getCurrentFramebuffer())[offset] = color; + } + + /** + * @brief Blends two colors + * + * @param src Source color + * @param dst Destination color + * @param alpha Opacity + * @return Blended color + */ + inline u8 blendColor(u8 src, u8 dst, u8 alpha) { + u8 oneMinusAlpha = 0x0F - alpha; + + return (dst * alpha + src * oneMinusAlpha) / float(0xF); + } + + /** + * @brief Draws a single source blended pixel onto the screen + * + * @param x X pos + * @param y Y pos + * @param color Color + */ + inline void setPixelBlendSrc(s32 x, s32 y, Color color) { + if (x < 0 || y < 0 || x >= cfg::FramebufferWidth || y >= cfg::FramebufferHeight) + return; + + u32 offset = this->getPixelOffset(x, y); + + if (offset == UINT32_MAX) + return; + + Color src((static_cast(this->getCurrentFramebuffer()))[offset]); + Color dst(color); + Color end(0); + + end.r = this->blendColor(src.r, dst.r, dst.a); + end.g = this->blendColor(src.g, dst.g, dst.a); + end.b = this->blendColor(src.b, dst.b, dst.a); + end.a = src.a; + + this->setPixel(x, y, end); + } + + /** + * @brief Draws a single destination blended pixel onto the screen + * + * @param x X pos + * @param y Y pos + * @param color Color + */ + inline void setPixelBlendDst(s32 x, s32 y, Color color) { + if (x < 0 || y < 0 || x >= cfg::FramebufferWidth || y >= cfg::FramebufferHeight) + return; + + u32 offset = this->getPixelOffset(x, y); + + if (offset == UINT32_MAX) + return; + + Color src((static_cast(this->getCurrentFramebuffer()))[offset]); + Color dst(color); + Color end(0); + + end.r = this->blendColor(src.r, dst.r, dst.a); + end.g = this->blendColor(src.g, dst.g, dst.a); + end.b = this->blendColor(src.b, dst.b, dst.a); + end.a = std::min(dst.a + src.a, 0xF); + + this->setPixel(x, y, end); + } + + /** + * @brief Draws a rectangle of given sizes + * + * @param x X pos + * @param y Y pos + * @param w Width + * @param h Height + * @param color Color + */ + inline void drawRect(s32 x, s32 y, s32 w, s32 h, Color color) { + for (s32 x1 = x; x1 < (x + w); x1++) + for (s32 y1 = y; y1 < (y + h); y1++) + this->setPixelBlendDst(x1, y1, color); + } + + void drawCircle(s32 centerX, s32 centerY, u16 radius, bool filled, Color color) { + s32 x = radius; + s32 y = 0; + s32 radiusError = 0; + s32 xChange = 1 - (radius << 1); + s32 yChange = 0; + + while (x >= y) { + if(filled) { + for (s32 i = centerX - x; i <= centerX + x; i++) { + s32 y0 = centerY + y; + s32 y1 = centerY - y; + s32 x0 = i; + + this->setPixelBlendDst(x0, y0, color); + this->setPixelBlendDst(x0, y1, color); + } + + for (s32 i = centerX - y; i <= centerX + y; i++) { + s32 y0 = centerY + x; + s32 y1 = centerY - x; + s32 x0 = i; + + this->setPixelBlendDst(x0, y0, color); + this->setPixelBlendDst(x0, y1, color); + } + + y++; + radiusError += yChange; + yChange += 2; + if (((radiusError << 1) + xChange) > 0) { + x--; + radiusError += xChange; + xChange += 2; + } + } else { + this->setPixelBlendDst(centerX + x, centerY + y, color); + this->setPixelBlendDst(centerX + y, centerY + x, color); + this->setPixelBlendDst(centerX - y, centerY + x, color); + this->setPixelBlendDst(centerX - x, centerY + y, color); + this->setPixelBlendDst(centerX - x, centerY - y, color); + this->setPixelBlendDst(centerX - y, centerY - x, color); + this->setPixelBlendDst(centerX + y, centerY - x, color); + this->setPixelBlendDst(centerX + x, centerY - y, color); + + if(radiusError <= 0) { + y++; + radiusError += 2 * y + 1; + } else { + x--; + radiusError -= 2 * x + 1; + } + } + } + } + + /** + * @brief Draws a RGBA8888 bitmap from memory + * + * @param x X start position + * @param y Y start position + * @param w Bitmap width + * @param h Bitmap height + * @param bmp Pointer to bitmap data + */ + void drawBitmap(s32 x, s32 y, s32 w, s32 h, const u8 *bmp) { + for (s32 y1 = 0; y1 < h; y1++) { + for (s32 x1 = 0; x1 < w; x1++) { + const Color color = { static_cast(bmp[0] >> 4), static_cast(bmp[1] >> 4), static_cast(bmp[2] >> 4), static_cast(bmp[3] >> 4) }; + setPixelBlendSrc(x + x1, y + y1, a(color)); + bmp += 4; + } + } + } + + /** + * @brief Fills the entire layer with a given color + * + * @param color Color + */ + inline void fillScreen(Color color) { + std::fill_n(static_cast(this->getCurrentFramebuffer()), this->getFramebufferSize() / sizeof(Color), color); + } + + /** + * @brief Clears the layer (With transparency) + * + */ + inline void clearScreen() { + this->fillScreen({ 0x00, 0x00, 0x00, 0x00 }); + } + + /** + * @brief Draws a string + * + * @param string String to draw + * @param monospace Draw string in monospace font + * @param x X pos + * @param y Y pos + * @param fontSize Height of the text drawn in pixels + * @param color Text color. Use transparent color to skip drawing and only get the string's dimensions + * @return Dimensions of drawn string + */ + std::pair drawString(const char* string, bool monospace, s32 x, s32 y, float fontSize, Color color, ssize_t maxWidth = 0) { + s32 maxX = x; + s32 currX = x; + s32 currY = y; + + struct Glyph { + stbtt_fontinfo *currFont; + float currFontSize; + int bounds[4]; + int xAdvance; + u8 *glyphBmp; + int width, height; + }; + + static std::unordered_map s_glyphCache; + + do { + if (maxWidth > 0 && maxWidth < (currX - x)) + break; + + u32 currCharacter; + ssize_t codepointWidth = decode_utf8(&currCharacter, reinterpret_cast(string)); + + if (codepointWidth <= 0) + break; + + string += codepointWidth; + + if (currCharacter == '\n') { + maxX = std::max(currX, maxX); + + currX = x; + currY += fontSize; + + continue; + } + + u64 key = (static_cast(currCharacter) << 32) | static_cast(monospace) << 31 | static_cast(std::bit_cast(fontSize)); + + Glyph *glyph = nullptr; + + auto it = s_glyphCache.find(key); + if (it == s_glyphCache.end()) { + /* Cache glyph */ + glyph = &s_glyphCache.emplace(key, Glyph()).first->second; + + if (stbtt_FindGlyphIndex(&this->m_extFont, currCharacter)) + glyph->currFont = &this->m_extFont; + else if(this->m_hasLocalFont && stbtt_FindGlyphIndex(&this->m_stdFont, currCharacter)==0) + glyph->currFont = &this->m_localFont; + else + glyph->currFont = &this->m_stdFont; + + glyph->currFontSize = stbtt_ScaleForPixelHeight(glyph->currFont, fontSize); + + stbtt_GetCodepointBitmapBoxSubpixel(glyph->currFont, currCharacter, glyph->currFontSize, glyph->currFontSize, + 0, 0, &glyph->bounds[0], &glyph->bounds[1], &glyph->bounds[2], &glyph->bounds[3]); + + int yAdvance = 0; + stbtt_GetCodepointHMetrics(glyph->currFont, monospace ? 'W' : currCharacter, &glyph->xAdvance, &yAdvance); + + glyph->glyphBmp = stbtt_GetCodepointBitmap(glyph->currFont, glyph->currFontSize, glyph->currFontSize, currCharacter, &glyph->width, &glyph->height, nullptr, nullptr); + } else { + /* Use cached glyph */ + glyph = &it->second; + } + + if (glyph->glyphBmp != nullptr && !std::iswspace(currCharacter) && fontSize > 0 && color.a != 0x0) { + + auto x = currX + glyph->bounds[0]; + auto y = currY + glyph->bounds[1]; + for (s32 bmpY = 0; bmpY < glyph->height; bmpY++) { + for (s32 bmpX = 0; bmpX < glyph->width; bmpX++) { + auto bmpColor = glyph->glyphBmp[glyph->width * bmpY + bmpX] >> 4; + if (bmpColor == 0xF) { + this->setPixel(x + bmpX, y + bmpY, color); + } else if (bmpColor != 0x0) { + Color tmpColor = color; + tmpColor.a = bmpColor * (float(tmpColor.a) / 0xF); + this->setPixelBlendDst(x + bmpX, y + bmpY, tmpColor); + } + } + } + + } + + currX += static_cast(glyph->xAdvance * glyph->currFontSize); + + } while (*string != '\0'); + + maxX = std::max(currX, maxX); + + return { maxX - x, currY - y }; + } + + /** + * @brief Limit a strings length and end it with "…" + * + * @param string String to truncate + * @param maxLength Maximum length of string + */ + std::string limitStringLength(std::string string, bool monospace, float fontSize, s32 maxLength) { + if (string.size() < 2) + return string; + + s32 currX = 0; + ssize_t strPos = 0; + ssize_t codepointWidth; + + do { + u32 currCharacter; + codepointWidth = decode_utf8(&currCharacter, reinterpret_cast(&string[strPos])); + + if (codepointWidth <= 0) + break; + + strPos += codepointWidth; + + stbtt_fontinfo *currFont = nullptr; + + if (stbtt_FindGlyphIndex(&this->m_extFont, currCharacter)) + currFont = &this->m_extFont; + else if(this->m_hasLocalFont && stbtt_FindGlyphIndex(&this->m_stdFont, currCharacter)==0) + currFont = &this->m_localFont; + else + currFont = &this->m_stdFont; + + float currFontSize = stbtt_ScaleForPixelHeight(currFont, fontSize); + + int xAdvance = 0, yAdvance = 0; + stbtt_GetCodepointHMetrics(currFont, monospace ? 'W' : currCharacter, &xAdvance, &yAdvance); + + currX += static_cast(xAdvance * currFontSize); + + } while (string[strPos] != '\0' && string[strPos] != '\n' && currX < maxLength); + + string = string.substr(0, strPos - codepointWidth) + "…"; + string.shrink_to_fit(); + + return string; + } + + private: + Renderer() {} + + /** + * @brief Gets the renderer instance + * + * @return Renderer + */ + static Renderer& get() { + static Renderer renderer; + + return renderer; + } + + /** + * @brief Sets the opacity of the layer + * + * @param opacity Opacity + */ + static void setOpacity(float opacity) { + opacity = std::clamp(opacity, 0.0F, 1.0F); + + Renderer::s_opacity = opacity; + } + + bool m_initialized = false; + ViDisplay m_display; + ViLayer m_layer; + Event m_vsyncEvent; + + NWindow m_window; + Framebuffer m_framebuffer; + void *m_currentFramebuffer = nullptr; + + std::stack m_scissoringStack; + + stbtt_fontinfo m_stdFont, m_localFont, m_extFont; + bool m_hasLocalFont = false; + + static inline float s_opacity = 1.0F; + + /** + * @brief Get the current framebuffer address + * + * @return Framebuffer address + */ + inline void* getCurrentFramebuffer() { + return this->m_currentFramebuffer; + } + + /** + * @brief Get the next framebuffer address + * + * @return Next framebuffer address + */ + inline void* getNextFramebuffer() { + return static_cast(this->m_framebuffer.buf) + this->getNextFramebufferSlot() * this->getFramebufferSize(); + } + + /** + * @brief Get the framebuffer size + * + * @return Framebuffer size + */ + inline size_t getFramebufferSize() { + return this->m_framebuffer.fb_size; + } + + /** + * @brief Get the number of framebuffers in use + * + * @return Number of framebuffers + */ + inline size_t getFramebufferCount() { + return this->m_framebuffer.num_fbs; + } + + /** + * @brief Get the currently used framebuffer's slot + * + * @return Slot + */ + inline u8 getCurrentFramebufferSlot() { + return this->m_window.cur_slot; + } + + /** + * @brief Get the next framebuffer's slot + * + * @return Next slot + */ + inline u8 getNextFramebufferSlot() { + return (this->getCurrentFramebufferSlot() + 1) % this->getFramebufferCount(); + } + + /** + * @brief Waits for the vsync event + * + */ + inline void waitForVSync() { + eventWait(&this->m_vsyncEvent, UINT64_MAX); + } + + /** + * @brief Decodes a x and y coordinate into a offset into the swizzled framebuffer + * + * @param x X pos + * @param y Y Pos + * @return Offset + */ + u32 getPixelOffset(s32 x, s32 y) { + if (!this->m_scissoringStack.empty()) { + auto currScissorConfig = this->m_scissoringStack.top(); + if (x < currScissorConfig.x || + y < currScissorConfig.y || + x > currScissorConfig.x + currScissorConfig.w || + y > currScissorConfig.y + currScissorConfig.h) + return UINT32_MAX; + } + + u32 tmpPos = ((y & 127) / 16) + (x / 32 * 8) + ((y / 16 / 8) * (((cfg::FramebufferWidth / 2) / 16 * 8))); + tmpPos *= 16 * 16 * 4; + + tmpPos += ((y % 16) / 8) * 512 + ((x % 32) / 16) * 256 + ((y % 8) / 2) * 64 + ((x % 16) / 8) * 32 + (y % 2) * 16 + (x % 8) * 2; + + return tmpPos / 2; + } + + /** + * @brief Initializes the renderer and layers + * + */ + void init() { + + cfg::LayerPosX = 0; + cfg::LayerPosY = 0; + cfg::FramebufferWidth = 448; + cfg::FramebufferHeight = 720; + cfg::LayerWidth = cfg::ScreenHeight * (float(cfg::FramebufferWidth) / float(cfg::FramebufferHeight)); + cfg::LayerHeight = cfg::ScreenHeight; + + if (this->m_initialized) + return; + + tsl::hlp::doWithSmSession([this]{ + ASSERT_FATAL(viInitialize(ViServiceType_Manager)); + ASSERT_FATAL(viOpenDefaultDisplay(&this->m_display)); + ASSERT_FATAL(viGetDisplayVsyncEvent(&this->m_display, &this->m_vsyncEvent)); + ASSERT_FATAL(viCreateManagedLayer(&this->m_display, static_cast(0), 0, &__nx_vi_layer_id)); + ASSERT_FATAL(viCreateLayer(&this->m_display, &this->m_layer)); + ASSERT_FATAL(viSetLayerScalingMode(&this->m_layer, ViScalingMode_FitToLayer)); + + if (s32 layerZ = 0; R_SUCCEEDED(viGetZOrderCountMax(&this->m_display, &layerZ)) && layerZ > 0) + ASSERT_FATAL(viSetLayerZ(&this->m_layer, layerZ)); + + ASSERT_FATAL(tsl::hlp::viAddToLayerStack(&this->m_layer, ViLayerStack_Default)); + ASSERT_FATAL(tsl::hlp::viAddToLayerStack(&this->m_layer, ViLayerStack_Screenshot)); + ASSERT_FATAL(tsl::hlp::viAddToLayerStack(&this->m_layer, ViLayerStack_Recording)); + ASSERT_FATAL(tsl::hlp::viAddToLayerStack(&this->m_layer, ViLayerStack_Arbitrary)); + ASSERT_FATAL(tsl::hlp::viAddToLayerStack(&this->m_layer, ViLayerStack_LastFrame)); + ASSERT_FATAL(tsl::hlp::viAddToLayerStack(&this->m_layer, ViLayerStack_Null)); + ASSERT_FATAL(tsl::hlp::viAddToLayerStack(&this->m_layer, ViLayerStack_ApplicationForDebug)); + ASSERT_FATAL(tsl::hlp::viAddToLayerStack(&this->m_layer, ViLayerStack_Lcd)); + //ASSERT_FATAL(tsl::hlp::viAddToLayerStack(&this->m_layer, 8)); + + ASSERT_FATAL(viSetLayerSize(&this->m_layer, cfg::LayerWidth, cfg::LayerHeight)); + ASSERT_FATAL(viSetLayerPosition(&this->m_layer, cfg::LayerPosX, cfg::LayerPosY)); + ASSERT_FATAL(nwindowCreateFromLayer(&this->m_window, &this->m_layer)); + ASSERT_FATAL(framebufferCreate(&this->m_framebuffer, &this->m_window, cfg::FramebufferWidth, cfg::FramebufferHeight, PIXEL_FORMAT_RGBA_4444, 2)); + ASSERT_FATAL(setInitialize()); + ASSERT_FATAL(this->initFonts()); + setExit(); + }); + + this->m_initialized = true; + } + + /** + * @brief Exits the renderer and layer + * + */ + void exit() { + if (!this->m_initialized) + return; + + //framebufferClose(&this->m_framebuffer); + Framebuffer* fb = &this->m_framebuffer; + if (!fb || !fb->has_init) + return; + + if (fb->buf_linear) + free(fb->buf_linear); + + if (fb->buf) { + nwindowReleaseBuffers(fb->win); + nvMapClose(&fb->map); + free(fb->buf); + } + + memset(fb, 0, sizeof(*fb)); + nvFenceExit(); + nvMapExit(); + svcSleepThread(10'000'000); + nvExit(); + nwindowClose(&this->m_window); + viDestroyManagedLayer(&this->m_layer); + viCloseDisplay(&this->m_display); + eventClose(&this->m_vsyncEvent); + viExit(); + } + + /** + * @brief Initializes Nintendo's shared fonts. Default and Extended + * + * @return Result + */ + Result initFonts() { + static PlFontData stdFontData, localFontData, extFontData; + + // Nintendo's default font + TSL_R_TRY(plGetSharedFontByType(&stdFontData, PlSharedFontType_Standard)); + + u8 *fontBuffer = reinterpret_cast(stdFontData.address); + stbtt_InitFont(&this->m_stdFont, fontBuffer, stbtt_GetFontOffsetForIndex(fontBuffer, 0)); + + u64 languageCode; + if (R_SUCCEEDED(setGetSystemLanguage(&languageCode))) { + // Check if need localization font + SetLanguage setLanguage; + TSL_R_TRY(setMakeLanguage(languageCode, &setLanguage)); + this->m_hasLocalFont = true; + switch (setLanguage) { + case SetLanguage_ZHCN: + case SetLanguage_ZHHANS: + TSL_R_TRY(plGetSharedFontByType(&localFontData, PlSharedFontType_ChineseSimplified)); + break; + case SetLanguage_KO: + TSL_R_TRY(plGetSharedFontByType(&localFontData, PlSharedFontType_KO)); + break; + case SetLanguage_ZHTW: + case SetLanguage_ZHHANT: + TSL_R_TRY(plGetSharedFontByType(&localFontData, PlSharedFontType_ChineseTraditional)); + break; + default: + this->m_hasLocalFont = false; + break; + } + + if (this->m_hasLocalFont) { + fontBuffer = reinterpret_cast(localFontData.address); + stbtt_InitFont(&this->m_localFont, fontBuffer, stbtt_GetFontOffsetForIndex(fontBuffer, 0)); + } + } + + // Nintendo's extended font containing a bunch of icons + TSL_R_TRY(plGetSharedFontByType(&extFontData, PlSharedFontType_NintendoExt)); + + fontBuffer = reinterpret_cast(extFontData.address); + stbtt_InitFont(&this->m_extFont, fontBuffer, stbtt_GetFontOffsetForIndex(fontBuffer, 0)); + + return 0; + } + + /** + * @brief Start a new frame + * @warning Don't call this more than once before calling \ref endFrame + */ + inline void startFrame() { + this->m_currentFramebuffer = framebufferBegin(&this->m_framebuffer, nullptr); + } + + /** + * @brief End the current frame + * @warning Don't call this before calling \ref startFrame once + */ + inline void endFrame() { + this->waitForVSync(); + framebufferEnd(&this->m_framebuffer); + + this->m_currentFramebuffer = nullptr; + } + }; + + } + + // Elements + + namespace elm { + + enum class TouchEvent { + Touch, + Hold, + Scroll, + Release + }; + + /** + * @brief The top level Element of the libtesla UI library + * @note When creating your own elements, extend from this or one of it's sub classes + */ + class Element { + public: + Element() {} + virtual ~Element() { } + + /** + * @brief Handles focus requesting + * @note This function should return the element to focus. + * When this element should be focused, return `this`. + * When one of it's child should be focused, return `this->child->requestFocus(oldFocus, direction)` + * When this element is not focusable, return `nullptr` + * + * @param oldFocus Previously focused element + * @param direction Direction in which focus moved. \ref FocusDirection::None is passed for the initial load + * @return Element to focus + */ + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) { + return nullptr; + } + + /** + * @brief Function called when a joycon button got pressed + * + * @param keys Keys pressed in the last frame + * @return true when button press has been consumed + * @return false when button press should be passed on to the parent + */ + virtual bool onClick(u64 keys) { + return m_clickListener(keys); + } + + /** + * @brief Called once per frame with the latest HID inputs + * + * @param keysDown Buttons pressed in the last frame + * @param keysHeld Buttons held down longer than one frame + * @param touchInput Last touch position + * @param leftJoyStick Left joystick position + * @param rightJoyStick Right joystick position + * @return Weather or not the input has been consumed + */ + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) { + return false; + } + + /** + * @brief Function called when the element got touched + * @todo Not yet implemented + * + * @param x X pos + * @param y Y pos + * @return true when touch input has been consumed + * @return false when touch input should be passed on to the parent + */ + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) { + return false; + } + + /** + * @brief Called once per frame to draw the element + * @warning Do not call this yourself. Use \ref Element::frame(gfx::Renderer *renderer) + * + * @param renderer Renderer + */ + virtual void draw(gfx::Renderer *renderer) = 0; + + /** + * @brief Called when the underlying Gui gets created and after calling \ref Gui::invalidate() to calculate positions and boundaries of the element + * @warning Do not call this yourself. Use \ref Element::invalidate() + * + * @param parentX Parent X pos + * @param parentY Parent Y pos + * @param parentWidth Parent Width + * @param parentHeight Parent Height + */ + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) = 0; + + /** + * @brief Draws highlighting and the element itself + * @note When drawing children of a element in \ref Element::draw(gfx::Renderer *renderer), use `this->child->frame(renderer)` instead of calling draw directly + * + * @param renderer + */ + void frame(gfx::Renderer *renderer) { + renderer->enableScissoring(0, 0, tsl::cfg::FramebufferWidth, tsl::cfg::FramebufferHeight); + + if (this->m_focused) + this->drawFocusBackground(renderer); + + renderer->disableScissoring(); + + this->draw(renderer); + + renderer->enableScissoring(0, 0, tsl::cfg::FramebufferWidth, tsl::cfg::FramebufferHeight); + + if (this->m_focused) + this->drawHighlight(renderer); + + renderer->disableScissoring(); + } + + /** + * @brief Forces a layout recreation of a element + * + */ + void invalidate() { + const auto& parent = this->getParent(); + + if (parent == nullptr) + this->layout(0, 0, cfg::FramebufferWidth, cfg::FramebufferHeight); + else + this->layout(ELEMENT_BOUNDS(parent)); + } + + /** + * @brief Shake the highlight in the given direction to signal that the focus cannot move there + * + * @param direction Direction to shake highlight in + */ + void shakeHighlight(FocusDirection direction) { + this->m_highlightShaking = true; + this->m_highlightShakingDirection = direction; + this->m_highlightShakingStartTime = std::chrono::system_clock::now(); + } + + /** + * @brief Triggers the blue click animation to signal a element has been clicked on + * + */ + void triggerClickAnimation() { + this->m_clickAnimationProgress = tsl::style::ListItemHighlightLength; + } + + /** + * @brief Resets the click animation progress, canceling the animation + */ + void resetClickAnimation() { + this->m_clickAnimationProgress = 0; + } + + /** + * @brief Draws the blue highlight animation when clicking on a button + * @note Override this if you have a element that e.g requires a non-rectangular animation or a different color + * + * @param renderer Renderer + */ + virtual void drawClickAnimation(gfx::Renderer *renderer) { + Color animColor = tsl::style::color::ColorClickAnimation; + u8 saturation = tsl::style::ListItemHighlightSaturation * (float(this->m_clickAnimationProgress) / float(tsl::style::ListItemHighlightLength)); + + animColor.g = saturation; + animColor.b = saturation; + + renderer->drawRect(ELEMENT_BOUNDS(this), a(animColor)); + } + + /** + * @brief Draws the back background when a element is highlighted + * @note Override this if you have a element that e.g requires a non-rectangular focus + * + * @param renderer Renderer + */ + virtual void drawFocusBackground(gfx::Renderer *renderer) { + renderer->drawRect(ELEMENT_BOUNDS(this), a(0xF000)); + + if (this->m_clickAnimationProgress > 0) { + this->drawClickAnimation(renderer); + this->m_clickAnimationProgress--; + } + } + + /** + * @brief Draws the blue boarder when a element is highlighted + * @note Override this if you have a element that e.g requires a non-rectangular focus + * + * @param renderer Renderer + */ + virtual void drawHighlight(gfx::Renderer *renderer) { + static float counter = 0; + const float progress = (std::sin(counter) + 1) / 2; + Color highlightColor = { static_cast((0x2 - 0x8) * progress + 0x8), + static_cast((0x8 - 0xF) * progress + 0xF), + static_cast((0xC - 0xF) * progress + 0xF), + 0xF }; + + counter += 0.1F; + + s32 x = 0, y = 0; + + if (this->m_highlightShaking) { + auto t = (std::chrono::system_clock::now() - this->m_highlightShakingStartTime); + if (t >= 100ms) + this->m_highlightShaking = false; + else { + s32 amplitude = std::rand() % 5 + 5; + + switch (this->m_highlightShakingDirection) { + case FocusDirection::Up: + y -= shakeAnimation(t, amplitude); + break; + case FocusDirection::Down: + y += shakeAnimation(t, amplitude); + break; + case FocusDirection::Left: + x -= shakeAnimation(t, amplitude); + break; + case FocusDirection::Right: + x += shakeAnimation(t, amplitude); + break; + default: + break; + } + + x = std::clamp(x, -amplitude, amplitude); + y = std::clamp(y, -amplitude, amplitude); + } + } + + renderer->drawRect(this->getX() + x - 4, this->getY() + y - 4, this->getWidth() + 8, 4, a(highlightColor)); + renderer->drawRect(this->getX() + x - 4, this->getY() + y + this->getHeight(), this->getWidth() + 8, 4, a(highlightColor)); + renderer->drawRect(this->getX() + x - 4, this->getY() + y, 4, this->getHeight(), a(highlightColor)); + renderer->drawRect(this->getX() + x + this->getWidth(), this->getY() + y, 4, this->getHeight(), a(highlightColor)); + + } + + /** + * @brief Sets the boundaries of this view + * + * @param x Start X pos + * @param y Start Y pos + * @param width Width + * @param height Height + */ + void setBoundaries(s32 x, s32 y, s32 width, s32 height) { + this->m_x = x; + this->m_y = y; + this->m_width = width; + this->m_height = height; + } + + /** + * @brief Adds a click listener to the element + * + * @param clickListener Click listener called with keys that were pressed last frame. Callback should return true if keys got consumed + */ + virtual void setClickListener(std::function clickListener) { + this->m_clickListener = clickListener; + } + + /** + * @brief Gets the element's X position + * + * @return X position + */ + inline s32 getX() { return this->m_x; } + /** + * @brief Gets the element's Y position + * + * @return Y position + */ + inline s32 getY() { return this->m_y; } + /** + * @brief Gets the element's Width + * + * @return Width + */ + inline s32 getWidth() { return this->m_width; } + /** + * @brief Gets the element's Height + * + * @return Height + */ + inline s32 getHeight() { return this->m_height; } + + inline s32 getTopBound() { return this->getY(); } + inline s32 getLeftBound() { return this->getX(); } + inline s32 getRightBound() { return this->getX() + this->getWidth(); } + inline s32 getBottomBound() { return this->getY() + this->getHeight(); } + + /** + * @brief Check if the coordinates are in the elements bounds + * + * @return true if coordinates are in bounds, false otherwise + */ + bool inBounds(s32 touchX, s32 touchY) { + return touchX >= this->getLeftBound() && touchX <= this->getRightBound() && touchY >= this->getTopBound() && touchY <= this->getBottomBound(); + } + + /** + * @brief Sets the element's parent + * @note This is required to handle focus and button downpassing properly + * + * @param parent Parent + */ + inline void setParent(Element *parent) { this->m_parent = parent; } + + /** + * @brief Get the element's parent + * + * @return Parent + */ + inline Element* getParent() { return this->m_parent; } + + /** + * @brief Marks this element as focused or unfocused to draw the highlight + * + * @param focused Focused + */ + virtual inline void setFocused(bool focused) { + this->m_focused = focused; + this->m_clickAnimationProgress = 0; + } + + + static InputMode getInputMode() { return Element::s_inputMode; } + + static void setInputMode(InputMode mode) { Element::s_inputMode = mode; } + + protected: + constexpr static inline auto a = &gfx::Renderer::a; + bool m_focused = false; + u8 m_clickAnimationProgress = 0; + + // Highlight shake animation + bool m_highlightShaking = false; + std::chrono::system_clock::time_point m_highlightShakingStartTime; + FocusDirection m_highlightShakingDirection; + + static inline InputMode s_inputMode; + + /** + * @brief Shake animation callculation based on a damped sine wave + * + * @param t Passed time + * @param a Amplitude + * @return Damped sine wave output + */ + int shakeAnimation(std::chrono::system_clock::duration t, float a) { + float w = 0.2F; + float tau = 0.05F; + + int t_ = t.count() / 1'000'000; + + return roundf(a * exp(-(tau * t_) * sin(w * t_))); + } + + private: + friend class Gui; + + s32 m_x = 0, m_y = 0, m_width = 0, m_height = 0; + Element *m_parent = nullptr; + + std::function m_clickListener = [](u64) { return false; }; + + }; + + /** + * @brief A Element that exposes the renderer directly to draw custom views easily + */ + class CustomDrawer : public Element { + public: + /** + * @brief Constructor + * @note This element should only be used to draw static things the user cannot interact with e.g info text, images, etc. + * + * @param renderFunc Callback that will be called once every frame to draw this view + */ + CustomDrawer(std::function renderFunc) : Element(), m_renderFunc(renderFunc) {} + virtual ~CustomDrawer() {} + + virtual void draw(gfx::Renderer* renderer) override { + renderer->enableScissoring(ELEMENT_BOUNDS(this)); + this->m_renderFunc(renderer, ELEMENT_BOUNDS(this)); + renderer->disableScissoring(); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + + } + + private: + std::function m_renderFunc; + }; + + + /** + * @brief The base frame which can contain another view + * + */ + class OverlayFrame : public Element { + public: + /** + * @brief Constructor + * + * @param title Name of the Overlay drawn bolt at the top + * @param subtitle Subtitle drawn bellow the title e.g version number + */ + OverlayFrame(const std::string& title, const std::string& subtitle) : Element(), m_title(title), m_subtitle(subtitle) {} + virtual ~OverlayFrame() { + if (this->m_contentElement != nullptr) + delete this->m_contentElement; + } + + virtual void draw(gfx::Renderer *renderer) override { + renderer->fillScreen(a(tsl::style::color::ColorFrameBackground)); + renderer->drawRect(tsl::cfg::FramebufferWidth - 1, 0, 1, tsl::cfg::FramebufferHeight, a(0xF222)); + + renderer->drawString(this->m_title.c_str(), false, 20, 50, 30, a(tsl::style::color::ColorText)); + renderer->drawString(this->m_subtitle.c_str(), false, 20, 70, 15, a(tsl::style::color::ColorDescription)); + + renderer->drawRect(15, tsl::cfg::FramebufferHeight - 73, tsl::cfg::FramebufferWidth - 30, 1, a(tsl::style::color::ColorText)); + + renderer->drawString("\uE0E1 Back \uE0E0 OK", false, 30, 693, 23, a(tsl::style::color::ColorText)); + + if (this->m_contentElement != nullptr) + this->m_contentElement->frame(renderer); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + this->setBoundaries(parentX, parentY, parentWidth, parentHeight); + + if (this->m_contentElement != nullptr) { + this->m_contentElement->setBoundaries(parentX + 35, parentY + 125, parentWidth - 85, parentHeight - 73 - 125); + this->m_contentElement->invalidate(); + } + } + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) override { + if (this->m_contentElement != nullptr) + return this->m_contentElement->requestFocus(oldFocus, direction); + else + return nullptr; + } + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) { + // Discard touches outside bounds + if (!this->m_contentElement->inBounds(currX, currY)) + return false; + + if (this->m_contentElement != nullptr) + return this->m_contentElement->onTouch(event, currX, currY, prevX, prevY, initialX, initialY); + else return false; + } + + /** + * @brief Sets the content of the frame + * + * @param content Element + */ + void setContent(Element *content) { + if (this->m_contentElement != nullptr) + delete this->m_contentElement; + + this->m_contentElement = content; + + if (content != nullptr) { + this->m_contentElement->setParent(this); + this->invalidate(); + } + } + + /** + * @brief Changes the title of the menu + * + * @param title Title to change to + */ + void setTitle(const std::string &title) { + this->m_title = title; + } + + /** + * @brief Changes the subtitle of the menu + * + * @param title Subtitle to change to + */ + void setSubtitle(const std::string &subtitle) { + this->m_subtitle = subtitle; + } + + protected: + Element *m_contentElement = nullptr; + + std::string m_title, m_subtitle; + }; + + /** + * @brief The base frame which can contain another view with a customizable header + * + */ + class HeaderOverlayFrame : public Element { + public: + HeaderOverlayFrame(u16 headerHeight = 175) : Element(), m_headerHeight(headerHeight) {} + virtual ~HeaderOverlayFrame() { + if (this->m_contentElement != nullptr) + delete this->m_contentElement; + + if (this->m_header != nullptr) + delete this->m_header; + } + + virtual void draw(gfx::Renderer *renderer) override { + renderer->fillScreen(a(tsl::style::color::ColorFrameBackground)); + renderer->drawRect(tsl::cfg::FramebufferWidth - 1, 0, 1, tsl::cfg::FramebufferHeight, a(0xF222)); + + renderer->drawRect(15, tsl::cfg::FramebufferHeight - 73, tsl::cfg::FramebufferWidth - 30, 1, a(tsl::style::color::ColorText)); + + renderer->drawString("\uE0E1 Back \uE0E0 OK", false, 30, 693, 23, a(tsl::style::color::ColorText)); + + if (this->m_header != nullptr) + this->m_header->frame(renderer); + + if (this->m_contentElement != nullptr) + this->m_contentElement->frame(renderer); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + this->setBoundaries(parentX, parentY, parentWidth, parentHeight); + + if (this->m_contentElement != nullptr) { + this->m_contentElement->setBoundaries(parentX + 35, parentY + this->m_headerHeight, parentWidth - 85, parentHeight - 73 - this->m_headerHeight); + this->m_contentElement->invalidate(); + } + + if (this->m_header != nullptr) { + this->m_header->setBoundaries(parentX, parentY, parentWidth, this->m_headerHeight); + this->m_header->invalidate(); + } + } + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) { + // Discard touches outside bounds + if (!this->m_contentElement->inBounds(currX, currY)) + return false; + + if (this->m_contentElement != nullptr) + return this->m_contentElement->onTouch(event, currX, currY, prevX, prevY, initialX, initialY); + else return false; + } + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) override { + if (this->m_contentElement != nullptr) + return this->m_contentElement->requestFocus(oldFocus, direction); + else + return nullptr; + } + + /** + * @brief Sets the content of the frame + * + * @param content Element + */ + void setContent(Element *content) { + if (this->m_contentElement != nullptr) + delete this->m_contentElement; + + this->m_contentElement = content; + + if (content != nullptr) { + this->m_contentElement->setParent(this); + this->invalidate(); + } + } + + /** + * @brief Sets the header of the frame + * + * @param header Header custom drawer + */ + void setHeader(CustomDrawer *header) { + if (this->m_header != nullptr) + delete this->m_header; + + this->m_header = header; + + if (header != nullptr) { + this->m_header->setParent(this); + this->invalidate(); + } + } + + protected: + Element *m_contentElement = nullptr; + CustomDrawer *m_header = nullptr; + + u16 m_headerHeight; + }; + + /** + * @brief Single color rectangle element mainly used for debugging to visualize boundaries + * + */ + class DebugRectangle : public Element { + public: + /** + * @brief Constructor + * + * @param color Color of the rectangle + */ + DebugRectangle(Color color) : Element(), m_color(color) {} + virtual ~DebugRectangle() {} + + virtual void draw(gfx::Renderer *renderer) override { + renderer->drawRect(ELEMENT_BOUNDS(this), a(this->m_color)); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override {} + + private: + Color m_color; + }; + + + /** + * @brief A List containing list items + * + */ + class List : public Element { + public: + /** + * @brief Constructor + * + */ + List() : Element() {} + virtual ~List() { + for (auto& item : this->m_items) + delete item; + } + + virtual void draw(gfx::Renderer *renderer) override { + if (this->m_clearList) { + for (auto& item : this->m_items) + delete item; + + this->m_items.clear(); + this->m_offset = 0; + this->m_focusedIndex = 0; + this->invalidate(); + this->m_clearList = false; + } + + for (auto [index, element] : this->m_itemsToAdd) { + element->invalidate(); + if (index >= 0 && (this->m_items.size() > static_cast(index))) { + const auto& it = this->m_items.cbegin() + static_cast(index); + this->m_items.insert(it, element); + } else { + this->m_items.push_back(element); + } + this->invalidate(); + this->updateScrollOffset(); + } + this->m_itemsToAdd.clear(); + + for (auto element : this->m_itemsToRemove) { + for (auto it = m_items.cbegin(); it != m_items.cend(); ++it) { + if (*it == element) { + this->m_items.erase(it); + if (this->m_focusedIndex >= (it - this->m_items.cbegin())) { + this->m_focusedIndex--; + } + this->invalidate(); + this->updateScrollOffset(); + delete element; + break; + } + } + } + this->m_itemsToRemove.clear(); + + renderer->enableScissoring(this->getLeftBound(), this->getTopBound() - 5, this->getWidth(), this->getHeight() + 4); + + for (auto &entry : this->m_items) { + if (entry->getBottomBound() > this->getTopBound() && entry->getTopBound() < this->getBottomBound()) { + entry->frame(renderer); + } + } + + renderer->disableScissoring(); + + if (this->m_listHeight > this->getHeight()) { + float scrollbarHeight = static_cast(this->getHeight() * this->getHeight()) / this->m_listHeight; + float scrollbarOffset = (static_cast(this->m_offset)) / static_cast(this->m_listHeight - this->getHeight()) * (this->getHeight() - std::ceil(scrollbarHeight)); + + renderer->drawRect(this->getRightBound() + 10, this->getY() + scrollbarOffset, 5, scrollbarHeight - 50, a(tsl::style::color::ColorHandle)); + renderer->drawCircle(this->getRightBound() + 12, this->getY() + scrollbarOffset, 2, true, a(tsl::style::color::ColorHandle)); + renderer->drawCircle(this->getRightBound() + 12, this->getY() + scrollbarOffset + scrollbarHeight - 50, 2, true, a(tsl::style::color::ColorHandle)); + + float prevOffset = this->m_offset; + + if (Element::getInputMode() == InputMode::Controller) + this->m_offset += ((this->m_nextOffset) - this->m_offset) * 0.1F; + else if (Element::getInputMode() == InputMode::TouchScroll) + this->m_offset += ((this->m_nextOffset) - this->m_offset); + + if (static_cast(prevOffset) != static_cast(this->m_offset)) + this->invalidate(); + } + + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + s32 y = this->getY() - this->m_offset; + + this->m_listHeight = 0; + for (auto &entry : this->m_items) + this->m_listHeight += entry->getHeight(); + + for (auto &entry : this->m_items) { + entry->setBoundaries(this->getX(), y, this->getWidth(), entry->getHeight()); + entry->invalidate(); + y += entry->getHeight(); + } + } + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) { + bool handled = false; + + // Discard touches out of bounds + if (!this->inBounds(currX, currY)) + return false; + + // Direct touches to all children + for (auto &item : this->m_items) + handled |= item->onTouch(event, currX, currY, prevX, prevY, initialX, initialY); + + if (handled) + return true; + + // Handle scrolling + if (event != TouchEvent::Release && Element::getInputMode() == InputMode::TouchScroll) { + if (prevX != 0 && prevY != 0) + this->m_nextOffset += (prevY - currY); + + if (this->m_nextOffset < 0) + this->m_nextOffset = 0; + + if (this->m_nextOffset > (this->m_listHeight - this->getHeight()) + 50) + this->m_nextOffset = (this->m_listHeight - this->getHeight() + 50); + + return true; + } + + return false; + } + + /** + * @brief Adds a new item to the list before the next frame starts + * + * @param element Element to add + * @param index Index in the list where the item should be inserted. -1 or greater list size will insert it at the end + * @param height Height of the element. Don't set this parameter for libtesla to try and figure out the size based on the type + */ + void addItem(Element *element, u16 height = 0, ssize_t index = -1) { + if (element != nullptr) { + if (height != 0) + element->setBoundaries(this->getX(), this->getY(), this->getWidth(), height); + + element->setParent(this); + element->invalidate(); + + this->m_itemsToAdd.emplace_back(index, element); + } + } + + /** + * @brief Removes an item form the list and deletes it + * @note Item will only be deleted if it was found in the list + * + * @param element Element to remove from list. Call \ref Gui::removeFocus before. + */ + virtual void removeItem(Element *element) { + if (element != nullptr) + this->m_itemsToRemove.emplace_back(element); + } + + /** + * @brief Try to remove an item from the list + * + * @param index Index of element in list. Call \ref Gui::removeFocus before. + */ + virtual void removeIndex(size_t index) { + if (index < this->m_items.size()) + removeItem(this->m_items[index]); + } + + /** + * @brief Removes all children from the list later on + * @warning When clearing a list, make sure none of the its children are focused. Call \ref Gui::removeFocus before. + */ + void clear() { + this->m_clearList = true; + } + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) override { + Element *newFocus = nullptr; + + if (this->m_clearList || this->m_itemsToAdd.size() > 0) + return nullptr; + + if (direction == FocusDirection::None) { + u16 i = 0; + + if (oldFocus == nullptr) { + s32 elementHeight = 0; + while (elementHeight < this->m_offset && i < this->m_items.size() - 1) { + i++; + elementHeight += this->m_items[i]->getHeight(); + } + } + + for (; i < this->m_items.size(); i++) { + newFocus = this->m_items[i]->requestFocus(oldFocus, direction); + + if (newFocus != nullptr) { + this->m_focusedIndex = i; + + this->updateScrollOffset(); + return newFocus; + } + } + } else { + if (direction == FocusDirection::Down) { + + for (u16 i = this->m_focusedIndex + 1; i < this->m_items.size(); i++) { + newFocus = this->m_items[i]->requestFocus(oldFocus, direction); + + if (newFocus != nullptr && newFocus != oldFocus) { + this->m_focusedIndex = i; + + this->updateScrollOffset(); + return newFocus; + } + } + + return oldFocus; + } else if (direction == FocusDirection::Up) { + if (this->m_focusedIndex > 0) { + + for (u16 i = this->m_focusedIndex - 1; i >= 0; i--) { + if (i > this->m_items.size() || this->m_items[i] == nullptr) + return oldFocus; + else + newFocus = this->m_items[i]->requestFocus(oldFocus, direction); + + if (newFocus != nullptr && newFocus != oldFocus) { + this->m_focusedIndex = i; + + this->updateScrollOffset(); + return newFocus; + } + } + } + + return oldFocus; + } + } + + return oldFocus; + } + + /** + * @brief Gets the item at the index in the list + * + * @param index Index position in list + * @return Element from list. nullptr for if the index is out of bounds + */ + virtual Element* getItemAtIndex(u32 index) { + if (this->m_items.size() <= index) + return nullptr; + + return this->m_items[index]; + } + + /** + * @brief Gets the index in the list of the element passed in + * + * @param element Element to check + * @return Index in list. -1 for if the element isn't a member of the list + */ + virtual s32 getIndexInList(Element *element) { + auto it = std::find(this->m_items.begin(), this->m_items.end(), element); + + if (it == this->m_items.end()) + return -1; + + return it - this->m_items.begin(); + } + + virtual void setFocusedIndex(u32 index) { + if (this->m_items.size() > index) { + m_focusedIndex = index; + this->updateScrollOffset(); + } + } + + protected: + std::vector m_items; + u16 m_focusedIndex = 0; + + float m_offset = 0, m_nextOffset = 0; + s32 m_listHeight = 0; + + bool m_clearList = false; + std::vector m_itemsToRemove; + std::vector> m_itemsToAdd; + + private: + + virtual void updateScrollOffset() { + if (this->getInputMode() != InputMode::Controller) + return; + + if (this->m_listHeight <= this->getHeight()) { + this->m_nextOffset = 0; + this->m_offset = 0; + + return; + } + + this->m_nextOffset = 0; + for (u16 i = 0; i < this->m_focusedIndex; i++) + this->m_nextOffset += this->m_items[i]->getHeight(); + + this->m_nextOffset -= this->getHeight() / 3; + + if (this->m_nextOffset < 0) + this->m_nextOffset = 0; + + if (this->m_nextOffset > (this->m_listHeight - this->getHeight()) + 50) + this->m_nextOffset = (this->m_listHeight - this->getHeight() + 50); + } + }; + + /** + * @brief A item that goes into a list + * + */ + class ListItem : public Element { + public: + /** + * @brief Constructor + * + * @param text Initial description text + */ + ListItem(const std::string& text, const std::string& value = "") + : Element(), m_text(text), m_value(value) { + } + virtual ~ListItem() {} + + virtual void draw(gfx::Renderer *renderer) override { + if (this->m_touched && Element::getInputMode() == InputMode::Touch) { + renderer->drawRect(ELEMENT_BOUNDS(this), a(tsl::style::color::ColorClickAnimation)); + } + + if (this->m_maxWidth == 0) { + if (this->m_value.length() > 0) { + auto [valueWidth, valueHeight] = renderer->drawString(this->m_value.c_str(), false, 0, 0, 20, tsl::style::color::ColorTransparent); + this->m_maxWidth = this->getWidth() - valueWidth - 70; + } else { + this->m_maxWidth = this->getWidth() - 40; + } + + auto [width, height] = renderer->drawString(this->m_text.c_str(), false, 0, 0, 23, tsl::style::color::ColorTransparent); + this->m_trunctuated = width > this->m_maxWidth; + + if (this->m_trunctuated) { + this->m_scrollText = this->m_text + " "; + auto [width, height] = renderer->drawString(this->m_scrollText.c_str(), false, 0, 0, 23, tsl::style::color::ColorTransparent); + this->m_scrollText += this->m_text; + this->m_textWidth = width; + this->m_ellipsisText = renderer->limitStringLength(this->m_text, false, 22, this->m_maxWidth); + } else { + this->m_textWidth = width; + } + } + + renderer->drawRect(this->getX(), this->getY(), this->getWidth(), 1, a(tsl::style::color::ColorFrame)); + renderer->drawRect(this->getX(), this->getTopBound(), this->getWidth(), 1, a(tsl::style::color::ColorFrame)); + + if (this->m_trunctuated) { + if (this->m_focused) { + renderer->enableScissoring(this->getX(), this->getY(), this->m_maxWidth + 40, this->getHeight()); + renderer->drawString(this->m_scrollText.c_str(), false, this->getX() + 20 - this->m_scrollOffset, this->getY() + 45, 23, tsl::style::color::ColorText); + renderer->disableScissoring(); + if (this->m_scrollAnimationCounter == 90) { + if (this->m_scrollOffset == this->m_textWidth) { + this->m_scrollOffset = 0; + this->m_scrollAnimationCounter = 0; + } else { + this->m_scrollOffset++; + } + } else { + this->m_scrollAnimationCounter++; + } + } else { + renderer->drawString(this->m_ellipsisText.c_str(), false, this->getX() + 20, this->getY() + 45, 23, a(tsl::style::color::ColorText)); + } + } else { + renderer->drawString(this->m_text.c_str(), false, this->getX() + 20, this->getY() + 45, 23, a(tsl::style::color::ColorText)); + } + + renderer->drawString(this->m_value.c_str(), false, this->getX() + this->m_maxWidth + 45, this->getY() + 45, 20, this->m_faint ? a(tsl::style::color::ColorDescription) : a(tsl::style::color::ColorHighlight)); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + this->setBoundaries(this->getX(), this->getY(), this->getWidth(), tsl::style::ListItemDefaultHeight); + } + + virtual bool onClick(u64 keys) override { + if (keys & HidNpadButton_A) + this->triggerClickAnimation(); + else if (keys & (HidNpadButton_AnyUp | HidNpadButton_AnyDown | HidNpadButton_AnyLeft | HidNpadButton_AnyRight)) + this->m_clickAnimationProgress = 0; + + return Element::onClick(keys); + } + + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) override { + if (event == TouchEvent::Touch) + this->m_touched = this->inBounds(currX, currY); + + if (event == TouchEvent::Release && this->m_touched) { + this->m_touched = false; + + if (Element::getInputMode() == InputMode::Touch) { + bool handled = this->onClick(HidNpadButton_A); + + this->m_clickAnimationProgress = 0; + return handled; + } + } + + + return false; + } + + + virtual void setFocused(bool state) override { + this->m_scroll = false; + this->m_scrollOffset = 0; + this->m_scrollAnimationCounter = 0; + Element::setFocused(state); + } + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) override { + return this; + } + + /** + * @brief Sets the left hand description text of the list item + * + * @param text Text + */ + inline void setText(const std::string& text) { + this->m_text = text; + this->m_scrollText = ""; + this->m_ellipsisText = ""; + this->m_maxWidth = 0; + } + + /** + * @brief Sets the right hand value text of the list item + * + * @param value Text + * @param faint Should the text be drawn in a glowing green or a faint gray + */ + inline void setValue(const std::string& value, bool faint = false) { + this->m_value = value; + this->m_faint = faint; + this->m_maxWidth = 0; + } + + /** + * @brief Gets the left hand description text of the list item + * + * @return Text + */ + inline const std::string& getText() const { + return this->m_text; + } + + /** + * @brief Gets the right hand value text of the list item + * + * @return Value + */ + inline const std::string& getValue() { + return this->m_value; + } + + protected: + std::string m_text; + std::string m_value = ""; + std::string m_scrollText = ""; + std::string m_ellipsisText = ""; + + bool m_scroll = false; + bool m_trunctuated = false; + bool m_faint = false; + + bool m_touched = false; + + u16 m_maxScroll = 0; + u16 m_scrollOffset = 0; + u32 m_maxWidth = 0; + u32 m_textWidth = 0; + u16 m_scrollAnimationCounter = 0; + }; + + /** + * @brief A toggleable list item that changes the state from On to Off when the A button gets pressed + * + */ + class ToggleListItem : public ListItem { + public: + /** + * @brief Constructor + * + * @param text Initial description text + * @param initialState Is the toggle set to On or Off initially + * @param onValue Value drawn if the toggle is on + * @param offValue Value drawn if the toggle is off + */ + ToggleListItem(const std::string& text, bool initialState, const std::string& onValue = "On", const std::string& offValue = "Off") + : ListItem(text), m_state(initialState), m_onValue(onValue), m_offValue(offValue) { + + this->setState(this->m_state); + } + + virtual ~ToggleListItem() {} + + virtual bool onClick(u64 keys) override { + if (keys & HidNpadButton_A) { + this->m_state = !this->m_state; + + this->setState(this->m_state); + this->m_stateChangedListener(this->m_state); + + return ListItem::onClick(keys); + } + + return false; + } + + /** + * @brief Gets the current state of the toggle + * + * @return State + */ + virtual inline bool getState() { + return this->m_state; + } + + /** + * @brief Sets the current state of the toggle. Updates the Value + * + * @param state State + */ + virtual void setState(bool state) { + this->m_state = state; + + if (state) + this->setValue(this->m_onValue, false); + else + this->setValue(this->m_offValue, true); + } + + /** + * @brief Adds a listener that gets called whenever the state of the toggle changes + * + * @param stateChangedListener Listener with the current state passed in as parameter + */ + void setStateChangedListener(std::function stateChangedListener) { + this->m_stateChangedListener = stateChangedListener; + } + + protected: + bool m_state = true; + std::string m_onValue, m_offValue; + + std::function m_stateChangedListener = [](bool){}; + }; + + class CategoryHeader : public Element { + public: + CategoryHeader(const std::string &title, bool hasSeparator = false) : m_text(title), m_hasSeparator(hasSeparator) {} + virtual ~CategoryHeader() {} + + virtual void draw(gfx::Renderer *renderer) override { + renderer->drawRect(this->getX() - 2, this->getBottomBound() - 30, 5, 23, a(tsl::style::color::ColorHeaderBar)); + renderer->drawString(this->m_text.c_str(), false, this->getX() + 13, this->getBottomBound() - 12, 15, a(tsl::style::color::ColorText)); + + if (this->m_hasSeparator) + renderer->drawRect(this->getX(), this->getBottomBound(), this->getWidth(), 1, a(tsl::style::color::ColorFrame)); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + // Check if the CategoryHeader is part of a list and if it's the first entry in it, half it's height + if (List *list = dynamic_cast(this->getParent()); list != nullptr) { + if (list->getIndexInList(this) == 0) { + this->setBoundaries(this->getX(), this->getY(), this->getWidth(), tsl::style::ListItemDefaultHeight / 2); + return; + } + } + + this->setBoundaries(this->getX(), this->getY(), this->getWidth(), tsl::style::ListItemDefaultHeight); + } + + virtual bool onClick(u64 keys) { + return false; + } + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) override { + return nullptr; + } + + inline void setText(const std::string &text) { + this->m_text = text; + } + + inline const std::string& getText() const { + return this->m_text; + } + + private: + std::string m_text; + bool m_hasSeparator; + }; + + /** + * @brief A customizable analog trackbar going from 0% to 100% (like the brightness slider) + * + */ + class TrackBar : public Element { + public: + /** + * @brief Constructor + * + * @param icon Icon shown next to the track bar + */ + TrackBar(const char icon[3]) : m_icon(icon) { } + + virtual ~TrackBar() {} + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) { + return this; + } + + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState leftJoyStick, HidAnalogStickState rightJoyStick) override { + if (keysHeld & HidNpadButton_AnyLeft && keysHeld & HidNpadButton_AnyRight) + return true; + + if (keysHeld & HidNpadButton_AnyLeft) { + if (this->m_value > 0) { + this->m_value--; + this->m_valueChangedListener(this->m_value); + return true; + } + } + + if (keysHeld & HidNpadButton_AnyRight) { + if (this->m_value < 100) { + this->m_value++; + this->m_valueChangedListener(this->m_value); + return true; + } + } + + return false; + } + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) override { + if (event == TouchEvent::Release) { + this->m_interactionLocked = false; + return false; + } + + + if (!this->m_interactionLocked && this->inBounds(initialX, initialY)) { + if (currX > this->getLeftBound() + 50 && currX < this->getRightBound() && currY > this->getTopBound() && currY < this->getBottomBound()) { + s16 newValue = (static_cast(currX - (this->getX() + 60)) / static_cast(this->getWidth() - 95)) * 100; + + if (newValue < 0) { + newValue = 0; + } else if (newValue > 100) { + newValue = 100; + } + + if (newValue != this->m_value) { + this->m_value = newValue; + this->m_valueChangedListener(this->getProgress()); + } + + return true; + } + } + else + this->m_interactionLocked = true; + + return false; + } + + virtual void draw(gfx::Renderer *renderer) override { + renderer->drawRect(this->getX(), this->getY(), this->getWidth(), 1, a(tsl::style::color::ColorFrame)); + renderer->drawRect(this->getX(), this->getBottomBound(), this->getWidth(), 1, a(tsl::style::color::ColorFrame)); + + renderer->drawString(this->m_icon, false, this->getX() + 15, this->getY() + 50, 23, a(tsl::style::color::ColorText)); + + u16 handlePos = (this->getWidth() - 95) * static_cast(this->m_value) / 100; + renderer->drawCircle(this->getX() + 60, this->getY() + 42, 2, true, a(tsl::style::color::ColorHighlight)); + renderer->drawCircle(this->getX() + 60 + this->getWidth() - 95, this->getY() + 42, 2, true, a(tsl::style::color::ColorFrame)); + renderer->drawRect(this->getX() + 60 + handlePos, this->getY() + 40, this->getWidth() - 95 - handlePos, 5, a(tsl::style::color::ColorFrame)); + renderer->drawRect(this->getX() + 60, this->getY() + 40, handlePos, 5, a(tsl::style::color::ColorHighlight)); + + renderer->drawCircle(this->getX() + 62 + handlePos, this->getY() + 42, 18, true, a(tsl::style::color::ColorHandle)); + renderer->drawCircle(this->getX() + 62 + handlePos, this->getY() + 42, 18, false, a(tsl::style::color::ColorFrame)); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + this->setBoundaries(this->getX(), this->getY(), this->getWidth(), tsl::style::TrackBarDefaultHeight); + } + + virtual void drawFocusBackground(gfx::Renderer *renderer) { + // No background drawn here in HOS + } + + virtual void drawHighlight(gfx::Renderer *renderer) override { + static float counter = 0; + const float progress = (std::sin(counter) + 1) / 2; + Color highlightColor = { static_cast((0x2 - 0x8) * progress + 0x8), + static_cast((0x8 - 0xF) * progress + 0xF), + static_cast((0xC - 0xF) * progress + 0xF), + static_cast((0x6 - 0xD) * progress + 0xD) }; + + counter += 0.1F; + + u16 handlePos = (this->getWidth() - 95) * static_cast(this->m_value) / 100; + + s32 x = 0; + s32 y = 0; + + if (Element::m_highlightShaking) { + auto t = (std::chrono::system_clock::now() - Element::m_highlightShakingStartTime); + if (t >= 100ms) + Element::m_highlightShaking = false; + else { + s32 amplitude = std::rand() % 5 + 5; + + switch (Element::m_highlightShakingDirection) { + case FocusDirection::Up: + y -= shakeAnimation(t, amplitude); + break; + case FocusDirection::Down: + y += shakeAnimation(t, amplitude); + break; + case FocusDirection::Left: + x -= shakeAnimation(t, amplitude); + break; + case FocusDirection::Right: + x += shakeAnimation(t, amplitude); + break; + default: + break; + } + + x = std::clamp(x, -amplitude, amplitude); + y = std::clamp(y, -amplitude, amplitude); + } + } + + for (u8 i = 16; i <= 19; i++) { + renderer->drawCircle(this->getX() + 62 + x + handlePos, this->getY() + 42 + y, i, false, a(highlightColor)); + } + } + + /** + * @brief Gets the current value of the trackbar + * + * @return State + */ + virtual inline u8 getProgress() { + return this->m_value; + } + + /** + * @brief Sets the current state of the toggle. Updates the Value + * + * @param state State + */ + virtual void setProgress(u8 value) { + this->m_value = value; + } + + /** + * @brief Adds a listener that gets called whenever the state of the toggle changes + * + * @param stateChangedListener Listener with the current state passed in as parameter + */ + void setValueChangedListener(std::function valueChangedListener) { + this->m_valueChangedListener = valueChangedListener; + } + + protected: + const char *m_icon = nullptr; + s16 m_value = 0; + bool m_interactionLocked = false; + + std::function m_valueChangedListener = [](u8){}; + }; + + + /** + * @brief A customizable analog trackbar going from 0% to 100% but using discrete steps (Like the volume slider) + * + */ + class StepTrackBar : public TrackBar { + public: + /** + * @brief Constructor + * + * @param icon Icon shown next to the track bar + * @param numSteps Number of steps the track bar has + */ + StepTrackBar(const char icon[3], size_t numSteps) + : TrackBar(icon), m_numSteps(numSteps) { } + + virtual ~StepTrackBar() {} + + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState leftJoyStick, HidAnalogStickState rightJoyStick) override { + static u32 tick = 0; + + if (keysHeld & HidNpadButton_AnyLeft && keysHeld & HidNpadButton_AnyRight) { + tick = 0; + return true; + } + + if (keysHeld & (HidNpadButton_AnyLeft | HidNpadButton_AnyRight)) { + if ((tick == 0 || tick > 20) && (tick % 3) == 0) { + if (keysHeld & HidNpadButton_AnyLeft && this->m_value > 0) { + this->m_value = std::max(this->m_value - (100 / (this->m_numSteps - 1)), 0); + } else if (keysHeld & HidNpadButton_AnyRight && this->m_value < 100) { + this->m_value = std::min(this->m_value + (100 / (this->m_numSteps - 1)), 100); + } else { + return false; + } + this->m_valueChangedListener(this->getProgress()); + } + tick++; + return true; + } else { + tick = 0; + } + + return false; + } + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) override { + if (this->inBounds(initialX, initialY)) { + if (currY > this->getTopBound() && currY < this->getBottomBound()) { + s16 newValue = (static_cast(currX - (this->getX() + 60)) / static_cast(this->getWidth() - 95)) * 100; + + if (newValue < 0) { + newValue = 0; + } else if (newValue > 100) { + newValue = 100; + } else { + newValue = std::round(newValue / (100.0F / (this->m_numSteps - 1))) * (100.0F / (this->m_numSteps - 1)); + } + + if (newValue != this->m_value) { + this->m_value = newValue; + this->m_valueChangedListener(this->getProgress()); + } + + return true; + } + } + + return false; + } + + /** + * @brief Gets the current value of the trackbar + * + * @return State + */ + virtual inline u8 getProgress() override { + return this->m_value / (100 / (this->m_numSteps - 1)); + } + + /** + * @brief Sets the current state of the toggle. Updates the Value + * + * @param state State + */ + virtual void setProgress(u8 value) override { + value = std::min(value, u8(this->m_numSteps - 1)); + this->m_value = value * (100 / (this->m_numSteps - 1)); + } + + protected: + u8 m_numSteps = 1; + }; + + + /** + * @brief A customizable trackbar with multiple discrete steps with specific names. Name gets displayed above the bar + * + */ + class NamedStepTrackBar : public StepTrackBar { + public: + /** + * @brief Constructor + * + * @param icon Icon shown next to the track bar + * @param stepDescriptions Step names displayed above the track bar + */ + NamedStepTrackBar(const char icon[3], std::initializer_list stepDescriptions) + : StepTrackBar(icon, stepDescriptions.size()), m_stepDescriptions(stepDescriptions.begin(), stepDescriptions.end()) { } + + virtual ~NamedStepTrackBar() {} + + virtual void draw(gfx::Renderer *renderer) override { + + u16 trackBarWidth = this->getWidth() - 95; + u16 stepWidth = trackBarWidth / (this->m_numSteps - 1); + + for (u8 i = 0; i < this->m_numSteps; i++) { + renderer->drawRect(this->getX() + 60 + stepWidth * i, this->getY() + 50, 1, 10, a(tsl::style::color::ColorFrame)); + } + + u8 currentDescIndex = std::clamp(this->m_value / (100 / (this->m_numSteps - 1)), 0, this->m_numSteps - 1); + + auto [descWidth, descHeight] = renderer->drawString(this->m_stepDescriptions[currentDescIndex].c_str(), false, 0, 0, 15, tsl::style::color::ColorTransparent); + renderer->drawString(this->m_stepDescriptions[currentDescIndex].c_str(), false, ((this->getX() + 60) + (this->getWidth() - 95) / 2) - (descWidth / 2), this->getY() + 20, 15, a(tsl::style::color::ColorDescription)); + + StepTrackBar::draw(renderer); + } + + protected: + std::vector m_stepDescriptions; + }; + + } + + // GUI + + /** + * @brief The top level Gui class + * @note The main menu and every sub menu are a separate Gui. Create your own Gui class that extends from this one to create your own menus + * + */ + class Gui { + public: + Gui() { } + + virtual ~Gui() { + if (this->m_topElement != nullptr) + delete this->m_topElement; + } + + /** + * @brief Creates all elements present in this Gui + * @note Implement this function and let it return a heap allocated element used as the top level element. This is usually some kind of frame e.g \ref OverlayFrame + * + * @return Top level element + */ + virtual elm::Element* createUI() = 0; + + /** + * @brief Called once per frame to update values + * + */ + virtual void update() {} + + /** + * @brief Called once per frame with the latest HID inputs + * + * @param keysDown Buttons pressed in the last frame + * @param keysHeld Buttons held down longer than one frame + * @param touchInput Last touch position + * @param leftJoyStick Left joystick position + * @param rightJoyStick Right joystick position + * @return Weather or not the input has been consumed + */ + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState leftJoyStick, HidAnalogStickState rightJoyStick) { + return false; + } + + /** + * @brief Gets the top level element + * + * @return Top level element + */ + elm::Element* getTopElement() { + return this->m_topElement; + } + + /** + * @brief Get the currently focused element + * + * @return Focused element + */ + elm::Element* getFocusedElement() { + return this->m_focusedElement; + } + + /** + * @brief Requests focus to a element + * @note Use this function when focusing a element outside of a element's requestFocus function + * + * @param element Element to focus + * @param direction Focus direction + */ + void requestFocus(elm::Element *element, FocusDirection direction, bool shake = true) { + elm::Element *oldFocus = this->m_focusedElement; + + if (element != nullptr) { + this->m_focusedElement = element->requestFocus(oldFocus, direction); + + if (oldFocus != nullptr) + oldFocus->setFocused(false); + + if (this->m_focusedElement != nullptr) { + this->m_focusedElement->setFocused(true); + } + } + + if (shake && oldFocus == this->m_focusedElement && this->m_focusedElement != nullptr) + this->m_focusedElement->shakeHighlight(direction); + } + + /** + * @brief Removes focus from a element + * + * @param element Element to remove focus from. Pass nullptr to remove the focus unconditionally + */ + void removeFocus(elm::Element* element = nullptr) { + if (element == nullptr || element == this->m_focusedElement) { + if (this->m_focusedElement != nullptr) { + this->m_focusedElement->setFocused(false); + this->m_focusedElement = nullptr; + } + } + } + + void restoreFocus() { + this->m_initialFocusSet = false; + } + + protected: + constexpr static inline auto a = &gfx::Renderer::a; + + private: + elm::Element *m_focusedElement = nullptr; + elm::Element *m_topElement = nullptr; + + bool m_initialFocusSet = false; + + friend class Overlay; + friend class gfx::Renderer; + + /** + * @brief Draws the Gui + * + * @param renderer + */ + void draw(gfx::Renderer *renderer) { + if (this->m_topElement != nullptr) + this->m_topElement->draw(renderer); + } + + bool initialFocusSet() { + return this->m_initialFocusSet; + } + + void markInitialFocusSet() { + this->m_initialFocusSet = true; + } + + }; + + + // Overlay + + /** + * @brief The top level Overlay class + * @note Every Tesla overlay should have exactly one Overlay class initializing services and loading the default Gui + */ + class Overlay { + protected: + /** + * @brief Constructor + * @note Called once when the Overlay gets loaded + */ + Overlay() {} + public: + /** + * @brief Deconstructor + * @note Called once when the Overlay exits + * + */ + virtual ~Overlay() {} + + /** + * @brief Initializes services + * @note Called once at the start to initializes services. You have a sm session available during this call, no need to initialize sm yourself + */ + virtual void initServices() {} + + /** + * @brief Exits services + * @note Make sure to exit all services you initialized in \ref Overlay::initServices() here to prevent leaking handles + */ + virtual void exitServices() {} + + /** + * @brief Called before overlay changes from invisible to visible state + * + */ + virtual void onShow() {} + + /** + * @brief Called before overlay changes from visible to invisible state + * + */ + virtual void onHide() {} + + /** + * @brief Loads the default Gui + * @note This function should return the initial Gui to load using the \ref Gui::initially(Args.. args) function + * e.g `return initially();` + * + * @return Default Gui + */ + virtual std::unique_ptr loadInitialGui() = 0; + + /** + * @brief Gets a reference to the current Gui on top of the Gui stack + * + * @return Current Gui reference + */ + std::unique_ptr& getCurrentGui() { + return this->m_guiStack.top(); + } + + /** + * @brief Shows the Gui + * + */ + void show() { + if (this->m_disableNextAnimation) { + this->m_animationCounter = 5; + this->m_disableNextAnimation = false; + } + else { + this->m_fadeInAnimationPlaying = true; + this->m_animationCounter = 0; + } + + this->onShow(); + + if (auto& currGui = this->getCurrentGui(); currGui != nullptr) + currGui->restoreFocus(); + } + + /** + * @brief Hides the Gui + * + */ + void hide() { + if (this->m_disableNextAnimation) { + this->m_animationCounter = 0; + this->m_disableNextAnimation = false; + } + else { + this->m_fadeOutAnimationPlaying = true; + this->m_animationCounter = 5; + } + + this->onHide(); + } + + /** + * @brief Returns whether fade animation is playing + * + * @return whether fade animation is playing + */ + bool fadeAnimationPlaying() { + return this->m_fadeInAnimationPlaying || this->m_fadeOutAnimationPlaying; + } + + /** + * @brief Closes the Gui + * @note This makes the Tesla overlay exit and return back to the Tesla-Menu + * + */ + void close() { + this->m_shouldClose = true; + } + + /** + * @brief Gets the Overlay instance + * + * @return Overlay instance + */ + static inline Overlay* const get() { + return Overlay::s_overlayInstance; + } + + /** + * @brief Creates the initial Gui of an Overlay and moves the object to the Gui stack + * + * @tparam T + * @tparam Args + * @param args + * @return constexpr std::unique_ptr + */ + template + constexpr inline std::unique_ptr initially(Args&&... args) { + return std::move(std::make_unique(args...)); + } + + private: + using GuiPtr = std::unique_ptr; + std::stack> m_guiStack; + static inline Overlay *s_overlayInstance = nullptr; + + bool m_fadeInAnimationPlaying = true, m_fadeOutAnimationPlaying = false; + u8 m_animationCounter = 0; + + bool m_shouldHide = false; + bool m_shouldClose = false; + + bool m_disableNextAnimation = false; + + bool m_closeOnExit; + + /** + * @brief Initializes the Renderer + * + */ + void initScreen() { + gfx::Renderer::get().init(); + } + + /** + * @brief Exits the Renderer + * + */ + void exitScreen() { + gfx::Renderer::get().exit(); + } + + /** + * @brief Weather or not the Gui should get hidden + * + * @return should hide + */ + bool shouldHide() { + return this->m_shouldHide; + } + + /** + * @brief Weather or not hte Gui should get closed + * + * @return should close + */ + bool shouldClose() { + return this->m_shouldClose; + } + + /** + * @brief Handles fade in and fade out animations of the Overlay + * + */ + void animationLoop() { + if (this->m_fadeInAnimationPlaying) { + this->m_animationCounter++; + + if (this->m_animationCounter >= 5) + this->m_fadeInAnimationPlaying = false; + } + + if (this->m_fadeOutAnimationPlaying) { + this->m_animationCounter--; + + if (this->m_animationCounter == 0) { + this->m_fadeOutAnimationPlaying = false; + this->m_shouldHide = true; + } + } + + gfx::Renderer::setOpacity(0.2 * this->m_animationCounter); + } + + /** + * @brief Main loop + * + */ + void loop() { + auto& renderer = gfx::Renderer::get(); + + renderer.startFrame(); + + this->animationLoop(); + this->getCurrentGui()->update(); + this->getCurrentGui()->draw(&renderer); + + renderer.endFrame(); + } + + /** + * @brief Called once per frame with the latest HID inputs + * + * @param keysDown Buttons pressed in the last frame + * @param keysHeld Buttons held down longer than one frame + * @param touchInput Last touch position + * @param leftJoyStick Left joystick position + * @param rightJoyStick Right joystick position + * @return Weather or not the input has been consumed + */ + void handleInput(u64 keysDown, u64 keysHeld, bool touchDetected, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) { + static HidTouchState initialTouchPos = { 0 }; + static HidTouchState oldTouchPos = { 0 }; + static bool oldTouchDetected = false; + static elm::TouchEvent touchEvent; + static u32 repeatTick = 0; + + auto& currentGui = this->getCurrentGui(); + + if (currentGui == nullptr) + return; + + auto currentFocus = currentGui->getFocusedElement(); + auto topElement = currentGui->getTopElement(); + + if (currentFocus == nullptr) { + if (keysDown & HidNpadButton_B) { + if (!currentGui->handleInput(HidNpadButton_B, 0,{},{},{})) + this->goBack(); + return; + } + + if (topElement == nullptr) + return; + else if (currentGui != nullptr) { + if (!currentGui->initialFocusSet() || keysDown & (HidNpadButton_AnyUp | HidNpadButton_AnyDown | HidNpadButton_AnyLeft | HidNpadButton_AnyRight)) { + currentGui->requestFocus(topElement, FocusDirection::None); + currentGui->markInitialFocusSet(); + repeatTick = 1; + } + } + } + + bool handled = false; + elm::Element *parentElement = currentFocus; + + while (!handled && parentElement != nullptr) { + handled = parentElement->onClick(keysDown); + parentElement = parentElement->getParent(); + } + + parentElement = currentFocus; + while (!handled && parentElement != nullptr) { + handled = parentElement->handleInput(keysDown, keysHeld, touchPos, joyStickPosLeft, joyStickPosRight); + parentElement = parentElement->getParent(); + } + + if (currentGui != this->getCurrentGui()) + return; + + handled = handled | currentGui->handleInput(keysDown, keysHeld, touchPos, joyStickPosLeft, joyStickPosRight); + + if (!handled && currentFocus != nullptr) { + static bool shouldShake = true; + + if ((((keysHeld & HidNpadButton_AnyUp) != 0) + ((keysHeld & HidNpadButton_AnyDown) != 0) + ((keysHeld & HidNpadButton_AnyLeft) != 0) + ((keysHeld & HidNpadButton_AnyRight) != 0)) == 1) { + if ((repeatTick == 0 || repeatTick > 20) && (repeatTick % 4) == 0) { + if (keysHeld & HidNpadButton_AnyUp) + currentGui->requestFocus(currentFocus->getParent(), FocusDirection::Up, shouldShake); + else if (keysHeld & HidNpadButton_AnyDown) + currentGui->requestFocus(currentFocus->getParent(), FocusDirection::Down, shouldShake); + else if (keysHeld & HidNpadButton_AnyLeft) + currentGui->requestFocus(currentFocus->getParent(), FocusDirection::Left, shouldShake); + else if (keysHeld & HidNpadButton_AnyRight) + currentGui->requestFocus(currentFocus->getParent(), FocusDirection::Right, shouldShake); + + shouldShake = currentGui->getFocusedElement() != currentFocus; + } + repeatTick++; + } else { + if (keysDown & HidNpadButton_B) + this->goBack(); + repeatTick = 0; + shouldShake = true; + } + } + + if (!touchDetected && oldTouchDetected) { + if (currentGui != nullptr && topElement != nullptr) + topElement->onTouch(elm::TouchEvent::Release, oldTouchPos.x, oldTouchPos.y, oldTouchPos.x, oldTouchPos.y, initialTouchPos.x, initialTouchPos.y); + } + + if (touchDetected) { + + u32 xDistance = std::abs(static_cast(initialTouchPos.x) - static_cast(touchPos.x)); + u32 yDistance = std::abs(static_cast(initialTouchPos.y) - static_cast(touchPos.y)); + + xDistance *= xDistance; + yDistance *= yDistance; + + if ((xDistance + yDistance) > 1000) { + elm::Element::setInputMode(InputMode::TouchScroll); + touchEvent = elm::TouchEvent::Scroll; + } else { + if (touchEvent != elm::TouchEvent::Scroll) + touchEvent = elm::TouchEvent::Hold; + } + + if (!oldTouchDetected) { + initialTouchPos = touchPos; + elm::Element::setInputMode(InputMode::Touch); + currentGui->removeFocus(); + touchEvent = elm::TouchEvent::Touch; + } + + + if (currentGui != nullptr && topElement != nullptr) + topElement->onTouch(touchEvent, touchPos.x, touchPos.y, oldTouchPos.x, oldTouchPos.y, initialTouchPos.x, initialTouchPos.y); + + oldTouchPos = touchPos; + + // Hide overlay when touching out of bounds + if (touchPos.x >= cfg::FramebufferWidth) { + if (tsl::elm::Element::getInputMode() == tsl::InputMode::Touch) { + oldTouchPos = { 0 }; + initialTouchPos = { 0 }; + + this->hide(); + } + } + } else { + if (oldTouchPos.x < 150U && oldTouchPos.y > cfg::FramebufferHeight - 73U) + if (initialTouchPos.x < 150U && initialTouchPos.y > cfg::FramebufferHeight - 73U) + if (!currentGui->handleInput(HidNpadButton_B, 0,{},{},{})) + this->goBack(); + + elm::Element::setInputMode(InputMode::Controller); + + oldTouchPos = { 0 }; + initialTouchPos = { 0 }; + } + + oldTouchDetected = touchDetected; + } + + /** + * @brief Clears the screen + * + */ + void clearScreen() { + auto& renderer = gfx::Renderer::get(); + + renderer.startFrame(); + renderer.clearScreen(); + renderer.endFrame(); + } + + /** + * @brief Reset hide and close flags that were previously set by \ref Overlay::close() or \ref Overlay::hide() + * + */ + void resetFlags() { + this->m_shouldHide = false; + this->m_shouldClose = false; + } + + /** + * @brief Disables the next animation that would play + * + */ + void disableNextAnimation() { + this->m_disableNextAnimation = true; + } + + /** + * @brief Changes to a different Gui + * + * @param gui Gui to change to + * @return Reference to the Gui + */ + std::unique_ptr& changeTo(std::unique_ptr&& gui) { + if (this->m_guiStack.top() != nullptr && this->m_guiStack.top()->m_focusedElement != nullptr) + this->m_guiStack.top()->m_focusedElement->resetClickAnimation(); + + gui->m_topElement = gui->createUI(); + + this->m_guiStack.push(std::move(gui)); + + return this->m_guiStack.top(); + } + + /** + * @brief Creates a new Gui and changes to it + * + * @tparam G Gui to create + * @tparam Args Arguments to pass to the Gui + * @param args Arguments to pass to the Gui + * @return Reference to the newly created Gui + */ + template + std::unique_ptr& changeTo(Args&&... args) { + return this->changeTo(std::make_unique(std::forward(args)...)); + } + + /** + * @brief Pops the top Gui from the stack and goes back to the last one + * @note The Overlay gets closes once there are no more Guis on the stack + */ + void goBack() { + if (!this->m_closeOnExit && this->m_guiStack.size() == 1) { + this->hide(); + return; + } + + if (!this->m_guiStack.empty()) + this->m_guiStack.pop(); + + if (this->m_guiStack.empty()) + this->close(); + } + + template + friend std::unique_ptr& changeTo(Args&&... args); + + friend void goBack(); + + template + friend int loop(int argc, char** argv); + + friend class tsl::Gui; + }; + + + namespace impl { + + /** + * @brief Data shared between the different threads + * + */ + struct SharedThreadData { + bool running = false; + + Event comboEvent = { 0 }; + + bool overlayOpen = false; + + std::mutex dataMutex; + u64 keysDown = 0; + u64 keysDownPending = 0; + u64 keysHeld = 0; + HidTouchScreenState touchState = { 0 }; + HidAnalogStickState joyStickPosLeft = { 0 }, joyStickPosRight = { 0 }; + }; + + + /** + * @brief Extract values from Tesla settings file + * + */ + static void parseOverlaySettings() { + hlp::ini::IniData parsedConfig = hlp::ini::readOverlaySettings(); + + u64 decodedKeys = hlp::comboStringToKeys(parsedConfig["tesla"]["key_combo"]); + if (decodedKeys) + tsl::cfg::launchCombo = decodedKeys; + } + + /** + * @brief Update and save launch combo keys + * + * @param keys the new combo keys + */ + [[maybe_unused]] static void updateCombo(u64 keys) { + tsl::cfg::launchCombo = keys; + hlp::ini::updateOverlaySettings({ + { "tesla", { + { "key_combo", tsl::hlp::keysToComboString(keys) } + }} + }); + } + + /** + * @brief Background event polling loop thread + * + * @param args Used to pass in a pointer to a \ref SharedThreadData struct + */ + static void backgroundEventPoller(void *args) { + SharedThreadData *shData = static_cast(args); + + // To prevent focus glitchout, close the overlay immediately when the home button gets pressed + Event homeButtonPressEvent = {}; + hidsysAcquireHomeButtonEventHandle(&homeButtonPressEvent, false); + eventClear(&homeButtonPressEvent); + hlp::ScopeGuard homeButtonEventGuard([&] { eventClose(&homeButtonPressEvent); }); + + // To prevent focus glitchout, close the overlay immediately when the power button gets pressed + Event powerButtonPressEvent = {}; + hidsysAcquireSleepButtonEventHandle(&powerButtonPressEvent, false); + eventClear(&powerButtonPressEvent); + hlp::ScopeGuard powerButtonEventGuard([&] { eventClose(&powerButtonPressEvent); }); + + // Parse Tesla settings + impl::parseOverlaySettings(); + + // Configure input to take all controllers and up to 8 + padConfigureInput(8, HidNpadStyleSet_NpadStandard | HidNpadStyleTag_NpadSystemExt); + + // Initialize pad + PadState pad; + padInitializeAny(&pad); + + // Initialize touch screen + hidInitializeTouchScreen(); + + // Drop all inputs from the previous overlay + padUpdate(&pad); + + enum WaiterObject { + WaiterObject_HomeButton, + WaiterObject_PowerButton, + + WaiterObject_Count + }; + + // Construct waiter + Waiter objects[2] = { + [WaiterObject_HomeButton] = waiterForEvent(&homeButtonPressEvent), + [WaiterObject_PowerButton] = waiterForEvent(&powerButtonPressEvent), + }; + + while (shData->running) { + // Scan for input changes + padUpdate(&pad); + + // Read in HID values + { + std::scoped_lock lock(shData->dataMutex); + + shData->keysDown = padGetButtonsDown(&pad); + shData->keysHeld = padGetButtons(&pad); + shData->joyStickPosLeft = padGetStickPos(&pad, 0); + shData->joyStickPosRight = padGetStickPos(&pad, 1); + + // Read in touch positions + if (hidGetTouchScreenStates(&shData->touchState, 1) == 0) + shData->touchState = { 0 }; + + if (((shData->keysHeld & tsl::cfg::launchCombo) == tsl::cfg::launchCombo) && shData->keysDown & tsl::cfg::launchCombo) { + if (shData->overlayOpen) { + tsl::Overlay::get()->hide(); + shData->overlayOpen = false; + } + else + eventFire(&shData->comboEvent); + } + + shData->keysDownPending |= shData->keysDown; + } + + //20 ms + s32 idx = 0; + Result rc = waitObjects(&idx, objects, WaiterObject_Count, 20'000'000ul); + if (R_SUCCEEDED(rc)) { + if (shData->overlayOpen) { + tsl::Overlay::get()->hide(); + shData->overlayOpen = false; + } + + switch (idx) { + case WaiterObject_HomeButton: + eventClear(&homeButtonPressEvent); + break; + case WaiterObject_PowerButton: + eventClear(&powerButtonPressEvent); + break; + } + } else if (rc != KERNELRESULT(TimedOut)) { + ASSERT_FATAL(rc); + } + } + } + + } + + /** + * @brief Creates a new Gui and changes to it + * + * @tparam G Gui to create + * @tparam Args Arguments to pass to the Gui + * @param args Arguments to pass to the Gui + * @return Reference to the newly created Gui + */ + template + std::unique_ptr& changeTo(Args&&... args) { + return Overlay::get()->changeTo(std::forward(args)...); + } + + /** + * @brief Pops the top Gui from the stack and goes back to the last one + * @note The Overlay gets closed once there are no more Guis on the stack + */ + static void goBack() { + Overlay::get()->goBack(); + } + + static void setNextOverlay(const std::string& ovlPath, std::string args) { + + args += " --skipCombo"; + + envSetNextLoad(ovlPath.c_str(), args.c_str()); + } + + + + /** + * @brief libtesla's main function + * @note Call it directly from main passing in argc and argv and returning it e.g `return tsl::loop(argc, argv);` + * + * @tparam TOverlay Your overlay class + * @tparam launchFlags \ref LaunchFlags + * @param argc argc + * @param argv argv + * @return int result + */ + template + static inline int loop(int argc, char** argv) { + static_assert(std::is_base_of_v, "tsl::loop expects a type derived from tsl::Overlay"); + + impl::SharedThreadData shData; + + shData.running = true; + + Thread backgroundThread; + threadCreate(&backgroundThread, impl::backgroundEventPoller, &shData, nullptr, 0x1000, 0x2c, -2); + threadStart(&backgroundThread); + + eventCreate(&shData.comboEvent, false); + + auto& overlay = tsl::Overlay::s_overlayInstance; + overlay = new TOverlay(); + overlay->m_closeOnExit = (u8(launchFlags) & u8(impl::LaunchFlags::CloseOnExit)) == u8(impl::LaunchFlags::CloseOnExit); + + tsl::hlp::doWithSmSession([&overlay]{ overlay->initServices(); }); + overlay->initScreen(); + overlay->changeTo(overlay->loadInitialGui()); + + + // Argument parsing + for (u8 arg = 0; arg < argc; arg++) { + if (strcasecmp(argv[arg], "--skipCombo") == 0) { + eventFire(&shData.comboEvent); + overlay->disableNextAnimation(); + } + } + + + while (shData.running) { + + eventWait(&shData.comboEvent, UINT64_MAX); + eventClear(&shData.comboEvent); + shData.overlayOpen = true; + + + hlp::requestForeground(true); + + overlay->show(); + overlay->clearScreen(); + + + while (shData.running) { + overlay->loop(); + + { + std::scoped_lock lock(shData.dataMutex); + if (!overlay->fadeAnimationPlaying()) { + overlay->handleInput(shData.keysDownPending, shData.keysHeld, shData.touchState.count, shData.touchState.touches[0], shData.joyStickPosLeft, shData.joyStickPosRight); + } + shData.keysDownPending = 0; + } + + if (overlay->shouldHide()) + break; + + if (overlay->shouldClose()) + shData.running = false; + } + + overlay->clearScreen(); + overlay->resetFlags(); + + hlp::requestForeground(false); + + shData.overlayOpen = false; + eventClear(&shData.comboEvent); + } + + eventClose(&shData.comboEvent); + + threadWaitForExit(&backgroundThread); + threadClose(&backgroundThread); + + overlay->exitScreen(); + overlay->exitServices(); + + delete overlay; + + return 0; + } + +} + + +#ifdef TESLA_INIT_IMPL + +namespace tsl::cfg { + + u16 LayerWidth = 0; + u16 LayerHeight = 0; + u16 LayerPosX = 0; + u16 LayerPosY = 0; + u16 FramebufferWidth = 0; + u16 FramebufferHeight = 0; + u64 launchCombo = HidNpadButton_L | HidNpadButton_Down | HidNpadButton_StickR; +} + +extern "C" { + + u32 __nx_applet_type = AppletType_None; + u32 __nx_fs_num_sessions = 1; + u32 __nx_nv_transfermem_size = 0x40000; + ViLayerFlags __nx_vi_stray_layer_flags = (ViLayerFlags)0; + + /** + * @brief libtesla service initializing function to override libnx's + * + */ + void __appInit(void) { + tsl::hlp::doWithSmSession([]{ + ASSERT_FATAL(fsInitialize()); + ASSERT_FATAL(hidInitialize()); // Controller inputs and Touch + if (hosversionAtLeast(16,0,0)) { + ASSERT_FATAL(plInitialize(PlServiceType_User)); // Font data. Use pl:u for 16.0.0+ + } else { + ASSERT_FATAL(plInitialize(PlServiceType_System)); // Use pl:s for 15.0.1 and below to prevent qlaunch/overlaydisp session exhaustion + } + ASSERT_FATAL(pmdmntInitialize()); // PID querying + ASSERT_FATAL(hidsysInitialize()); // Focus control + ASSERT_FATAL(setsysInitialize()); // Settings querying + }); + } + + /** + * @brief libtesla service exiting function to override libnx's + * + */ + void __appExit(void) { + fsExit(); + hidExit(); + plExit(); + pmdmntExit(); + hidsysExit(); + setsysExit(); + } + +} + +#endif diff --git a/Source/sys-clk-OC(deprecated)/overlay/scripts/make_logo.sh b/Source/sys-clk-OC(deprecated)/overlay/scripts/make_logo.sh new file mode 100644 index 00000000..bbd01a7f --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/scripts/make_logo.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +DEST="$CURRENT_DIR/../data/logo_rgba.bin" +FONT="$CURRENT_DIR/../../manager/resources/fira/FiraSans-Medium-rnx.ttf" +FONT_SIZE="30.5" +TEXT="sys-clk" + +function render() { + convert -background transparent -colorspace RGB -depth 8 -fill white -font "$1" -pointsize "$2" "label:$3" "$4" +} + +render "$FONT" "$FONT_SIZE" "$TEXT" info: +render "$FONT" "$FONT_SIZE" "$TEXT" "RGBA:$DEST" \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ipc.h b/Source/sys-clk-OC(deprecated)/overlay/src/ipc.h new file mode 100644 index 00000000..3ce147a9 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ipc.h @@ -0,0 +1,14 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include +#include diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/main.cpp b/Source/sys-clk-OC(deprecated)/overlay/src/main.cpp new file mode 100644 index 00000000..1d5fb7a2 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/main.cpp @@ -0,0 +1,78 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#define TESLA_INIT_IMPL +#include + +#include "ui/gui/fatal_gui.h" +#include "ui/gui/main_gui.h" + +class AppOverlay : public tsl::Overlay +{ + public: + AppOverlay() {} + ~AppOverlay() {} + + virtual void exitServices() override { + sysclkIpcExit(); + } + + virtual std::unique_ptr loadInitialGui() override + { + uint32_t apiVersion; + smInitialize(); + + tsl::hlp::ScopeGuard smGuard([] { smExit(); }); + + if(!sysclkIpcRunning()) + { + return initially( + "sys-clk is not running.\n\n" + "\n" + "Please make sure it is correctly\n\n" + "installed and enabled.\n\n" + "\n" + "Loader.kip is required to\n\n" + "use this version of sys-clk.", + "" + ); + } + + if(R_FAILED(sysclkIpcInitialize()) || R_FAILED(sysclkIpcGetAPIVersion(&apiVersion))) + { + return initially( + "Could not connect to sys-clk.\n\n" + "\n" + "Please make sure it is correctly\n\n" + "installed and enabled.", + "" + ); + } + + if(SYSCLK_IPC_API_VERSION != apiVersion) + { + return initially( + "Overlay not compatible with\n\n" + "the running sys-clk version.\n\n" + "\n" + "Please make sure everything is\n\n" + "installed and up to date.", + "" + ); + } + + return initially(); + } +}; + +int main(int argc, char **argv) +{ + return tsl::loop(argc, argv); +} diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/elements/base_frame.h b/Source/sys-clk-OC(deprecated)/overlay/src/ui/elements/base_frame.h new file mode 100644 index 00000000..756fa7dc --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/elements/base_frame.h @@ -0,0 +1,31 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include +#include "../gui/base_gui.h" + +class BaseFrame : public tsl::elm::HeaderOverlayFrame +{ + public: + BaseFrame(BaseGui* gui) : tsl::elm::HeaderOverlayFrame(130) { // headerHeight + this->gui = gui; + } + + void draw(tsl::gfx::Renderer* renderer) override + { + tsl::elm::HeaderOverlayFrame::draw(renderer); + this->gui->preDraw(renderer); + } + + protected: + BaseGui* gui; +}; diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/format.h b/Source/sys-clk-OC(deprecated)/overlay/src/ui/format.h new file mode 100644 index 00000000..ab5f0ed3 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/format.h @@ -0,0 +1,26 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#define FREQ_DEFAULT_TEXT "Do not override" + +static inline std::string formatListFreqMhz(std::uint32_t mhz) +{ + if(mhz == 0) + return FREQ_DEFAULT_TEXT; + + char buf[10]; + return std::string(buf, snprintf(buf, sizeof(buf), "%u MHz", mhz)); +} + +static inline std::string formatListFreqHz(std::uint32_t hz) { return formatListFreqMhz(hz / 1000000); } diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/app_profile_gui.cpp b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/app_profile_gui.cpp new file mode 100644 index 00000000..a1008444 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/app_profile_gui.cpp @@ -0,0 +1,137 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "app_profile_gui.h" + +#include "../format.h" +#include "fatal_gui.h" + +AppProfileGui::AppProfileGui(std::uint64_t applicationId, SysClkTitleProfileList* profileList) +{ + this->applicationId = applicationId; + this->profileList = profileList; +} + +AppProfileGui::~AppProfileGui() +{ + delete this->profileList; +} + +void AppProfileGui::openFreqChoiceGui(tsl::elm::ListItem* listItem, SysClkProfile profile, SysClkModule module) +{ + tsl::changeTo(this->profileList->mhzMap[profile][module], module, profile, [this, listItem, profile, module](std::uint32_t mhz) { + this->profileList->mhzMap[profile][module] = mhz; + listItem->setValue(formatListFreqMhz(this->profileList->mhzMap[profile][module])); + Result rc = sysclkIpcSetProfiles(this->applicationId, this->profileList); + if(R_FAILED(rc)) + { + FatalGui::openWithResultCode("sysclkIpcSetProfiles", rc); + return false; + } + + return true; + }); +} + +void AppProfileGui::addModuleListItem(SysClkProfile profile, SysClkModule module) +{ + tsl::elm::ListItem* listItem = new tsl::elm::ListItem(sysclkFormatModule(module, true)); + listItem->setValue(formatListFreqMhz(this->profileList->mhzMap[profile][module])); + listItem->setClickListener([this, listItem, profile, module](u64 keys) { + if((keys & HidNpadButton_A) == HidNpadButton_A) + { + this->openFreqChoiceGui(listItem, profile, module); + return true; + } + + return false; + }); + + this->listElement->addItem(listItem); +} + +void AppProfileGui::addProfileUI(SysClkProfile profile) +{ + this->listElement->addItem(new tsl::elm::CategoryHeader(sysclkFormatProfile(profile, true))); + this->addModuleListItem(profile, SysClkModule_CPU); + this->addModuleListItem(profile, SysClkModule_GPU); + this->addModuleListItem(profile, SysClkModule_MEM); +} + +void AppProfileGui::listUI() +{ + if (this->applicationId != SYSCLK_GLOBAL_PROFILE_TID) { + SysClkConfigValueList* configList = new SysClkConfigValueList; + sysclkIpcGetConfigValues(configList); + bool globalGovernorEnabled = configList->values[SysClkConfigValue_GovernorExperimental]; + + if (globalGovernorEnabled) { + tsl::elm::ToggleListItem* cpuGovernorToggle = new tsl::elm::ToggleListItem("CPU Freq Governor", + GetGovernorEnabled(this->profileList->governorConfig, SysClkModule_CPU)); + cpuGovernorToggle->setStateChangedListener([this](bool state) { + this->profileList->governorConfig = ToggleGovernor(this->profileList->governorConfig, SysClkModule_CPU, state); + + Result rc = sysclkIpcSetProfiles(this->applicationId, this->profileList); + if (R_FAILED(rc)) + FatalGui::openWithResultCode("sysclkIpcSetProfiles", rc); + }); + this->listElement->addItem(cpuGovernorToggle); + + tsl::elm::ToggleListItem* gpuGovernorToggle = new tsl::elm::ToggleListItem("GPU Freq Governor", + GetGovernorEnabled(this->profileList->governorConfig, SysClkModule_GPU)); + gpuGovernorToggle->setStateChangedListener([this](bool state) { + this->profileList->governorConfig = ToggleGovernor(this->profileList->governorConfig, SysClkModule_GPU, state); + + Result rc = sysclkIpcSetProfiles(this->applicationId, this->profileList); + if (R_FAILED(rc)) + FatalGui::openWithResultCode("sysclkIpcSetProfiles", rc); + }); + this->listElement->addItem(gpuGovernorToggle); + } + + delete configList; + } + + this->addProfileUI(SysClkProfile_Docked); + this->addProfileUI(SysClkProfile_Handheld); + this->addProfileUI(SysClkProfile_HandheldCharging); + this->addProfileUI(SysClkProfile_HandheldChargingOfficial); + this->addProfileUI(SysClkProfile_HandheldChargingUSB); +} + +void AppProfileGui::changeTo(std::uint64_t applicationId) +{ + SysClkTitleProfileList* profileList = new SysClkTitleProfileList; + Result rc = sysclkIpcGetProfiles(applicationId, profileList); + if(R_FAILED(rc)) + { + delete profileList; + FatalGui::openWithResultCode("sysclkIpcGetProfiles", rc); + return; + } + + tsl::changeTo(applicationId, profileList); +} + +void AppProfileGui::update() +{ + BaseMenuGui::update(); + + if(this->context && this->applicationId != SYSCLK_GLOBAL_PROFILE_TID && this->applicationId != this->context->applicationId) + { + tsl::changeTo( + "Application changed\n\n" + "\n" + "The running application changed\n\n" + "while editing was going on.", + "" + ); + } +} diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/app_profile_gui.h b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/app_profile_gui.h new file mode 100644 index 00000000..a492bc5b --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/app_profile_gui.h @@ -0,0 +1,33 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include "../../ipc.h" +#include "base_menu_gui.h" +#include "freq_choice_gui.h" + +class AppProfileGui : public BaseMenuGui +{ + protected: + std::uint64_t applicationId; + SysClkTitleProfileList* profileList; + + void openFreqChoiceGui(tsl::elm::ListItem* listItem, SysClkProfile profile, SysClkModule module); + void addModuleListItem(SysClkProfile profile, SysClkModule module); + void addProfileUI(SysClkProfile profile); + + public: + AppProfileGui(std::uint64_t applicationId, SysClkTitleProfileList* profileList); + ~AppProfileGui(); + void listUI() override; + static void changeTo(std::uint64_t applicationId); + void update() override; +}; diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_gui.cpp b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_gui.cpp new file mode 100644 index 00000000..a5a4c9ee --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_gui.cpp @@ -0,0 +1,40 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "base_gui.h" + +#include "../elements/base_frame.h" +// #include "logo_rgba_bin.h" + +#define LOGO_LABEL_X 40 +#define LOGO_LABEL_Y 35 +#define LOGO_LABEL_FONT_SIZE 20 + +#define VERSION_X 266 +#define VERSION_Y LOGO_LABEL_Y +#define VERSION_FONT_SIZE SMALL_TEXT_SIZE + +void BaseGui::preDraw(tsl::gfx::Renderer* renderer) +{ + renderer->drawString("Sys-clk-OC overlay", false, LOGO_LABEL_X, LOGO_LABEL_Y, LOGO_LABEL_FONT_SIZE, TEXT_COLOR); + renderer->drawString(TARGET_VERSION, false, VERSION_X, VERSION_Y, VERSION_FONT_SIZE, DESC_COLOR); +} + +tsl::elm::Element* BaseGui::createUI() +{ + BaseFrame* rootFrame = new BaseFrame(this); + rootFrame->setContent(this->baseUI()); + return rootFrame; +} + +void BaseGui::update() +{ + this->refresh(); +} diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_gui.h b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_gui.h new file mode 100644 index 00000000..59cb9170 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_gui.h @@ -0,0 +1,27 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#include "../style.h" + +class BaseGui : public tsl::Gui +{ + public: + BaseGui() {} + ~BaseGui() {} + virtual void preDraw(tsl::gfx::Renderer* renderer); + void update() override; + tsl::elm::Element* createUI() override; + virtual tsl::elm::Element* baseUI() = 0; + virtual void refresh() {} +}; diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_menu_gui.cpp b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_menu_gui.cpp new file mode 100644 index 00000000..8fbe3c84 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_menu_gui.cpp @@ -0,0 +1,114 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "base_menu_gui.h" + +#include "fatal_gui.h" + +BaseMenuGui::BaseMenuGui() +{ + this->context = nullptr; + this->lastContextUpdate = 0; + this->listElement = nullptr; +} + +BaseMenuGui::~BaseMenuGui() +{ + if(this->context) + { + delete this->context; + } +} + +void BaseMenuGui::preDraw(tsl::gfx::Renderer* renderer) +{ + BaseGui::preDraw(renderer); + if(this->context) + { + char buf[32]; + + renderer->drawString("App ID: ", false, 40, 60, SMALL_TEXT_SIZE, DESC_COLOR); + snprintf(buf, sizeof(buf), "%016lX", context->applicationId); + renderer->drawString(buf, false, 100, 60, SMALL_TEXT_SIZE, VALUE_COLOR); + + renderer->drawString("Profile: ", false, 266, 60, SMALL_TEXT_SIZE, DESC_COLOR); + renderer->drawString(sysclkFormatProfile(context->profile, true), false, 322, 60, SMALL_TEXT_SIZE, VALUE_COLOR); + + static struct + { + SysClkModule m; + std::uint32_t x; + } freqOffsets[SysClkModule_EnumMax] = { + { SysClkModule_CPU, 80 }, + { SysClkModule_GPU, 200 }, + { SysClkModule_MEM, 320 }, + }; + + for(unsigned int i = 0; i < SysClkModule_EnumMax; i++) + { + std::uint32_t hz = this->context->freqs[freqOffsets[i].m]; + snprintf(buf, sizeof(buf), "%u MHz", hz / 1000000); + renderer->drawString(buf, false, freqOffsets[i].x, 85, SMALL_TEXT_SIZE, VALUE_COLOR); + } + renderer->drawString("CPU:", false, 40, 85, SMALL_TEXT_SIZE, DESC_COLOR); + renderer->drawString("GPU:", false, 160, 85, SMALL_TEXT_SIZE, DESC_COLOR); + renderer->drawString("MEM:", false, 270, 85, SMALL_TEXT_SIZE, DESC_COLOR); + + static struct + { + SysClkThermalSensor s; + std::uint32_t x; + } tempOffsets[SysClkModule_EnumMax] = { + { SysClkThermalSensor_SOC, 80 }, + { SysClkThermalSensor_PCB, 200 }, + { SysClkThermalSensor_Skin, 320 }, + }; + + renderer->drawString("SOC:", false, 40, 110, SMALL_TEXT_SIZE, DESC_COLOR); + renderer->drawString("PCB:", false, 160, 110, SMALL_TEXT_SIZE, DESC_COLOR); + renderer->drawString("Skin:", false, 270, 110, SMALL_TEXT_SIZE, DESC_COLOR); + for(unsigned int i = 0; i < SysClkModule_EnumMax; i++) + { + std::uint32_t millis = this->context->temps[tempOffsets[i].s]; + snprintf(buf, sizeof(buf), "%u.%u °C", millis / 1000, (millis - millis / 1000 * 1000) / 100); + renderer->drawString(buf, false, tempOffsets[i].x, 110, SMALL_TEXT_SIZE, VALUE_COLOR); + } + } +} + +void BaseMenuGui::refresh() +{ + std::uint64_t ticks = armGetSystemTick(); + + if(armTicksToNs(ticks - this->lastContextUpdate) > 500000000UL) + { + this->lastContextUpdate = ticks; + if(!this->context) + { + this->context = new SysClkContext; + } + + Result rc = sysclkIpcGetCurrentContext(this->context); + if(R_FAILED(rc)) + { + FatalGui::openWithResultCode("sysclkIpcGetCurrentContext", rc); + return; + } + } +} + +tsl::elm::Element* BaseMenuGui::baseUI() +{ + tsl::elm::List* list = new tsl::elm::List(); + this->listElement = list; + this->listUI(); + + return list; +} diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_menu_gui.h b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_menu_gui.h new file mode 100644 index 00000000..4b576195 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/base_menu_gui.h @@ -0,0 +1,30 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include "../../ipc.h" +#include "base_gui.h" + +class BaseMenuGui : public BaseGui +{ + protected: + SysClkContext* context; + std::uint64_t lastContextUpdate; + tsl::elm::List* listElement; + + public: + BaseMenuGui(); + ~BaseMenuGui(); + void preDraw(tsl::gfx::Renderer* renderer) override; + tsl::elm::Element* baseUI() override; + void refresh() override; + virtual void listUI() = 0; +}; diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/fatal_gui.cpp b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/fatal_gui.cpp new file mode 100644 index 00000000..264668c7 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/fatal_gui.cpp @@ -0,0 +1,67 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "fatal_gui.h" + +FatalGui::FatalGui(const std::string message, const std::string info) +{ + this->message = message; + this->info = info; +} + +void FatalGui::openWithResultCode(std::string tag, Result rc) +{ + char rcStr[32]; + std::string info = tag; + info.append(rcStr, snprintf(rcStr, sizeof(rcStr), "\n\n[0x%x] %04d-%04d", rc, R_MODULE(rc), R_DESCRIPTION(rc))); + + tsl::changeTo( + "Could not connect to sys-clk.\n\n" + "\n" + "Please make sure everything is\n\n" + "correctly installed and enabled.", + info + ); +} + +tsl::elm::Element* FatalGui::baseUI() +{ + tsl::elm::CustomDrawer* drawer = new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer* renderer, u16 x, u16 y, u16 w, u16 h) { + renderer->drawString("\uE150", false, 40, 210, 40, TEXT_COLOR); + renderer->drawString("Fatal error", false, 100, 210, 30, TEXT_COLOR); + + std::uint32_t txtY = 255; + if(!this->message.empty()) + { + txtY += renderer->drawString(this->message.c_str(), false, 40, txtY, 23, TEXT_COLOR).second; + txtY += 55; + } + + if(!this->info.empty()) + { + renderer->drawString(this->info.c_str(), false, 40, txtY, 18, DESC_COLOR); + } + }); + + return drawer; +} + +bool FatalGui::handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) +{ + if((keysDown & HidNpadButton_A) == HidNpadButton_A || (keysDown & HidNpadButton_B) == HidNpadButton_B) + { + while(tsl::Overlay::get()->getCurrentGui() != nullptr) { + tsl::goBack(); + } + return true; + } + + return false; +} diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/fatal_gui.h b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/fatal_gui.h new file mode 100644 index 00000000..e20b6cc9 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/fatal_gui.h @@ -0,0 +1,29 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#include "base_gui.h" + +class FatalGui : public BaseGui +{ + protected: + std::string message; + std::string info; + + public: + FatalGui(const std::string message, const std::string info); + ~FatalGui() {} + tsl::elm::Element* baseUI() override; + bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight); + static void openWithResultCode(std::string tag, Result rc); +}; diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/freq_choice_gui.cpp b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/freq_choice_gui.cpp new file mode 100644 index 00000000..9a1c6a39 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/freq_choice_gui.cpp @@ -0,0 +1,65 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "freq_choice_gui.h" + +#include "../format.h" +#include "fatal_gui.h" + +FreqChoiceGui::FreqChoiceGui(std::uint32_t selectedMHz, SysClkModule module, SysClkProfile profile, FreqChoiceListener listener) +{ + this->hzTable = new SysClkFrequencyTable; + Result rc = sysclkIpcGetFrequencyTable(module, profile, hzTable); + if (R_FAILED(rc)) { + FatalGui::openWithResultCode("sysclkIpcGetFrequencyTable", rc); + } + + this->selectedMHz = selectedMHz; + this->listener = listener; +} + +FreqChoiceGui::~FreqChoiceGui() { + delete this->hzTable; +} + +tsl::elm::ListItem* FreqChoiceGui::createFreqListItem(std::uint32_t mhz, bool selected) +{ + tsl::elm::ListItem* listItem = new tsl::elm::ListItem(formatListFreqMhz(mhz)); + listItem->setValue(selected ? "\uE14B" : ""); + + listItem->setClickListener([this, mhz](u64 keys) { + if((keys & HidNpadButton_A) == HidNpadButton_A && this->listener) + { + if(this->listener(mhz)) + { + tsl::goBack(); + } + return true; + } + + return false; + }); + + return listItem; +} + +void FreqChoiceGui::listUI() +{ + this->listElement->addItem(this->createFreqListItem(0, this->selectedMHz == 0)); + + size_t idx = 0; + uint32_t freq; + while(idx < FREQ_TABLE_MAX_ENTRY_COUNT && (freq = this->hzTable->freq[idx])) + { + uint32_t mhz = freq / 1000'000; + this->listElement->addItem(this->createFreqListItem(mhz, mhz == this->selectedMHz)); + idx++; + } +} diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/freq_choice_gui.h b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/freq_choice_gui.h new file mode 100644 index 00000000..ec1f2341 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/freq_choice_gui.h @@ -0,0 +1,33 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#include "base_menu_gui.h" + +using FreqChoiceListener = std::function; + +#define FREQ_DEFAULT_TEXT "Do not override" + +class FreqChoiceGui : public BaseMenuGui +{ + protected: + std::uint32_t selectedMHz; + SysClkFrequencyTable* hzTable; + FreqChoiceListener listener; + tsl::elm::ListItem* createFreqListItem(std::uint32_t mhz, bool selected); + + public: + FreqChoiceGui(std::uint32_t selectedMHz, SysClkModule module, SysClkProfile profile, FreqChoiceListener listener); + ~FreqChoiceGui(); + void listUI() override; +}; diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/global_override_gui.cpp b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/global_override_gui.cpp new file mode 100644 index 00000000..e9254cf6 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/global_override_gui.cpp @@ -0,0 +1,83 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "global_override_gui.h" + +#include "fatal_gui.h" +#include "../format.h" + +GlobalOverrideGui::GlobalOverrideGui() +{ + for(std::uint16_t m = 0; m < SysClkModule_EnumMax; m++) + { + this->listItems[m] = nullptr; + this->listHz[m] = 0; + } +} + +void GlobalOverrideGui::openFreqChoiceGui(SysClkModule module) +{ + tsl::changeTo(this->context->overrideFreqs[module] / 1000'000, module, this->context->profile, [this, module](std::uint32_t mhz) { + Result rc = sysclkIpcSetOverride(module, mhz * 1000'000); + if(R_FAILED(rc)) + { + FatalGui::openWithResultCode("sysclkIpcSetOverride", rc); + return false; + } + + this->lastContextUpdate = armGetSystemTick(); + this->context->overrideFreqs[module] = mhz * 1000'000; + + return true; + }); +} + +void GlobalOverrideGui::addModuleListItem(SysClkModule module) +{ + tsl::elm::ListItem* listItem = new tsl::elm::ListItem(sysclkFormatModule(module, true)); + listItem->setValue(formatListFreqMhz(0)); + + listItem->setClickListener([this, module](u64 keys) { + if((keys & HidNpadButton_A) == HidNpadButton_A) + { + this->openFreqChoiceGui(module); + return true; + } + + return false; + }); + + this->listElement->addItem(listItem); + this->listItems[module] = listItem; +} + +void GlobalOverrideGui::listUI() +{ + this->addModuleListItem(SysClkModule_CPU); + this->addModuleListItem(SysClkModule_GPU); + this->addModuleListItem(SysClkModule_MEM); +} + +void GlobalOverrideGui::refresh() +{ + BaseMenuGui::refresh(); + + if(this->context) + { + for(std::uint16_t m = 0; m < SysClkModule_EnumMax; m++) + { + if(this->listItems[m] != nullptr && this->listHz[m] != this->context->overrideFreqs[m]) + { + this->listItems[m]->setValue(formatListFreqHz(this->context->overrideFreqs[m])); + this->listHz[m] = this->context->overrideFreqs[m]; + } + } + } +} diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/global_override_gui.h b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/global_override_gui.h new file mode 100644 index 00000000..3dc9caf5 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/global_override_gui.h @@ -0,0 +1,31 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include "../../ipc.h" +#include "base_menu_gui.h" +#include "freq_choice_gui.h" + +class GlobalOverrideGui : public BaseMenuGui +{ + protected: + tsl::elm::ListItem* listItems[SysClkModule_EnumMax]; + std::uint32_t listHz[SysClkModule_EnumMax]; + + void openFreqChoiceGui(SysClkModule module); + void addModuleListItem(SysClkModule module); + + public: + GlobalOverrideGui(); + ~GlobalOverrideGui() {} + void listUI() override; + void refresh() override; +}; diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/main_gui.cpp b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/main_gui.cpp new file mode 100644 index 00000000..6113aaaa --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/main_gui.cpp @@ -0,0 +1,92 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "main_gui.h" + +#include "fatal_gui.h" +#include "app_profile_gui.h" +#include "global_override_gui.h" +#include "misc_gui.h" + +void MainGui::listUI() +{ + this->enabledToggle = new tsl::elm::ToggleListItem("Enable", false); + enabledToggle->setStateChangedListener([this](bool state) { + Result rc = sysclkIpcSetEnabled(state); + if(R_FAILED(rc)) + { + FatalGui::openWithResultCode("sysclkIpcSetEnabled", rc); + } + + this->lastContextUpdate = armGetSystemTick(); + this->context->enabled = state; + }); + this->listElement->addItem(this->enabledToggle); + + tsl::elm::ListItem* appProfileItem = new tsl::elm::ListItem("Edit app profile"); + appProfileItem->setClickListener([this](u64 keys) { + if((keys & HidNpadButton_A) == HidNpadButton_A && this->context) + { + AppProfileGui::changeTo(this->context->applicationId); + return true; + } + + return false; + }); + this->listElement->addItem(appProfileItem); + + this->listElement->addItem(new tsl::elm::CategoryHeader("Advanced")); + + tsl::elm::ListItem* globalOverrideItem = new tsl::elm::ListItem("Temporary overrides"); + globalOverrideItem->setClickListener([this](u64 keys) { + if((keys & HidNpadButton_A) == HidNpadButton_A) + { + tsl::changeTo(); + return true; + } + + return false; + }); + this->listElement->addItem(globalOverrideItem); + + tsl::elm::ListItem* globalProfileItem = new tsl::elm::ListItem("Global profile"); + globalProfileItem->setClickListener([this](u64 keys) { + if((keys & HidNpadButton_A) == HidNpadButton_A && this->context) + { + AppProfileGui::changeTo(SYSCLK_GLOBAL_PROFILE_TID); + return true; + } + + return false; + }); + this->listElement->addItem(globalProfileItem); + + tsl::elm::ListItem* miscItem = new tsl::elm::ListItem("Miscellaneous"); + miscItem->setClickListener([this](u64 keys) { + if((keys & HidNpadButton_A) == HidNpadButton_A && this->context) + { + tsl::changeTo(); + return true; + } + + return false; + }); + this->listElement->addItem(miscItem); +} + +void MainGui::refresh() +{ + BaseMenuGui::refresh(); + + if(this->context) + { + this->enabledToggle->setState(this->context->enabled); + } +} diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/main_gui.h b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/main_gui.h new file mode 100644 index 00000000..cfd0fac9 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/main_gui.h @@ -0,0 +1,25 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include "base_menu_gui.h" + +class MainGui : public BaseMenuGui +{ + protected: + tsl::elm::ToggleListItem* enabledToggle; + + public: + MainGui() {} + ~MainGui() {} + void listUI() override; + void refresh() override; +}; diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/misc_gui.cpp b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/misc_gui.cpp new file mode 100644 index 00000000..ece78845 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/misc_gui.cpp @@ -0,0 +1,183 @@ +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "misc_gui.h" + +#include "fatal_gui.h" +#include "../format.h" + +MiscGui::MiscGui() +{ + smInitialize(); + + this->configList = new SysClkConfigValueList {}; + this->chargeInfo = new PsmChargeInfo {}; + this->i2cInfo = new I2cInfo {}; + + sysclkIpcGetIsMariko(&this->isMariko); +} + +MiscGui::~MiscGui() +{ + delete this->configList; + delete this->chargeInfo; + delete this->i2cInfo; + this->configToggles.clear(); + + smExit(); +} + +void MiscGui::addConfigToggle(SysClkConfigValue configVal, const char* altName = nullptr) { + const char* configName = altName ? altName : sysclkFormatConfigValue(configVal, true); + tsl::elm::ToggleListItem* toggle = new tsl::elm::ToggleListItem(configName, this->configList->values[configVal]); + toggle->setStateChangedListener([this, configVal](bool state) { + this->configList->values[configVal] = uint64_t(state); + Result rc = sysclkIpcSetConfigValues(this->configList); + if (R_FAILED(rc)) + FatalGui::openWithResultCode("sysclkIpcSetConfigValues", rc); + + this->lastContextUpdate = armGetSystemTick(); + }); + this->listElement->addItem(toggle); + this->configToggles[configVal] = toggle; +} + +void MiscGui::updateConfigToggles() { + for (const auto& [value, toggle] : this->configToggles) { + if (toggle != nullptr) + toggle->setState(this->configList->values[value]); + } +} + +void MiscGui::listUI() +{ + // Config list + sysclkIpcGetConfigValues(this->configList); + this->listElement->addItem(new tsl::elm::CategoryHeader("Config")); + + addConfigToggle(SysClkConfigValue_AutoCPUBoost, this->isMariko ? nullptr : "Auto CPU Boost (Unsafe)"); + addConfigToggle(SysClkConfigValue_SyncReverseNXMode); + addConfigToggle(SysClkConfigValue_GovernorExperimental); + + // Charging Current + this->chargingCurrentHeader = new tsl::elm::CategoryHeader(""); + this->listElement->addItem(this->chargingCurrentHeader); + constexpr size_t current_steps = CHARGING_CURRENT_MA_LIMIT / 100; + this->chargingCurrentBar = new StepTrackBarIcon("", current_steps + 1); + this->chargingCurrentBar->setProgress(this->configList->values[SysClkConfigValue_ChargingCurrentLimit]); + this->chargingCurrentBar->setValueChangedListener([this](u8 val) { + if (val < 1) { + val = 1; + this->chargingCurrentBar->setProgress(val); + } + if (val > current_steps) { + val = current_steps; + this->chargingCurrentBar->setProgress(val); + } + uint32_t current_ma = val * 100; + this->configList->values[SysClkConfigValue_ChargingCurrentLimit] = current_ma; + + snprintf(chargingCurrentBarDesc, sizeof(chargingCurrentBarDesc), "Charging Current: %lu mA (Now: %+d mA)", this->configList->values[SysClkConfigValue_ChargingCurrentLimit], (int)this->i2cInfo->batCurrent); + this->chargingCurrentHeader->setText(chargingCurrentBarDesc); + + Result rc = sysclkIpcSetConfigValues(this->configList); + if (R_FAILED(rc)) + FatalGui::openWithResultCode("sysclkIpcSetConfigValues", rc); + + this->lastContextUpdate = armGetSystemTick(); + }); + this->listElement->addItem(this->chargingCurrentBar); + + // Charging Limit + this->chargingLimitHeader = new tsl::elm::CategoryHeader(""); + this->listElement->addItem(this->chargingLimitHeader); + this->chargingLimitBar = new StepTrackBarIcon("", 100 + 1); + this->chargingLimitBar->setProgress(this->configList->values[SysClkConfigValue_ChargingLimitPercentage]); + this->chargingLimitBar->setValueChangedListener([this](u8 val) { + if (val < 20) { + val = 20; + this->chargingLimitBar->setProgress(val); + } + this->configList->values[SysClkConfigValue_ChargingLimitPercentage] = val; + + snprintf(chargingLimitBarDesc, sizeof(chargingLimitBarDesc), "Battery Charging Limit: %lu%% (Now: %u%%)", this->configList->values[SysClkConfigValue_ChargingLimitPercentage], this->batteryChargePerc); + this->chargingLimitHeader->setText(chargingLimitBarDesc); + this->chargingLimitBar->setIcon(PsmGetBatteryStateIcon(this->chargeInfo)); + + Result rc = sysclkIpcSetConfigValues(this->configList); + if (R_FAILED(rc)) + FatalGui::openWithResultCode("sysclkIpcSetConfigValues", rc); + + this->lastContextUpdate = armGetSystemTick(); + }); + this->listElement->addItem(this->chargingLimitBar); + this->listElement->addItem(new tsl::elm::CustomDrawer([](tsl::gfx::Renderer* renderer, s32 x, s32 y, s32 w, s32 h) { + renderer->drawString("\uE016 Long-term use may render the battery gauge \ninaccurate!", false, x, y + 20, SMALL_TEXT_SIZE, DESC_COLOR); + }), SMALL_TEXT_SIZE * 2 + 20); + + // Temporary toggles + this->listElement->addItem(new tsl::elm::CategoryHeader("Temporary toggles")); + + this->chargingDisabledOverrideToggle = new tsl::elm::ToggleListItem("Force Disable Charging", false); + chargingDisabledOverrideToggle->setStateChangedListener([this](bool state) { + Result rc = sysclkIpcSetBatteryChargingDisabledOverride(state); + if (R_FAILED(rc)) + FatalGui::openWithResultCode("sysclkIpcSetBatteryChargingDisabledOverride", rc); + + this->lastContextUpdate = armGetSystemTick(); + }); + this->listElement->addItem(this->chargingDisabledOverrideToggle); + + this->backlightToggle = new tsl::elm::ToggleListItem("Screen Backlight", false); + backlightToggle->setStateChangedListener([this](bool state) { + LblUpdate(true); + }); + this->listElement->addItem(this->backlightToggle); + + // Info + this->listElement->addItem(new tsl::elm::CategoryHeader("Info")); + this->listElement->addItem(new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { + renderer->drawString(this->infoNames, false, x, y + 20, SMALL_TEXT_SIZE, DESC_COLOR); + renderer->drawString(this->infoVals, false, x + 120, y + 20, SMALL_TEXT_SIZE, VALUE_COLOR); + }), SMALL_TEXT_SIZE * 12 + 20); +} + +void MiscGui::refresh() { + BaseMenuGui::refresh(); + + if (this->context && ++frameCounter >= 60) + { + frameCounter = 0; + sysclkIpcGetConfigValues(this->configList); + updateConfigToggles(); + + PsmUpdate(); + this->chargingLimitBar->setIcon(PsmGetBatteryStateIcon(this->chargeInfo)); + + bool chargingDisabledOverride; + Result rc = sysclkIpcGetBatteryChargingDisabledOverride(&chargingDisabledOverride); + if (R_FAILED(rc)) + FatalGui::openWithResultCode("sysclkIpcGetBatteryChargingDisabledOverride", rc); + this->chargingDisabledOverrideToggle->setState(chargingDisabledOverride); + + LblUpdate(); + this->backlightToggle->setState(lblstatus); + + this->chargingCurrentBar->setProgress(this->configList->values[SysClkConfigValue_ChargingCurrentLimit] / 100); + snprintf(chargingCurrentBarDesc, sizeof(chargingCurrentBarDesc), "Charging Current: %lu mA (Now: %+d mA)", this->configList->values[SysClkConfigValue_ChargingCurrentLimit], (int)this->i2cInfo->batCurrent); + this->chargingCurrentHeader->setText(chargingCurrentBarDesc); + + this->chargingLimitBar->setProgress(this->configList->values[SysClkConfigValue_ChargingLimitPercentage]); + snprintf(chargingLimitBarDesc, sizeof(chargingLimitBarDesc), "Charging Limit: %lu%% (Now: %u%%)", this->configList->values[SysClkConfigValue_ChargingLimitPercentage], this->batteryChargePerc); + this->chargingLimitHeader->setText(chargingLimitBarDesc); + + I2cGetInfo(this->i2cInfo); + UpdateInfo(this->infoVals, sizeof(this->infoVals)); + } +} diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/misc_gui.h b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/misc_gui.h new file mode 100644 index 00000000..c9b32932 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/gui/misc_gui.h @@ -0,0 +1,182 @@ +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include "../../ipc.h" +#include "base_menu_gui.h" +#include + +class StepTrackBarIcon : public tsl::elm::StepTrackBar { +public: + StepTrackBarIcon(const char icon[3], size_t numSteps): + tsl::elm::StepTrackBar(icon, numSteps) { } + const char* getIcon() { return this->m_icon; } + void setIcon(const char* icon) { this->m_icon = icon; } +}; + +class MiscGui : public BaseMenuGui +{ + public: + MiscGui(); + ~MiscGui(); + void listUI() override; + void refresh() override; + + protected: + typedef struct { + float batCurrent; + u32 cpuVolt; + u32 gpuVolt; + u32 emcVddq; + u32 memVdd2; + } I2cInfo; + + void PsmUpdate(uint32_t dispatchId = 0) + { + psmInitialize(); + + if (dispatchId) + { + serviceDispatch(psmGetServiceSession(), dispatchId); + svcSleepThread(1000); + } + + serviceDispatchOut(psmGetServiceSession(), 17, *(this->chargeInfo)); + psmGetBatteryChargePercentage(&(this->batteryChargePerc)); + + psmExit(); + } + + void I2cGetInfo(I2cInfo* i2cInfo) + { + i2cInitialize(); + + float batCurrent = I2c_Max17050_GetBatteryCurrent(); + i2cInfo->batCurrent = std::abs(batCurrent) < 10. ? 0. : batCurrent; + + if (isMariko) { + i2cInfo->cpuVolt = I2c_BuckConverter_GetMvOut(&I2c_Mariko_CPU); + i2cInfo->gpuVolt = I2c_BuckConverter_GetMvOut(&I2c_Mariko_GPU); + i2cInfo->emcVddq = I2c_BuckConverter_GetMvOut(&I2c_Mariko_DRAM_VDDQ); + i2cInfo->memVdd2 = I2c_BuckConverter_GetMvOut(&I2c_Mariko_DRAM_VDD2); + } else { + i2cInfo->cpuVolt = I2c_BuckConverter_GetMvOut(&I2c_Erista_CPU); + i2cInfo->gpuVolt = I2c_BuckConverter_GetMvOut(&I2c_Erista_GPU); + i2cInfo->emcVddq = I2c_BuckConverter_GetMvOut(&I2c_Erista_DRAM); + i2cInfo->memVdd2 = i2cInfo->emcVddq; + } + + I2c_Bq24193_GetFastChargeCurrentLimit(reinterpret_cast(&(chargeInfo->ChargeCurrentLimit))); + + i2cExit(); + } + + void UpdateInfo(char* out, size_t outsize) + { + float chargerVoltLimit = (float)chargeInfo->ChargerVoltageLimit / 1000; + float chargerCurrLimit = (float)chargeInfo->ChargerCurrentLimit / 1000; + float chargerOutWatts = chargerVoltLimit * chargerCurrLimit; + + char chargeVoltLimit[20] = ""; + if (chargeInfo->ChargeVoltageLimit) + snprintf(chargeVoltLimit, sizeof(chargeVoltLimit), ", %umV", chargeInfo->ChargeVoltageLimit); + + char chargWattsInfo[30] = ""; + if (chargeInfo->ChargerType) + snprintf(chargWattsInfo, sizeof(chargWattsInfo), " %.1fV/%.1fA (%.1fW)", chargerVoltLimit, chargerCurrLimit, chargerOutWatts); + + char batCurInfo[30] = ""; + snprintf(batCurInfo, sizeof(batCurInfo), "%+.2fmA (%+.2fW)", + i2cInfo->batCurrent, i2cInfo->batCurrent * (float)chargeInfo->VoltageAvg / 1000'000); + + snprintf(out, outsize, + "%s%s\n" + "%.3fV %.2f\u00B0C\n" + "+%umA, -%umA\n" + "+%umA%s\n" + "%.2f%%\n" + "%.2f%%\n" + "%s\n" + "%s\n\n" + "%dmV\n" + "%dmV\n" + "Vddq %dmV, Vdd2 %dmV\n" + , + PsmInfoChargerTypeToStr(chargeInfo->ChargerType), chargWattsInfo, + (float)chargeInfo->VoltageAvg / 1000, (float)chargeInfo->BatteryTemperature / 1000, + chargeInfo->InputCurrentLimit, chargeInfo->VBUSCurrentLimit, + chargeInfo->ChargeCurrentLimit, chargeVoltLimit, + (float)chargeInfo->RawBatteryCharge / 1000, + (float)chargeInfo->BatteryAge / 1000, + PsmPowerRoleToStr(chargeInfo->PowerRole), + batCurInfo, + i2cInfo->cpuVolt, + i2cInfo->gpuVolt, + i2cInfo->emcVddq, i2cInfo->memVdd2 + ); + } + + void PsmChargingToggler(bool* enable) + { + if (!PsmIsChargerConnected(this->chargeInfo)) + { + *enable = false; + return; + } + + PsmUpdate(*enable ? 2 : 3); + + *enable = (PsmIsCharging(this->chargeInfo) == *enable); + } + + void LblUpdate(bool shouldSwitch = false) + { + lblInitialize(); + + lblGetBacklightSwitchStatus(&lblstatus); + if (shouldSwitch) + lblstatus ? lblSwitchBacklightOff(0) : lblSwitchBacklightOn(0); + + lblExit(); + } + + bool isMariko = false; + + std::map configToggles; + void addConfigToggle(SysClkConfigValue, const char*); + void updateConfigToggles(); + + tsl::elm::ToggleListItem *chargingDisabledOverrideToggle, *backlightToggle; + tsl::elm::CategoryHeader *chargingCurrentHeader, *chargingLimitHeader; + StepTrackBarIcon *chargingCurrentBar, *chargingLimitBar; + + SysClkConfigValueList* configList; + PsmChargeInfo* chargeInfo; + I2cInfo* i2cInfo; + LblBacklightSwitchStatus lblstatus = LblBacklightSwitchStatus_Disabled; + + const char* infoNames = + "Charger:\n"\ + "Battery:\n"\ + "Current Limit:\n"\ + "Charging Limit:\n"\ + "Raw Charge:\n"\ + "Battery Age:\n"\ + "Power Role:\n"\ + "Current Flow:\n\n"\ + "CPU Volt:\n"\ + "GPU Volt:\n"\ + "DRAM Volt:"; + char infoVals[300] = ""; + char chargingLimitBarDesc[40] = ""; + char chargingCurrentBarDesc[50] = ""; + u32 batteryChargePerc = 0; + u8 frameCounter = 60; +}; diff --git a/Source/sys-clk-OC(deprecated)/overlay/src/ui/style.h b/Source/sys-clk-OC(deprecated)/overlay/src/ui/style.h new file mode 100644 index 00000000..86b56d89 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/overlay/src/ui/style.h @@ -0,0 +1,20 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#define TEXT_COLOR tsl::gfx::Renderer::a(0xFFFF) +#define DESC_COLOR tsl::gfx::Renderer::a({ 0xC, 0xC, 0xC, 0xF }) +#define VALUE_COLOR tsl::gfx::Renderer::a({ 0x5, 0xC, 0xA, 0xF }) +#define SMALL_TEXT_SIZE 15 +#define LABEL_SPACING 7 +#define LABEL_FONT_SIZE 15 diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/.gitignore b/Source/sys-clk-OC(deprecated)/sysmodule/.gitignore new file mode 100644 index 00000000..36a52c92 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/.gitignore @@ -0,0 +1,2 @@ +/out +/build diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/Makefile b/Source/sys-clk-OC(deprecated)/sysmodule/Makefile new file mode 100644 index 00000000..0849c0f8 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/Makefile @@ -0,0 +1,164 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". +#--------------------------------------------------------------------------------- +TARGET := sys-clk-OC +BUILD := build +OUTDIR := out +RESOURCES := res +SOURCES := src src/nx/ipc ../common/src +DATA := data +INCLUDES := ../common/include +EXEFS_SRC := exefs_src +LIBNAMES := minIni nxExt + +#--------------------------------------------------------------------------------- +# version control constants +#--------------------------------------------------------------------------------- +TARGET_VERSION := 1.8.3 + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +DEFINES := -DDISABLE_IPC -DTARGET="\"$(TARGET)\"" -DTARGET_VERSION="\"$(TARGET_VERSION)\"" + +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O3 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -std=gnu++20 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lnx $(foreach lib,$(LIBNAMES),-l$(lib)) + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) $(foreach lib,$(LIBNAMES),$(TOPDIR)/lib/$(lib)) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(OUTDIR)/$(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)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +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) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +export APP_JSON := $(TOPDIR)/perms.json + +.PHONY: $(BUILD) clean clean-libs clean-build all libs + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +libs: + @$(foreach lib,$(LIBNAMES),$(MAKE) --no-print-directory -C $(TOPDIR)/lib/$(lib) && ) true + +$(LIBNAMES): + @echo $@ + +$(BUILD): libs + @[ -d $@ ] || mkdir -p $@ + @[ -d $(OUTDIR) ] || mkdir -p $(OUTDIR) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean-libs: + @echo clean libs $(LIBNAMES) ... + @$(foreach lib,$(LIBNAMES),$(MAKE) -C $(TOPDIR)/lib/$(lib) clean && ) true + +clean-build: + @echo clean build ... + @rm -fr $(BUILD) $(TARGET).kip $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf $(OUTDIR) + +clean: clean-libs clean-build + + +#--------------------------------------------------------------------------------- +else +.PHONY: all $(LIBFILES) + +LIBFILES := $(foreach lib,$(LIBNAMES),$(TOPDIR)/lib/$(lib)/lib/lib$(lib).a) +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- + +all: $(OUTPUT).nsp + +$(OUTPUT).nsp: $(OUTPUT).nso $(OUTPUT).npdm + +$(OUTPUT).elf: $(OFILES) $(LIBFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/.gitignore b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/.gitignore new file mode 100644 index 00000000..0806f7bf --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/.gitignore @@ -0,0 +1,13 @@ +# Editor files +*.swp +*~ + +# Objects +*.o +*.a +*.so + +# Lib +lib +release +debug \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/.gitrepo b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/.gitrepo new file mode 100644 index 00000000..44cc0b36 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme +; +[subrepo] + remote = https://github.com/compuphase/minIni + branch = master + commit = 8ce144c3c287fa4e59f8ed4c405cd8b7e29f189b + parent = f32c0b1bca87a6d7e55fb341f8db9ef33b13819f + method = merge + cmdver = 0.4.0 diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/LICENSE b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/LICENSE new file mode 100644 index 00000000..cbf8eb4c --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/LICENSE @@ -0,0 +1,189 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + + EXCEPTION TO THE APACHE 2.0 LICENSE + + As a special exception to the Apache License 2.0 (and referring to the + definitions in Section 1 of this license), you may link, statically or + dynamically, the "Work" to other modules to produce an executable file + containing portions of the "Work", and distribute that executable file + in "Object" form under the terms of your choice, without any of the + additional requirements listed in Section 4 of the Apache License 2.0. + This exception applies only to redistributions in "Object" form (not + "Source" form) and only if no modifications have been made to the "Work". + + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/Makefile b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/Makefile new file mode 100644 index 00000000..b9565d07 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/Makefile @@ -0,0 +1,133 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +SOURCES := dev +DATA := data +INCLUDES := dev +SRC_H_FILES := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIC -ftls-model=local-exec + +CFLAGS := -g -Wall -Werror \ + -ffunction-sections \ + -fdata-sections \ + $(ARCH) \ + $(BUILD_CFLAGS) + +CFLAGS += $(INCLUDE) + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +CFILES := minIni.c +CPPFILES := +SFILES := +BINFILES := + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +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 := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +.PHONY: clean all lib/lib$(TARGET).a + +#--------------------------------------------------------------------------------- +all: lib/lib$(TARGET).a + +lib: + @[ -d $@ ] || mkdir -p $@ + +release: + @[ -d $@ ] || mkdir -p $@ + +debug: + @[ -d $@ ] || mkdir -p $@ + +lib/lib$(TARGET).a : lib release $(SOURCES) $(INCLUDES) + @$(MAKE) BUILD=release OUTPUT=$(CURDIR)/$@ \ + BUILD_CFLAGS="-DNDEBUG=1 -O2" \ + DEPSDIR=$(CURDIR)/release \ + --no-print-directory -C release \ + -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr release debug lib + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +$(OFILES_SRC) : $(HFILES) + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/NOTICE b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/NOTICE new file mode 100644 index 00000000..dbd0bba0 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/NOTICE @@ -0,0 +1,12 @@ +minIni is a programmer's library to read and write "INI" files in embedded +systems. The library takes little resources and can be configured for various +kinds of file I/O libraries. + +The method for portable INI file management in minIni is, in part based, on the +article "Multiplatform .INI Files" by Joseph J. Graf in the March 1994 issue of +Dr. Dobb's Journal. + +The C++ class in minIni.h was contributed by Steven Van Ingelgem. + +The option to compile minIni as a read-only library was contributed by Luca +Bassanello. diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/README.md b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/README.md new file mode 100644 index 00000000..9f30a019 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/README.md @@ -0,0 +1,170 @@ +# minIni +minIni is a portable and configurable library for reading and writing ".INI" files. At 830 lines of commented source +code +(version 1.2), minIni truly is a "mini" INI file parser, especially considering its features. + +The library does not require the file I/O functions from the standard C/C++ library, but instead lets you configure +the file I/O interface to use via macros. minIni uses limited stack space and does not use dynamic memory (malloc and +friends) at all. + +Some minor variations on standard INI files are supported too, notably minIni supports INI files that lack sections. + + +# Acknowledgement + +minIni is derived from an earlier INI file parser (which I wrote) for desktop systems. + +In turn, that earlier parser was a re-write of the code from the article "Multiplatform .INI Files" by Joseph J. Graf +in the March 1994 issue of Dr. Dobb's Journal. In other words, minIni has its roots in the work of Joseph Graf (even +though the code has been almost completely re-written). + + +# Features + +minIni is a programmer's library to read and write "INI" files in embedded systems. minIni takes little resources, +can be configured for various kinds of file I/O libraries and provides functionality for reading, writing and +deleting keys from an INI file. + +Although the main feature of minIni is that it is small and minimal, it has a few other features: + + * minIni supports reading keys that are outside a section, and it thereby supports configuration files that do not use sections (but that are otherwise compatible with INI files). + * You may use a colon to separate key and value; the colon is equivalent to the equal sign. That is, the strings "Name: Value" and "Name=Value" have the same meaning. + * The hash character ("#") is an alternative for the semicolon to start a comment. Trailing comments (i.e. behind a key/value pair on a line) are allowed. + * Leading and trailing white space around key names and values is ignored. + * When writing a value that contains a comment character (";" or "#"), that value will automatically be put between double quotes; when reading the value, these quotes are removed. When a double-quote itself appears in the setting, these characters are escaped. + * Section and key enumeration are supported. + * You can optionally set the line termination (for text files) that minIni will use. (This is a compile-time setting, not a run-time setting.) + * Since writing speed is much lower than reading speed in Flash memory (SD/MMC cards, USB memory sticks), minIni minimizes "file writes" at the expense of double "file reads". + * The memory footprint is deterministic. There is no dynamic memory allocation. + +## INI file reading paradigms + +There are two approaches to reading settings from an INI file. One way is to call a function, such as +GetProfileString() for every section and key that you need. This is especially convenient if there is a large +INI file, but you only need a few settings from that file at any time —especially if the INI file can also +change while your program runs. This is the approach that the Microsoft Windows API uses. + +The above procedure is quite inefficient, however, when you need to retrieve quite a few settings in a row from +the INI file —especially if the INI file is not cached in memory (which it isn't, in minIni). A different approach +to getting settings from an INI file is to call a "parsing" function and let that function call the application +back with the section and key names plus the associated data. XML parsing libraries often use this approach; see +for example the Expat library. + +minIni supports both approaches. For reading a single setting, use functions like ini_gets(). For the callback +approach, implement a callback and call ini_browse(). See the minIni manual for details. + + +# INI file syntax + +INI files are best known from Microsoft Windows, but they are also used with applications that run on other +platforms (although their file extension is sometimes ".cfg" instead of ".ini"). + +INI files have a simple syntax with name/value pairs in a plain text file. The name must be unique (per section) +and the value must fit on a single line. INI files are commonly separated into sections —in minIni, this is +optional. A section is a name between square brackets, like "[Network]" in the example below. + +``` +[Network] +hostname=My Computer +address=dhcp +dns = 192.168.1.1 +``` + +In the API and in this documentation, the "name" for a setting is denoted as the key for the setting. The key +and the value are separated by an equal sign ("="). minIni supports the colon (":") as an alternative to the +equal sign for the key/value delimiter. + +Leading a trailing spaces around values or key names are removed. If you need to include leading and/or trailing +spaces in a value, put the value between double quotes. The ini_gets() function (from the minIni library, see the +minIni manual) strips off the double quotes from the returned value. Function ini_puts() adds double quotes if +the value to write contains trailing white space (or special characters). + +minIni ignores spaces around the "=" or ":" delimiters, but it does not ignore spaces between the brackets in a +section name. In other words, it is best not to put spaces behind the opening bracket "[" or before the closing +bracket "]" of a section name. + +Comments in the INI must start with a semicolon (";") or a hash character ("#"), and run to the end of the line. +A comment can be a line of its own, or it may follow a key/value pair (the "#" character and trailing comments +are extensions of minIni). + +For more details on the format, please see http://en.wikipedia.org/wiki/INI_file. + + +# Adapting minIni to a file system + +The minIni library must be configured for a platform with the help of a so- called "glue file". This glue file +contains macros (and possibly functions) that map file reading and writing functions used by the minIni library +to those provided by the operating system. The glue file must be called "minGlue.h". + +To get you started, the minIni distribution comes with the following example glue files: + + * a glue file that maps to the standard C/C++ library (specifically the file I/O functions from the "stdio" package), + * a glue file for Microchip's "Memory Disk Drive File System Library" (see http://www.microchip.com/), + * a glue file for the FAT library provided with the CCS PIC compiler (see http://www.ccsinfo.com/) + * a glue file for the EFS Library (EFSL, http://www.efsl.be/), + * and a glue file for the FatFs and Petit-FatFs libraries (http://elm-chan.org/fsw/ff/00index_e.html). + +The minIni library does not rely on the availability of a standard C library, because embedded operating systems +may have limited support for file I/O. Even on full operating systems, separating the file I/O from the INI format +parsing carries advantages, because it allows you to cache the INI file and thereby enhance performance. + +The glue file must specify the type that identifies a file, whether it is a handle or a pointer. For the standard +C/C++ file I/O library, this would be: + +```C +#define INI_FILETYPE FILE* +``` + +If you are not using the standard C/C++ file I/O library, chances are that you need a different handle or +"structure" to identify the storage than the ubiquitous "FILE*" type. For example, the glue file for the FatFs +library uses the following declaration: + +```C +#define INI_FILETYPE FIL +``` + +The minIni functions declare variables of this INI_FILETYPE type and pass these variables to sub-functions +(including the glue interface functions) by reference. + +For "write support", another type that must be defined is for variables that hold the "current position" in a +file. For the standard C/C++ I/O library, this is "fpos_t". + +Another item that needs to be configured is the buffer size. The functions in the minIni library allocate this +buffer on the stack, so the buffer size is directly related to the stack usage. In addition, the buffer size +determines the maximum line length that is supported in the INI file and the maximum path name length for the +temporary file. For example, minGlue.h could contain the definition: + +```C +#define INI_BUFFERSIZE 512 +``` + +The above macro limits the line length of the INI files supported by minIni to 512 characters. + +The temporary file is only used when writing to INI files. The minIni routines copy/change the INI file to a +temporary file and then rename that temporary file to the original file. This approach uses the least amount of +memory. The path name of the temporary file is the same as the input file, but with the last character set to a +tilde ("~"). + +Below is an example of a glue file (this is the one that maps to the C/C++ "stdio" library). + +```C +#include + +#define INI_FILETYPE FILE* +#define ini_openread(filename,file) ((*(file) = fopen((filename),"r")) != NULL) +#define ini_openwrite(filename,file) ((*(file) = fopen((filename),"w")) != NULL) +#define ini_close(file) (fclose(*(file)) == 0) +#define ini_read(buffer,size,file) (fgets((buffer),(size),*(file)) != NULL) +#define ini_write(buffer,file) (fputs((buffer),*(file)) >= 0) +#define ini_rename(source,dest) (rename((source), (dest)) == 0) +#define ini_remove(filename) (remove(filename) == 0) + +#define INI_FILEPOS fpos_t +#define ini_tell(file,pos) (fgetpos(*(file), (pos)) == 0) +#define ini_seek(file,pos) (fsetpos(*(file), (pos)) == 0) +``` + +As you can see, a glue file is mostly a set of macros that wraps one function definition around another. + +The glue file may contain more settings, for support of rational numbers, to explicitly set the line termination +character(s), or to disable write support (for example). See the manual that comes with the archive for the details. diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-FatFs.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-FatFs.h new file mode 100644 index 00000000..51593a26 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-FatFs.h @@ -0,0 +1,37 @@ +/* Glue functions for the minIni library, based on the FatFs and Petit-FatFs + * libraries, see http://elm-chan.org/fsw/ff/00index_e.html + * + * By CompuPhase, 2008-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The FatFs and Petit-FatFs libraries are copyright by ChaN and licensed at + * its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ + +/* You must set _USE_STRFUNC to 1 or 2 in the include file ff.h (or tff.h) + * to enable the "string functions" fgets() and fputs(). + */ +#include "ff.h" /* include tff.h for Tiny-FatFs */ + +#define INI_FILETYPE FIL +#define ini_openread(filename,file) (f_open((file), (filename), FA_READ+FA_OPEN_EXISTING) == FR_OK) +#define ini_openwrite(filename,file) (f_open((file), (filename), FA_WRITE+FA_CREATE_ALWAYS) == FR_OK) +#define ini_close(file) (f_close(file) == FR_OK) +#define ini_read(buffer,size,file) f_gets((buffer), (size),(file)) +#define ini_write(buffer,file) f_puts((buffer), (file)) +#define ini_remove(filename) (f_unlink(filename) == FR_OK) + +#define INI_FILEPOS DWORD +#define ini_tell(file,pos) (*(pos) = f_tell((file))) +#define ini_seek(file,pos) (f_lseek((file), *(pos)) == FR_OK) + +static int ini_rename(TCHAR *source, const TCHAR *dest) +{ + /* Function f_rename() does not allow drive letters in the destination file */ + char *drive = strchr(dest, ':'); + drive = (drive == NULL) ? dest : drive + 1; + return (f_rename(source, drive) == FR_OK); +} diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-ccs.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-ccs.h new file mode 100644 index 00000000..22a8c92b --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-ccs.h @@ -0,0 +1,64 @@ +/* minIni glue functions for FAT library by CCS, Inc. (as provided with their + * PIC MCU compiler) + * + * By CompuPhase, 2011-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The FAT library is copyright (c) 2007 Custom Computer Services, and + * licensed at its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ + +#ifndef FAT_PIC_C + #error FAT library must be included before this module +#endif +#define const /* keyword not supported by CCS */ + +#define INI_FILETYPE FILE +#define ini_openread(filename,file) (fatopen((filename), "r", (file)) == GOODEC) +#define ini_openwrite(filename,file) (fatopen((filename), "w", (file)) == GOODEC) +#define ini_close(file) (fatclose((file)) == 0) +#define ini_read(buffer,size,file) (fatgets((buffer), (size), (file)) != NULL) +#define ini_write(buffer,file) (fatputs((buffer), (file)) == GOODEC) +#define ini_remove(filename) (rm_file((filename)) == 0) + +#define INI_FILEPOS fatpos_t +#define ini_tell(file,pos) (fatgetpos((file), (pos)) == 0) +#define ini_seek(file,pos) (fatsetpos((file), (pos)) == 0) + +#ifndef INI_READONLY +/* CCS FAT library lacks a rename function, so instead we copy the file to the + * new name and delete the old file + */ +static int ini_rename(char *source, char *dest) +{ + FILE fr, fw; + int n; + + if (fatopen(source, "r", &fr) != GOODEC) + return 0; + if (rm_file(dest) != 0) + return 0; + if (fatopen(dest, "w", &fw) != GOODEC) + return 0; + + /* With some "insider knowledge", we can save some memory: the "source" + * parameter holds a filename that was built from the "dest" parameter. It + * was built in a local buffer with the size INI_BUFFERSIZE. We can reuse + * this buffer for copying the file. + */ + while (n=fatread(source, 1, INI_BUFFERSIZE, &fr)) + fatwrite(source, 1, n, &fw); + + fatclose(&fr); + fatclose(&fw); + + /* Now we need to delete the source file. However, we have garbled the buffer + * that held the filename of the source. So we need to build it again. + */ + ini_tempname(source, dest, INI_BUFFERSIZE); + return rm_file(source) == 0; +} +#endif diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-efsl.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-efsl.h new file mode 100644 index 00000000..5fe0fcf8 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-efsl.h @@ -0,0 +1,63 @@ +/* Glue functions for the minIni library, based on the EFS Library, see + * http://www.efsl.be/ + * + * By CompuPhase, 2008-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (EFSL is copyright 2005-2006 Lennart Ysboodt and Michael De Nil, and + * licensed under the GPL with an exception clause for static linking.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ +#define INI_LINETERM "\r\n" /* set line termination explicitly */ + +#include "efs.h" +extern EmbeddedFileSystem g_efs; + +#define INI_FILETYPE EmbeddedFile +#define ini_openread(filename,file) (file_fopen((file), &g_efs.myFs, (char*)(filename), 'r') == 0) +#define ini_openwrite(filename,file) (file_fopen((file), &g_efs.myFs, (char*)(filename), 'w') == 0) +#define ini_close(file) file_fclose(file) +#define ini_read(buffer,size,file) (file_read((file), (size), (buffer)) > 0) +#define ini_write(buffer,file) (file_write((file), strlen(buffer), (char*)(buffer)) > 0) +#define ini_remove(filename) rmfile(&g_efs.myFs, (char*)(filename)) + +#define INI_FILEPOS euint32 +#define ini_tell(file,pos) (*(pos) = (file)->FilePtr)) +#define ini_seek(file,pos) file_setpos((file), (*pos)) + +#if ! defined INI_READONLY +/* EFSL lacks a rename function, so instead we copy the file to the new name + * and delete the old file + */ +static int ini_rename(char *source, const char *dest) +{ + EmbeddedFile fr, fw; + int n; + + if (file_fopen(&fr, &g_efs.myFs, source, 'r') != 0) + return 0; + if (rmfile(&g_efs.myFs, (char*)dest) != 0) + return 0; + if (file_fopen(&fw, &g_efs.myFs, (char*)dest, 'w') != 0) + return 0; + + /* With some "insider knowledge", we can save some memory: the "source" + * parameter holds a filename that was built from the "dest" parameter. It + * was built in buffer and this buffer has the size INI_BUFFERSIZE. We can + * reuse this buffer for copying the file. + */ + while (n=file_read(&fr, INI_BUFFERSIZE, source)) + file_write(&fw, n, source); + + file_fclose(&fr); + file_fclose(&fw); + + /* Now we need to delete the source file. However, we have garbled the buffer + * that held the filename of the source. So we need to build it again. + */ + ini_tempname(source, dest, INI_BUFFERSIZE); + return rmfile(&g_efs.myFs, source) == 0; +} +#endif diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-ffs.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-ffs.h new file mode 100644 index 00000000..bf874e41 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-ffs.h @@ -0,0 +1,26 @@ +/* Glue functions for the minIni library, based on the "FAT Filing System" + * library by embedded-code.com + * + * By CompuPhase, 2008-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The "FAT Filing System" library itself is copyright embedded-code.com, and + * licensed at its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ +#include + +#define INI_FILETYPE FFS_FILE* +#define ini_openread(filename,file) ((*(file) = ffs_fopen((filename),"r")) != NULL) +#define ini_openwrite(filename,file) ((*(file) = ffs_fopen((filename),"w")) != NULL) +#define ini_close(file) (ffs_fclose(*(file)) == 0) +#define ini_read(buffer,size,file) (ffs_fgets((buffer),(size),*(file)) != NULL) +#define ini_write(buffer,file) (ffs_fputs((buffer),*(file)) >= 0) +#define ini_rename(source,dest) (ffs_rename((source), (dest)) == 0) +#define ini_remove(filename) (ffs_remove(filename) == 0) + +#define INI_FILEPOS long +#define ini_tell(file,pos) (ffs_fgetpos(*(file), (pos)) == 0) +#define ini_seek(file,pos) (ffs_fsetpos(*(file), (pos)) == 0) diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-mdd.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-mdd.h new file mode 100644 index 00000000..ec5e0be1 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-mdd.h @@ -0,0 +1,58 @@ +/* minIni glue functions for Microchip's "Memory Disk Drive" file system + * library, as presented in Microchip application note AN1045. + * + * By CompuPhase, 2011-2014 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The "Microchip Memory Disk Drive File System" is copyright (c) Microchip + * Technology Incorporated, and licensed at its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ + +#include "MDD File System\fsio.h" +#include + +#define INI_FILETYPE FSFILE* +#define ini_openread(filename,file) ((*(file) = FSfopen((filename),FS_READ)) != NULL) +#define ini_openwrite(filename,file) ((*(file) = FSfopen((filename),FS_WRITE)) != NULL) +#define ini_openrewrite(filename,file) ((*(file) = fopen((filename),FS_READPLUS)) != NULL) +#define ini_close(file) (FSfclose(*(file)) == 0) +#define ini_write(buffer,file) (FSfwrite((buffer), 1, strlen(buffer), (*file)) > 0) +#define ini_remove(filename) (FSremove((filename)) == 0) + +#define INI_FILEPOS long int +#define ini_tell(file,pos) (*(pos) = FSftell(*(file))) +#define ini_seek(file,pos) (FSfseek(*(file), *(pos), SEEK_SET) == 0) + +/* Since the Memory Disk Drive file system library reads only blocks of files, + * the function to read a text line does so by "over-reading" a block of the + * of the maximum size and truncating it behind the end-of-line. + */ +static int ini_read(char *buffer, int size, INI_FILETYPE *file) +{ + size_t numread = size; + char *eol; + + if ((numread = FSfread(buffer, 1, size, *file)) == 0) + return 0; /* at EOF */ + if ((eol = strchr(buffer, '\n')) == NULL) + eol = strchr(buffer, '\r'); + if (eol != NULL) { + /* terminate the buffer */ + *++eol = '\0'; + /* "unread" the data that was read too much */ + FSfseek(*file, - (int)(numread - (size_t)(eol - buffer)), SEEK_CUR); + } /* if */ + return 1; +} + +#ifndef INI_READONLY +static int ini_rename(const char *source, const char *dest) +{ + FSFILE* ftmp = FSfopen((source), FS_READ); + FSrename((dest), ftmp); + return FSfclose(ftmp) == 0; +} +#endif diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-stdio.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-stdio.h new file mode 100644 index 00000000..67d24334 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue-stdio.h @@ -0,0 +1,31 @@ +/* Glue functions for the minIni library, based on the C/C++ stdio library + * + * Or better said: this file contains macros that maps the function interface + * used by minIni to the standard C/C++ file I/O functions. + * + * By CompuPhase, 2008-2014 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + */ + +/* map required file I/O types and functions to the standard C library */ +#include + +#define INI_FILETYPE FILE* +#define ini_openread(filename,file) ((*(file) = fopen((filename),"rb")) != NULL) +#define ini_openwrite(filename,file) ((*(file) = fopen((filename),"wb")) != NULL) +#define ini_openrewrite(filename,file) ((*(file) = fopen((filename),"r+b")) != NULL) +#define ini_close(file) (fclose(*(file)) == 0) +#define ini_read(buffer,size,file) (fgets((buffer),(size),*(file)) != NULL) +#define ini_write(buffer,file) (fputs((buffer),*(file)) >= 0) +#define ini_rename(source,dest) (rename((source), (dest)) == 0) +#define ini_remove(filename) (remove(filename) == 0) + +#define INI_FILEPOS long int +#define ini_tell(file,pos) (*(pos) = ftell(*(file))) +#define ini_seek(file,pos) (fseek(*(file), *(pos), SEEK_SET) == 0) + +/* for floating-point support, define additional types and functions */ +#define INI_REAL float +#define ini_ftoa(string,value) sprintf((string),"%f",(value)) +#define ini_atof(string) (INI_REAL)strtod((string),NULL) diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue.h new file mode 100644 index 00000000..c3149627 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minGlue.h @@ -0,0 +1,35 @@ +/* Glue functions for the minIni library, based on the C/C++ stdio library + * + * Or better said: this file contains macros that maps the function interface + * used by minIni to the standard C/C++ file I/O functions. + * + * By CompuPhase, 2008-2014 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + */ + +/* map required file I/O types and functions to the standard C library */ +#include + +#define INI_FILETYPE FILE* +#define ini_openread(filename,file) ((*(file) = fopen((filename),"rb")) != NULL) +#define ini_openwrite(filename,file) ((*(file) = fopen((filename),"wb")) != NULL) +#define ini_openrewrite(filename,file) ((*(file) = fopen((filename),"r+b")) != NULL) +#define ini_close(file) (fclose(*(file)) == 0) +#define ini_read(buffer,size,file) (fgets((buffer),(size),*(file)) != NULL) +#define ini_write(buffer,file) (fputs((buffer),*(file)) >= 0) +#define ini_rename(source,dest) (rename((source), (dest)) == 0) +#define ini_remove(filename) (remove(filename) == 0) + +#define INI_FILEPOS long int +#define ini_tell(file,pos) (*(pos) = ftell(*(file))) +#define ini_seek(file,pos) (fseek(*(file), *(pos), SEEK_SET) == 0) + +/* for floating-point support, define additional types and functions */ +//#define INI_REAL float +//#define ini_ftoa(string,value) sprintf((string),"%f",(value)) +//#define ini_atof(string) (INI_REAL)strtod((string),NULL) + +#define INI_ANSIONLY +#define INI_LINETERM "\n" +#define PORTABLE_STRNICMP \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minIni.c b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minIni.c new file mode 100644 index 00000000..a0f75a27 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minIni.c @@ -0,0 +1,1009 @@ +/* minIni - Multi-Platform INI file parser, suitable for embedded systems + * + * These routines are in part based on the article "Multiplatform .INI Files" + * by Joseph J. Graf in the March 1994 issue of Dr. Dobb's Journal. + * + * Copyright (c) CompuPhase, 2008-2017 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Version: $Id: minIni.c 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $ + */ + +#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY +# if !defined UNICODE /* for Windows */ +# define UNICODE +# endif +# if !defined _UNICODE /* for C library */ +# define _UNICODE +# endif +#endif + +#define MININI_IMPLEMENTATION +#include "minIni.h" +#if defined NDEBUG + #define assert(e) +#else + #include +#endif + +#if !defined __T || defined INI_ANSIONLY + #include + #include + #include + #define TCHAR char + #define __T(s) s + #define _tcscat strcat + #define _tcschr strchr + #define _tcscmp strcmp + #define _tcscpy strcpy + #define _tcsicmp stricmp + #define _tcslen strlen + #define _tcsncmp strncmp + #define _tcsnicmp strnicmp + #define _tcsrchr strrchr + #define _tcstol strtol + #define _tcstod strtod + #define _totupper toupper + #define _stprintf sprintf + #define _tfgets fgets + #define _tfputs fputs + #define _tfopen fopen + #define _tremove remove + #define _trename rename +#endif + +#if defined __linux || defined __linux__ + #define __LINUX__ +#elif defined FREEBSD && !defined __FreeBSD__ + #define __FreeBSD__ +#elif defined(_MSC_VER) + #pragma warning(disable: 4996) /* for Microsoft Visual C/C++ */ +#endif +#if !defined strnicmp && !defined PORTABLE_STRNICMP + #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + #define strnicmp strncasecmp + #endif +#endif +#if !defined _totupper + #define _totupper toupper +#endif + +#if !defined INI_LINETERM + #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + #define INI_LINETERM __T("\n") + #else + #define INI_LINETERM __T("\r\n") + #endif +#endif +#if !defined INI_FILETYPE + #error Missing definition for INI_FILETYPE. +#endif + +#if !defined sizearray + #define sizearray(a) (sizeof(a) / sizeof((a)[0])) +#endif + +enum quote_option { + QUOTE_NONE, + QUOTE_ENQUOTE, + QUOTE_DEQUOTE, +}; + +#if defined PORTABLE_STRNICMP +int strnicmp(const TCHAR *s1, const TCHAR *s2, size_t n) +{ + while (n-- != 0 && (*s1 || *s2)) { + register int c1, c2; + c1 = *s1++; + if ('a' <= c1 && c1 <= 'z') + c1 += ('A' - 'a'); + c2 = *s2++; + if ('a' <= c2 && c2 <= 'z') + c2 += ('A' - 'a'); + if (c1 != c2) + return c1 - c2; + } /* while */ + return 0; +} +#endif /* PORTABLE_STRNICMP */ + +static TCHAR *skipleading(const TCHAR *str) +{ + assert(str != NULL); + while ('\0' < *str && *str <= ' ') + str++; + return (TCHAR *)str; +} + +static TCHAR *skiptrailing(const TCHAR *str, const TCHAR *base) +{ + assert(str != NULL); + assert(base != NULL); + while (str > base && '\0' < *(str-1) && *(str-1) <= ' ') + str--; + return (TCHAR *)str; +} + +static TCHAR *striptrailing(TCHAR *str) +{ + TCHAR *ptr = skiptrailing(_tcschr(str, '\0'), str); + assert(ptr != NULL); + *ptr = '\0'; + return str; +} + +static TCHAR *ini_strncpy(TCHAR *dest, const TCHAR *source, size_t maxlen, enum quote_option option) +{ + size_t d, s; + + assert(maxlen>0); + assert(source != NULL && dest != NULL); + assert((dest < source || (dest == source && option != QUOTE_ENQUOTE)) || dest > source + strlen(source)); + if (option == QUOTE_ENQUOTE && maxlen < 3) + option = QUOTE_NONE; /* cannot store two quotes and a terminating zero in less than 3 characters */ + + switch (option) { + case QUOTE_NONE: + for (d = 0; d < maxlen - 1 && source[d] != '\0'; d++) + dest[d] = source[d]; + assert(d < maxlen); + dest[d] = '\0'; + break; + case QUOTE_ENQUOTE: + d = 0; + dest[d++] = '"'; + for (s = 0; source[s] != '\0' && d < maxlen - 2; s++, d++) { + if (source[s] == '"') { + if (d >= maxlen - 3) + break; /* no space to store the escape character plus the one that follows it */ + dest[d++] = '\\'; + } /* if */ + dest[d] = source[s]; + } /* for */ + dest[d++] = '"'; + dest[d] = '\0'; + break; + case QUOTE_DEQUOTE: + for (d = s = 0; source[s] != '\0' && d < maxlen - 1; s++, d++) { + if ((source[s] == '"' || source[s] == '\\') && source[s + 1] == '"') + s++; + dest[d] = source[s]; + } /* for */ + dest[d] = '\0'; + break; + default: + assert(0); + } /* switch */ + + return dest; +} + +static TCHAR *cleanstring(TCHAR *string, enum quote_option *quotes) +{ + int isstring; + TCHAR *ep; + + assert(string != NULL); + assert(quotes != NULL); + + /* Remove a trailing comment */ + isstring = 0; + for (ep = string; *ep != '\0' && ((*ep != ';' && *ep != '#') || isstring); ep++) { + if (*ep == '"') { + if (*(ep + 1) == '"') + ep++; /* skip "" (both quotes) */ + else + isstring = !isstring; /* single quote, toggle isstring */ + } else if (*ep == '\\' && *(ep + 1) == '"') { + ep++; /* skip \" (both quotes */ + } /* if */ + } /* for */ + assert(ep != NULL && (*ep == '\0' || *ep == ';' || *ep == '#')); + *ep = '\0'; /* terminate at a comment */ + striptrailing(string); + /* Remove double quotes surrounding a value */ + *quotes = QUOTE_NONE; + if (*string == '"' && (ep = _tcschr(string, '\0')) != NULL && *(ep - 1) == '"') { + string++; + *--ep = '\0'; + *quotes = QUOTE_DEQUOTE; /* this is a string, so remove escaped characters */ + } /* if */ + return string; +} + +static int getkeystring(INI_FILETYPE *fp, const TCHAR *Section, const TCHAR *Key, + int idxSection, int idxKey, TCHAR *Buffer, int BufferSize, + INI_FILEPOS *mark) +{ + TCHAR *sp, *ep; + int len, idx; + enum quote_option quotes; + TCHAR LocalBuffer[INI_BUFFERSIZE]; + + assert(fp != NULL); + /* Move through file 1 line at a time until a section is matched or EOF. If + * parameter Section is NULL, only look at keys above the first section. If + * idxSection is positive, copy the relevant section name. + */ + len = (Section != NULL) ? (int)_tcslen(Section) : 0; + if (len > 0 || idxSection >= 0) { + assert(idxSection >= 0 || Section != NULL); + idx = -1; + do { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp)) + return 0; + sp = skipleading(LocalBuffer); + ep = _tcsrchr(sp, ']'); + } while (*sp != '[' || ep == NULL || + (((int)(ep-sp-1) != len || Section == NULL || _tcsnicmp(sp+1,Section,len) != 0) && ++idx != idxSection)); + if (idxSection >= 0) { + if (idx == idxSection) { + assert(ep != NULL); + assert(*ep == ']'); + *ep = '\0'; + ini_strncpy(Buffer, sp + 1, BufferSize, QUOTE_NONE); + return 1; + } /* if */ + return 0; /* no more section found */ + } /* if */ + } /* if */ + + /* Now that the section has been found, find the entry. + * Stop searching upon leaving the section's area. + */ + assert(Key != NULL || idxKey >= 0); + len = (Key != NULL) ? (int)_tcslen(Key) : 0; + idx = -1; + do { + if (mark != NULL) + ini_tell(fp, mark); /* optionally keep the mark to the start of the line */ + if (!ini_read(LocalBuffer,INI_BUFFERSIZE,fp) || *(sp = skipleading(LocalBuffer)) == '[') + return 0; + sp = skipleading(LocalBuffer); + ep = _tcschr(sp, '='); /* Parse out the equal sign */ + if (ep == NULL) + ep = _tcschr(sp, ':'); + } while (*sp == ';' || *sp == '#' || ep == NULL + || ((len == 0 || (int)(skiptrailing(ep,sp)-sp) != len || _tcsnicmp(sp,Key,len) != 0) && ++idx != idxKey)); + if (idxKey >= 0) { + if (idx == idxKey) { + assert(ep != NULL); + assert(*ep == '=' || *ep == ':'); + *ep = '\0'; + striptrailing(sp); + ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE); + return 1; + } /* if */ + return 0; /* no more key found (in this section) */ + } /* if */ + + /* Copy up to BufferSize chars to buffer */ + assert(ep != NULL); + assert(*ep == '=' || *ep == ':'); + sp = skipleading(ep + 1); + sp = cleanstring(sp, "es); /* Remove a trailing comment */ + ini_strncpy(Buffer, sp, BufferSize, quotes); + return 1; +} + +/** ini_gets() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue default string in the event of a failed read + * \param Buffer a pointer to the buffer to copy into + * \param BufferSize the maximum number of characters to copy + * \param Filename the name and full path of the .ini file to read from + * + * \return the number of characters copied into the supplied buffer + */ +int ini_gets(const TCHAR *Section, const TCHAR *Key, const TCHAR *DefValue, + TCHAR *Buffer, int BufferSize, const TCHAR *Filename) +{ + INI_FILETYPE fp; + int ok = 0; + + if (Buffer == NULL || BufferSize <= 0 || Key == NULL) + return 0; + if (ini_openread(Filename, &fp)) { + ok = getkeystring(&fp, Section, Key, -1, -1, Buffer, BufferSize, NULL); + (void)ini_close(&fp); + } /* if */ + if (!ok) + ini_strncpy(Buffer, (DefValue != NULL) ? DefValue : __T(""), BufferSize, QUOTE_NONE); + return (int)_tcslen(Buffer); +} + +/** ini_getl() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue the default value in the event of a failed read + * \param Filename the name of the .ini file to read from + * + * \return the value located at Key + */ +long ini_getl(const TCHAR *Section, const TCHAR *Key, long DefValue, const TCHAR *Filename) +{ + TCHAR LocalBuffer[64]; + int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); + return (len == 0) ? DefValue + : ((len >= 2 && _totupper((int)LocalBuffer[1]) == 'X') ? _tcstol(LocalBuffer, NULL, 16) + : _tcstol(LocalBuffer, NULL, 10)); +} + +#if defined INI_REAL +/** ini_getf() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue the default value in the event of a failed read + * \param Filename the name of the .ini file to read from + * + * \return the value located at Key + */ +INI_REAL ini_getf(const TCHAR *Section, const TCHAR *Key, INI_REAL DefValue, const TCHAR *Filename) +{ + TCHAR LocalBuffer[64]; + int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); + return (len == 0) ? DefValue : ini_atof(LocalBuffer); +} +#endif + +/** ini_getbool() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue default value in the event of a failed read; it should + * zero (0) or one (1). + * \param Filename the name and full path of the .ini file to read from + * + * A true boolean is found if one of the following is matched: + * - A string starting with 'y' or 'Y' + * - A string starting with 't' or 'T' + * - A string starting with '1' + * + * A false boolean is found if one of the following is matched: + * - A string starting with 'n' or 'N' + * - A string starting with 'f' or 'F' + * - A string starting with '0' + * + * \return the true/false flag as interpreted at Key + */ +int ini_getbool(const TCHAR *Section, const TCHAR *Key, int DefValue, const TCHAR *Filename) +{ + TCHAR LocalBuffer[2] = __T(""); + int ret; + + ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); + LocalBuffer[0] = (TCHAR)_totupper((int)LocalBuffer[0]); + if (LocalBuffer[0] == 'Y' || LocalBuffer[0] == '1' || LocalBuffer[0] == 'T') + ret = 1; + else if (LocalBuffer[0] == 'N' || LocalBuffer[0] == '0' || LocalBuffer[0] == 'F') + ret = 0; + else + ret = DefValue; + + return(ret); +} + +/** ini_getsection() + * \param idx the zero-based sequence number of the section to return + * \param Buffer a pointer to the buffer to copy into + * \param BufferSize the maximum number of characters to copy + * \param Filename the name and full path of the .ini file to read from + * + * \return the number of characters copied into the supplied buffer + */ +int ini_getsection(int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename) +{ + INI_FILETYPE fp; + int ok = 0; + + if (Buffer == NULL || BufferSize <= 0 || idx < 0) + return 0; + if (ini_openread(Filename, &fp)) { + ok = getkeystring(&fp, NULL, NULL, idx, -1, Buffer, BufferSize, NULL); + (void)ini_close(&fp); + } /* if */ + if (!ok) + *Buffer = '\0'; + return (int)_tcslen(Buffer); +} + +/** ini_getkey() + * \param Section the name of the section to browse through, or NULL to + * browse through the keys outside any section + * \param idx the zero-based sequence number of the key to return + * \param Buffer a pointer to the buffer to copy into + * \param BufferSize the maximum number of characters to copy + * \param Filename the name and full path of the .ini file to read from + * + * \return the number of characters copied into the supplied buffer + */ +int ini_getkey(const TCHAR *Section, int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename) +{ + INI_FILETYPE fp; + int ok = 0; + + if (Buffer == NULL || BufferSize <= 0 || idx < 0) + return 0; + if (ini_openread(Filename, &fp)) { + ok = getkeystring(&fp, Section, NULL, -1, idx, Buffer, BufferSize, NULL); + (void)ini_close(&fp); + } /* if */ + if (!ok) + *Buffer = '\0'; + return (int)_tcslen(Buffer); +} + + +#if !defined INI_NOBROWSE +/** ini_browse() + * \param Callback a pointer to a function that will be called for every + * setting in the INI file. + * \param UserData arbitrary data, which the function passes on the + * \c Callback function + * \param Filename the name and full path of the .ini file to read from + * + * \return 1 on success, 0 on failure (INI file not found) + * + * \note The \c Callback function must return 1 to continue + * browsing through the INI file, or 0 to stop. Even when the + * callback stops the browsing, this function will return 1 + * (for success). + */ +int ini_browse(INI_CALLBACK Callback, void *UserData, const TCHAR *Filename) +{ + TCHAR LocalBuffer[INI_BUFFERSIZE]; + int lenSec, lenKey; + enum quote_option quotes; + INI_FILETYPE fp; + + if (Callback == NULL) + return 0; + if (!ini_openread(Filename, &fp)) + return 0; + + LocalBuffer[0] = '\0'; /* copy an empty section in the buffer */ + lenSec = (int)_tcslen(LocalBuffer) + 1; + for ( ;; ) { + TCHAR *sp, *ep; + if (!ini_read(LocalBuffer + lenSec, INI_BUFFERSIZE - lenSec, &fp)) + break; + sp = skipleading(LocalBuffer + lenSec); + /* ignore empty strings and comments */ + if (*sp == '\0' || *sp == ';' || *sp == '#') + continue; + /* see whether we reached a new section */ + ep = _tcsrchr(sp, ']'); + if (*sp == '[' && ep != NULL) { + *ep = '\0'; + ini_strncpy(LocalBuffer, sp + 1, INI_BUFFERSIZE, QUOTE_NONE); + lenSec = (int)_tcslen(LocalBuffer) + 1; + continue; + } /* if */ + /* not a new section, test for a key/value pair */ + ep = _tcschr(sp, '='); /* test for the equal sign or colon */ + if (ep == NULL) + ep = _tcschr(sp, ':'); + if (ep == NULL) + continue; /* invalid line, ignore */ + *ep++ = '\0'; /* split the key from the value */ + striptrailing(sp); + ini_strncpy(LocalBuffer + lenSec, sp, INI_BUFFERSIZE - lenSec, QUOTE_NONE); + lenKey = (int)_tcslen(LocalBuffer + lenSec) + 1; + /* clean up the value */ + sp = skipleading(ep); + sp = cleanstring(sp, "es); /* Remove a trailing comment */ + ini_strncpy(LocalBuffer + lenSec + lenKey, sp, INI_BUFFERSIZE - lenSec - lenKey, quotes); + /* call the callback */ + if (!Callback(LocalBuffer, LocalBuffer + lenSec, LocalBuffer + lenSec + lenKey, UserData)) + break; + } /* for */ + + (void)ini_close(&fp); + return 1; +} +#endif /* INI_NOBROWSE */ + +#if ! defined INI_READONLY +static void ini_tempname(TCHAR *dest, const TCHAR *source, int maxlength) +{ + TCHAR *p; + + ini_strncpy(dest, source, maxlength, QUOTE_NONE); + p = _tcsrchr(dest, '\0'); + assert(p != NULL); + *(p - 1) = '~'; +} + +static enum quote_option check_enquote(const TCHAR *Value) +{ + const TCHAR *p; + + /* run through the value, if it has trailing spaces, or '"', ';' or '#' + * characters, enquote it + */ + assert(Value != NULL); + for (p = Value; *p != '\0' && *p != '"' && *p != ';' && *p != '#'; p++) + /* nothing */; + return (*p != '\0' || (p > Value && *(p - 1) == ' ')) ? QUOTE_ENQUOTE : QUOTE_NONE; +} + +static void writesection(TCHAR *LocalBuffer, const TCHAR *Section, INI_FILETYPE *fp) +{ + if (Section != NULL && _tcslen(Section) > 0) { + TCHAR *p; + LocalBuffer[0] = '['; + ini_strncpy(LocalBuffer + 1, Section, INI_BUFFERSIZE - 4, QUOTE_NONE); /* -1 for '[', -1 for ']', -2 for '\r\n' */ + p = _tcsrchr(LocalBuffer, '\0'); + assert(p != NULL); + *p++ = ']'; + _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ + if (fp != NULL) + (void)ini_write(LocalBuffer, fp); + } /* if */ +} + +static void writekey(TCHAR *LocalBuffer, const TCHAR *Key, const TCHAR *Value, INI_FILETYPE *fp) +{ + TCHAR *p; + enum quote_option option = check_enquote(Value); + ini_strncpy(LocalBuffer, Key, INI_BUFFERSIZE - 3, QUOTE_NONE); /* -1 for '=', -2 for '\r\n' */ + p = _tcsrchr(LocalBuffer, '\0'); + assert(p != NULL); + *p++ = '='; + ini_strncpy(p, Value, INI_BUFFERSIZE - (p - LocalBuffer) - 2, option); /* -2 for '\r\n' */ + p = _tcsrchr(LocalBuffer, '\0'); + assert(p != NULL); + _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ + if (fp != NULL) + (void)ini_write(LocalBuffer, fp); +} + +static int cache_accum(const TCHAR *string, int *size, int max) +{ + int len = (int)_tcslen(string); + if (*size + len >= max) + return 0; + *size += len; + return 1; +} + +static int cache_flush(TCHAR *buffer, int *size, + INI_FILETYPE *rfp, INI_FILETYPE *wfp, INI_FILEPOS *mark) +{ + int terminator_len = (int)_tcslen(INI_LINETERM); + int pos = 0; + + (void)ini_seek(rfp, mark); + assert(buffer != NULL); + buffer[0] = '\0'; + assert(size != NULL); + assert(*size <= INI_BUFFERSIZE); + while (pos < *size) { + (void)ini_read(buffer + pos, INI_BUFFERSIZE - pos, rfp); + while (pos < *size && buffer[pos] != '\0') + pos++; /* cannot use _tcslen() because buffer may not be zero-terminated */ + } /* while */ + if (buffer[0] != '\0') { + assert(pos > 0 && pos <= INI_BUFFERSIZE); + if (pos == INI_BUFFERSIZE) + pos--; + buffer[pos] = '\0'; /* force zero-termination (may be left unterminated in the above while loop) */ + (void)ini_write(buffer, wfp); + } + ini_tell(rfp, mark); /* update mark */ + *size = 0; + /* return whether the buffer ended with a line termination */ + return (pos > terminator_len) && (_tcscmp(buffer + pos - terminator_len, INI_LINETERM) == 0); +} + +static int close_rename(INI_FILETYPE *rfp, INI_FILETYPE *wfp, const TCHAR *filename, TCHAR *buffer) +{ + (void)ini_close(rfp); + (void)ini_close(wfp); + (void)ini_remove(filename); + (void)ini_tempname(buffer, filename, INI_BUFFERSIZE); + (void)ini_rename(buffer, filename); + return 1; +} + +/** ini_puts() + * \param Section the name of the section to write the string in + * \param Key the name of the entry to write, or NULL to erase all keys in the section + * \param Value a pointer to the buffer the string, or NULL to erase the key + * \param Filename the name and full path of the .ini file to write to + * + * \return 1 if successful, otherwise 0 + */ +int ini_puts(const TCHAR *Section, const TCHAR *Key, const TCHAR *Value, const TCHAR *Filename) +{ + INI_FILETYPE rfp; + INI_FILETYPE wfp; + INI_FILEPOS mark; + INI_FILEPOS head, tail; + TCHAR *sp, *ep; + TCHAR LocalBuffer[INI_BUFFERSIZE]; + int len, match, flag, cachelen; + + assert(Filename != NULL); + if (!ini_openread(Filename, &rfp)) { + /* If the .ini file doesn't exist, make a new file */ + if (Key != NULL && Value != NULL) { + if (!ini_openwrite(Filename, &wfp)) + return 0; + writesection(LocalBuffer, Section, &wfp); + writekey(LocalBuffer, Key, Value, &wfp); + (void)ini_close(&wfp); + } /* if */ + return 1; + } /* if */ + + /* If parameters Key and Value are valid (so this is not an "erase" request) + * and the setting already exists, there are two short-cuts to avoid rewriting + * the INI file. + */ + if (Key != NULL && Value != NULL) { + ini_tell(&rfp, &mark); + match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), &head); + if (match) { + /* if the current setting is identical to the one to write, there is + * nothing to do. + */ + if (_tcscmp(LocalBuffer,Value) == 0) { + (void)ini_close(&rfp); + return 1; + } /* if */ + /* if the new setting has the same length as the current setting, and the + * glue file permits file read/write access, we can modify in place. + */ + #if defined ini_openrewrite + /* we already have the start of the (raw) line, get the end too */ + ini_tell(&rfp, &tail); + /* create new buffer (without writing it to file) */ + writekey(LocalBuffer, Key, Value, NULL); + if (_tcslen(LocalBuffer) == (size_t)(tail - head)) { + /* length matches, close the file & re-open for read/write, then + * write at the correct position + */ + (void)ini_close(&rfp); + if (!ini_openrewrite(Filename, &wfp)) + return 0; + (void)ini_seek(&wfp, &head); + (void)ini_write(LocalBuffer, &wfp); + (void)ini_close(&wfp); + return 1; + } /* if */ + #endif + } /* if */ + /* key not found, or different value & length -> proceed (but rewind the + * input file first) + */ + (void)ini_seek(&rfp, &mark); + } /* if */ + + /* Get a temporary file name to copy to. Use the existing name, but with + * the last character set to a '~'. + */ + ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE); + if (!ini_openwrite(LocalBuffer, &wfp)) { + (void)ini_close(&rfp); + return 0; + } /* if */ + (void)ini_tell(&rfp, &mark); + cachelen = 0; + + /* Move through the file one line at a time until a section is + * matched or until EOF. Copy to temp file as it is read. + */ + len = (Section != NULL) ? (int)_tcslen(Section) : 0; + if (len > 0) { + do { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + /* Failed to find section, so add one to the end */ + flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + if (Key!=NULL && Value!=NULL) { + if (!flag) + (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ + writesection(LocalBuffer, Section, &wfp); + writekey(LocalBuffer, Key, Value, &wfp); + } /* if */ + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ + } /* if */ + /* Copy the line from source to dest, but not if this is the section that + * we are looking for and this section must be removed + */ + sp = skipleading(LocalBuffer); + ep = _tcsrchr(sp, ']'); + match = (*sp == '[' && ep != NULL && (int)(ep-sp-1) == len && _tcsnicmp(sp + 1,Section,len) == 0); + if (!match || Key != NULL) { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* if */ + } while (!match); + } /* if */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + /* when deleting a section, the section head that was just found has not been + * copied to the output file, but because this line was not "accumulated" in + * the cache, the position in the input file was reset to the point just + * before the section; this must now be skipped (again) + */ + if (Key == NULL) { + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + (void)ini_tell(&rfp, &mark); + } /* if */ + + /* Now that the section has been found, find the entry. Stop searching + * upon leaving the section's area. Copy the file as it is read + * and create an entry if one is not found. + */ + len = (Key != NULL) ? (int)_tcslen(Key) : 0; + for( ;; ) { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + /* EOF without an entry so make one */ + flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + if (Key!=NULL && Value!=NULL) { + if (!flag) + (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ + writekey(LocalBuffer, Key, Value, &wfp); + } /* if */ + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ + } /* if */ + sp = skipleading(LocalBuffer); + ep = _tcschr(sp, '='); /* Parse out the equal sign */ + if (ep == NULL) + ep = _tcschr(sp, ':'); + match = (ep != NULL && len > 0 && (int)(skiptrailing(ep,sp)-sp) == len && _tcsnicmp(sp,Key,len) == 0); + if ((Key != NULL && match) || *sp == '[') + break; /* found the key, or found a new section */ + /* copy other keys in the section */ + if (Key == NULL) { + (void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */ + } else { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* if */ + } /* for */ + /* the key was found, or we just dropped on the next section (meaning that it + * wasn't found); in both cases we need to write the key, but in the latter + * case, we also need to write the line starting the new section after writing + * the key + */ + flag = (*sp == '['); + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + if (Key != NULL && Value != NULL) + writekey(LocalBuffer, Key, Value, &wfp); + /* cache_flush() reset the "read pointer" to the start of the line with the + * previous key or the new section; read it again (because writekey() destroyed + * the buffer) + */ + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + if (flag) { + /* the new section heading needs to be copied to the output file */ + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } else { + /* forget the old key line */ + (void)ini_tell(&rfp, &mark); + } /* if */ + /* Copy the rest of the INI file */ + while (ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* while */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ +} + +/* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" book. */ +#define ABS(v) ((v) < 0 ? -(v) : (v)) + +static void strreverse(TCHAR *str) +{ + int i, j; + for (i = 0, j = (int)_tcslen(str) - 1; i < j; i++, j--) { + TCHAR t = str[i]; + str[i] = str[j]; + str[j] = t; + } /* for */ +} + +static void long2str(long value, TCHAR *str) +{ + int i = 0; + long sign = value; + + /* generate digits in reverse order */ + do { + int n = (int)(value % 10); /* get next lowest digit */ + str[i++] = (TCHAR)(ABS(n) + '0'); /* handle case of negative digit */ + } while (value /= 10); /* delete the lowest digit */ + if (sign < 0) + str[i++] = '-'; + str[i] = '\0'; + + strreverse(str); +} + +/** ini_putl() + * \param Section the name of the section to write the value in + * \param Key the name of the entry to write + * \param Value the value to write + * \param Filename the name and full path of the .ini file to write to + * + * \return 1 if successful, otherwise 0 + */ +int ini_putl(const TCHAR *Section, const TCHAR *Key, long Value, const TCHAR *Filename) +{ + TCHAR LocalBuffer[32]; + long2str(Value, LocalBuffer); + return ini_puts(Section, Key, LocalBuffer, Filename); +} + +#if defined INI_REAL +/** ini_putf() + * \param Section the name of the section to write the value in + * \param Key the name of the entry to write + * \param Value the value to write + * \param Filename the name and full path of the .ini file to write to + * + * \return 1 if successful, otherwise 0 + */ +int ini_putf(const TCHAR *Section, const TCHAR *Key, INI_REAL Value, const TCHAR *Filename) +{ + TCHAR LocalBuffer[64]; + ini_ftoa(LocalBuffer, Value); + return ini_puts(Section, Key, LocalBuffer, Filename); +} +#endif /* INI_REAL */ + +static void putsection(TCHAR *LocalBuffer, const TCHAR *Section, const TCHAR **Keys, const TCHAR **Values, INI_FILETYPE *fp) { + if(Keys && Values && *Keys && *Values) { + writesection(LocalBuffer, Section, fp); + while(*Keys && *Values) { + writekey(LocalBuffer, *Keys, *Values, fp); + Keys++; + Values++; + } + (void)ini_write(INI_LINETERM, fp); /* force a new line behind the last line of the section */ + } +} + +int ini_putsection(const TCHAR *Section, const TCHAR **Keys, const TCHAR **Values, const TCHAR *Filename) +{ + INI_FILETYPE rfp; + INI_FILETYPE wfp; + INI_FILEPOS mark; + TCHAR *sp = NULL; + TCHAR *ep; + TCHAR LocalBuffer[INI_BUFFERSIZE]; + int len, match, flag, cachelen; + + assert(Filename != NULL); + if (!ini_openread(Filename, &rfp)) { + /* If the .ini file doesn't exist, make a new file */ + if (!ini_openwrite(Filename, &wfp)) + return 0; + putsection(LocalBuffer, Section, Keys, Values, &wfp); + (void)ini_close(&wfp); + return 1; + } /* if */ + + /* Get a temporary file name to copy to. Use the existing name, but with + * the last character set to a '~'. + */ + ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE); + if (!ini_openwrite(LocalBuffer, &wfp)) { + (void)ini_close(&rfp); + return 0; + } /* if */ + (void)ini_tell(&rfp, &mark); + cachelen = 0; + + /* Move through the file one line at a time until a section is + * matched or until EOF. Copy to temp file as it is read. + */ + len = (Section != NULL) ? (int)_tcslen(Section) : 0; + if (len > 0) { + do { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + /* Failed to find section, so add one to the end */ + flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + if (!flag) + (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ + + putsection(LocalBuffer, Section, Keys, Values, &wfp); + + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ + } /* if */ + /* Copy the line from source to dest, but not if this is the section that + * we are looking for + */ + sp = skipleading(LocalBuffer); + ep = _tcsrchr(sp, ']'); + match = (*sp == '[' && ep != NULL && (int)(ep-sp-1) == len && _tcsnicmp(sp + 1,Section,len) == 0); + if (!match) { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* if */ + } while (!match); + } /* if */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + /* when deleting a section, the section head that was just found has not been + * copied to the output file, but because this line was not "accumulated" in + * the cache, the position in the input file was reset to the point just + * before the section; this must now be skipped (again) + */ + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + (void)ini_tell(&rfp, &mark); + + /* Now that the section has been found, find the entry. Stop searching + * upon leaving the section's area. Copy the file as it is read + * and create an entry if one is not found. + */ + len = 0; + for( ;; ) { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + /* EOF without an entry */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */ + break; + } /* if */ + sp = skipleading(LocalBuffer); + if (*sp == '[') + break; /* found a new section */ + /* copy other keys in the section */ + (void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */ + } /* for */ + /* we just dropped on the next section + * we also need to write the line starting the new section after writing + */ + flag = (sp && *sp == '['); + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + putsection(LocalBuffer, Section, Keys, Values, &wfp); + + /* cache_flush() reset the "read pointer" to the start of the line with the + * previous key or the new section; read it again (because writekey() destroyed + * the buffer) + */ + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + if (flag) { + /* the new section heading needs to be copied to the output file */ + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } else { + /* forget the old key line */ + (void)ini_tell(&rfp, &mark); + } /* if */ + /* Copy the rest of the INI file */ + while (ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* while */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ +} + +#endif /* !INI_READONLY */ diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minIni.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minIni.h new file mode 100644 index 00000000..afbe5042 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/minIni.h @@ -0,0 +1,158 @@ +/* minIni - Multi-Platform INI file parser, suitable for embedded systems + * + * Copyright (c) CompuPhase, 2008-2017 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Version: $Id: minIni.h 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $ + */ +#ifndef MININI_H +#define MININI_H + +#include "minGlue.h" + +#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY + #include + #define mTCHAR TCHAR +#else + /* force TCHAR to be "char", but only for minIni */ + #define mTCHAR char +#endif + +#if !defined INI_BUFFERSIZE + #define INI_BUFFERSIZE 512 +#endif + +#if defined __cplusplus + extern "C" { +#endif + +int ini_getbool(const mTCHAR *Section, const mTCHAR *Key, int DefValue, const mTCHAR *Filename); +long ini_getl(const mTCHAR *Section, const mTCHAR *Key, long DefValue, const mTCHAR *Filename); +int ini_gets(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *DefValue, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename); +int ini_getsection(int idx, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename); +int ini_getkey(const mTCHAR *Section, int idx, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename); + +#if defined INI_REAL +INI_REAL ini_getf(const mTCHAR *Section, const mTCHAR *Key, INI_REAL DefValue, const mTCHAR *Filename); +#endif + +#if !defined INI_READONLY +int ini_putl(const mTCHAR *Section, const mTCHAR *Key, long Value, const mTCHAR *Filename); +int ini_puts(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, const mTCHAR *Filename); +int ini_putsection(const mTCHAR *Section, const mTCHAR **Keys, const mTCHAR **Values, const mTCHAR *Filename); +#if defined INI_REAL +int ini_putf(const mTCHAR *Section, const mTCHAR *Key, INI_REAL Value, const mTCHAR *Filename); +#endif +#endif /* INI_READONLY */ + +#if !defined INI_NOBROWSE +typedef int (*INI_CALLBACK)(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData); +int ini_browse(INI_CALLBACK Callback, void *UserData, const mTCHAR *Filename); +#endif /* INI_NOBROWSE */ + +#if defined __cplusplus + } +#endif + + +#if defined __cplusplus + +#if defined __WXWINDOWS__ + #include "wxMinIni.h" +#else + #include + + /* The C++ class in minIni.h was contributed by Steven Van Ingelgem. */ + class minIni + { + public: + minIni(const std::string& filename) : iniFilename(filename) + { } + + bool getbool(const std::string& Section, const std::string& Key, bool DefValue=false) const + { return ini_getbool(Section.c_str(), Key.c_str(), int(DefValue), iniFilename.c_str()) != 0; } + + long getl(const std::string& Section, const std::string& Key, long DefValue=0) const + { return ini_getl(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); } + + int geti(const std::string& Section, const std::string& Key, int DefValue=0) const + { return static_cast(this->getl(Section, Key, long(DefValue))); } + + std::string gets(const std::string& Section, const std::string& Key, const std::string& DefValue="") const + { + char buffer[INI_BUFFERSIZE]; + ini_gets(Section.c_str(), Key.c_str(), DefValue.c_str(), buffer, INI_BUFFERSIZE, iniFilename.c_str()); + return buffer; + } + + std::string getsection(int idx) const + { + char buffer[INI_BUFFERSIZE]; + ini_getsection(idx, buffer, INI_BUFFERSIZE, iniFilename.c_str()); + return buffer; + } + + std::string getkey(const std::string& Section, int idx) const + { + char buffer[INI_BUFFERSIZE]; + ini_getkey(Section.c_str(), idx, buffer, INI_BUFFERSIZE, iniFilename.c_str()); + return buffer; + } + +#if defined INI_REAL + INI_REAL getf(const std::string& Section, const std::string& Key, INI_REAL DefValue=0) const + { return ini_getf(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); } +#endif + +#if ! defined INI_READONLY + bool put(const std::string& Section, const std::string& Key, long Value) + { return ini_putl(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; } + + bool put(const std::string& Section, const std::string& Key, int Value) + { return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; } + + bool put(const std::string& Section, const std::string& Key, bool Value) + { return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; } + + bool put(const std::string& Section, const std::string& Key, const std::string& Value) + { return ini_puts(Section.c_str(), Key.c_str(), Value.c_str(), iniFilename.c_str()) != 0; } + + bool put(const std::string& Section, const std::string& Key, const char* Value) + { return ini_puts(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; } + +#if defined INI_REAL + bool put(const std::string& Section, const std::string& Key, INI_REAL Value) + { return ini_putf(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; } +#endif + + bool del(const std::string& Section, const std::string& Key) + { return ini_puts(Section.c_str(), Key.c_str(), 0, iniFilename.c_str()) != 0; } + + bool del(const std::string& Section) + { return ini_puts(Section.c_str(), 0, 0, iniFilename.c_str()) != 0; } +#endif + +#if !defined INI_NOBROWSE + bool browse(INI_CALLBACK Callback, void *UserData) const + { return ini_browse(Callback, UserData, iniFilename.c_str()) != 0; } +#endif + + private: + std::string iniFilename; + }; + +#endif /* __WXWINDOWS__ */ +#endif /* __cplusplus */ + +#endif /* MININI_H */ diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/test.c b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/test.c new file mode 100644 index 00000000..80508cd7 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/test.c @@ -0,0 +1,117 @@ +/* Simple test program + * + * gcc -o test test.c minIni.c + */ +#include +#include +#include +#include "minIni.h" + +#define sizearray(a) (sizeof(a) / sizeof((a)[0])) + +const char inifile[] = "test.ini"; +const char inifile2[] = "testplain.ini"; + +int Callback(const char *section, const char *key, const char *value, void *userdata) +{ + (void)userdata; /* this parameter is not used in this example */ + printf(" [%s]\t%s=%s\n", section, key, value); + return 1; +} + +int main(void) +{ + char str[100]; + long n; + int s, k; + char section[50]; + + /* string reading */ + n = ini_gets("first", "string", "dummy", str, sizearray(str), inifile); + assert(n==4 && strcmp(str,"noot")==0); + n = ini_gets("second", "string", "dummy", str, sizearray(str), inifile); + assert(n==4 && strcmp(str,"mies")==0); + n = ini_gets("first", "undefined", "dummy", str, sizearray(str), inifile); + assert(n==5 && strcmp(str,"dummy")==0); + /* ----- */ + n = ini_gets("", "string", "dummy", str, sizearray(str), inifile2); + assert(n==4 && strcmp(str,"noot")==0); + n = ini_gets(NULL, "string", "dummy", str, sizearray(str), inifile2); + assert(n==4 && strcmp(str,"noot")==0); + /* ----- */ + printf("1. String reading tests passed\n"); + + /* value reading */ + n = ini_getl("first", "val", -1, inifile); + assert(n==1); + n = ini_getl("second", "val", -1, inifile); + assert(n==2); + n = ini_getl("first", "undefined", -1, inifile); + assert(n==-1); + /* ----- */ + n = ini_getl(NULL, "val", -1, inifile2); + assert(n==1); + /* ----- */ + printf("2. Value reading tests passed\n"); + + /* string writing */ + n = ini_puts("first", "alt", "flagged as \"correct\"", inifile); + assert(n==1); + n = ini_gets("first", "alt", "dummy", str, sizearray(str), inifile); + assert(n==20 && strcmp(str,"flagged as \"correct\"")==0); + /* ----- */ + n = ini_puts("second", "alt", "correct", inifile); + assert(n==1); + n = ini_gets("second", "alt", "dummy", str, sizearray(str), inifile); + assert(n==7 && strcmp(str,"correct")==0); + /* ----- */ + n = ini_puts("third", "test", "correct", inifile); + assert(n==1); + n = ini_gets("third", "test", "dummy", str, sizearray(str), inifile); + assert(n==7 && strcmp(str,"correct")==0); + /* ----- */ + n = ini_puts("second", "alt", "overwrite", inifile); + assert(n==1); + n = ini_gets("second", "alt", "dummy", str, sizearray(str), inifile); + assert(n==9 && strcmp(str,"overwrite")==0); + /* ----- */ + n = ini_puts("second", "alt", "123456789", inifile); + assert(n==1); + n = ini_gets("second", "alt", "dummy", str, sizearray(str), inifile); + assert(n==9 && strcmp(str,"123456789")==0); + /* ----- */ + n = ini_puts(NULL, "alt", "correct", inifile2); + assert(n==1); + n = ini_gets(NULL, "alt", "dummy", str, sizearray(str), inifile2); + assert(n==7 && strcmp(str,"correct")==0); + /* ----- */ + printf("3. String writing tests passed\n"); + + /* section/key enumeration */ + printf("4. Section/key enumeration, file structure follows\n"); + for (s = 0; ini_getsection(s, section, sizearray(section), inifile) > 0; s++) { + printf(" [%s]\n", section); + for (k = 0; ini_getkey(section, k, str, sizearray(str), inifile) > 0; k++) { + printf("\t%s\n", str); + } /* for */ + } /* for */ + + /* browsing through the file */ + printf("5. browse through all settings, file field list follows\n"); + ini_browse(Callback, NULL, inifile); + + /* string deletion */ + n = ini_puts("first", "alt", NULL, inifile); + assert(n==1); + n = ini_puts("second", "alt", NULL, inifile); + assert(n==1); + n = ini_puts("third", NULL, NULL, inifile); + assert(n==1); + /* ----- */ + n = ini_puts(NULL, "alt", NULL, inifile2); + assert(n==1); + printf("6. String deletion tests passed\n"); + + return 0; +} + diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/test.ini b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/test.ini new file mode 100644 index 00000000..565aef78 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/test.ini @@ -0,0 +1,8 @@ +[First] +String=noot # trailing commment +Val=1 + +[Second] +Val = 2 +#comment=3 +String = mies diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/test2.cc b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/test2.cc new file mode 100644 index 00000000..4e19b319 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/test2.cc @@ -0,0 +1,80 @@ +/* + gcc -o minIni.o -c minIni.c + g++ -o test2.o -c test2.cc + g++ -o test2 test2.o minIni.o + ./test2 +*/ + + +#include +#include +#include +using namespace std ; + +#include "minIni.h" + +int main(void) +{ + minIni ini("test.ini"); + string s; + + /* string reading */ + s = ini.gets( "first", "string" , "aap" ); + assert(s == "noot"); + s = ini.gets( "second", "string" , "aap" ); + assert(s == "mies"); + s = ini.gets( "first", "dummy" , "aap" ); + assert(s == "aap"); + cout << "1. String reading tests passed" << endl ; + + + /* value reading */ + long n; + n = ini.getl("first", "val", -1 ); + assert(n==1); + n = ini.getl("second", "val", -1); + assert(n==2); + n = ini.getl("first", "dummy", -1); + assert(n==-1); + cout << "2. Value reading tests passed" << endl ; + + + /* string writing */ + bool b; + b = ini.put("first", "alt", "flagged as \"correct\""); + assert(b); + s = ini.gets("first", "alt", "aap"); + assert(s=="flagged as \"correct\""); + + b = ini.put("second", "alt", "correct"); + assert(b); + s = ini.gets("second", "alt", "aap"); + assert(s=="correct"); + + b = ini.put("third", "alt", "correct"); + assert(b); + s = ini.gets("third", "alt", "aap" ); + assert(s=="correct"); + cout << "3. String writing tests passed" << endl; + + /* section/key enumeration */ + cout << "4. section/key enumeration; file contents follows" << endl; + string section; + for (int is = 0; section = ini.getsection(is), section.length() > 0; is++) { + cout << " [" << section.c_str() << "]" << endl; + for (int ik = 0; s = ini.getkey(section, ik), s.length() > 0; ik++) { + cout << "\t" << s.c_str() << endl; + } + } + + /* string deletion */ + b = ini.del("first", "alt"); + assert(b); + b = ini.del("second", "alt"); + assert(b); + b = ini.del("third"); + assert(b); + cout << "5. string deletion passed " << endl; + + return 0; +} diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/testplain.ini b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/testplain.ini new file mode 100644 index 00000000..2a5ce4b6 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/testplain.ini @@ -0,0 +1,3 @@ +String=noot # trailing commment +#comment=3 +Val=1 diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/wxMinIni.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/wxMinIni.h new file mode 100644 index 00000000..f932bb63 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/dev/wxMinIni.h @@ -0,0 +1,101 @@ +/* minIni - Multi-Platform INI file parser, wxWidgets interface + * + * Copyright (c) CompuPhase, 2008-2012 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Version: $Id: wxMinIni.h 44 2012-01-04 15:52:56Z thiadmer.riemersma@gmail.com $ + */ +#ifndef WXMININI_H +#define WXMININI_H + +#include +#include "minIni.h" + +class minIni +{ +public: + minIni(const wxString& filename) : iniFilename(filename) + { } + + bool getbool(const wxString& Section, const wxString& Key, bool DefValue=false) const + { return ini_getbool(Section.utf8_str(), Key.utf8_str(), int(DefValue), iniFilename.utf8_str()) != 0; } + + long getl(const wxString& Section, const wxString& Key, long DefValue=0) const + { return ini_getl(Section.utf8_str(), Key.utf8_str(), DefValue, iniFilename.utf8_str()); } + + int geti(const wxString& Section, const wxString& Key, int DefValue=0) const + { return static_cast(ini_getl(Section.utf8_str(), Key.utf8_str(), (long)DefValue, iniFilename.utf8_str())); } + + wxString gets(const wxString& Section, const wxString& Key, const wxString& DefValue=wxT("")) const + { + char buffer[INI_BUFFERSIZE]; + ini_gets(Section.utf8_str(), Key.utf8_str(), DefValue.utf8_str(), buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); + wxString result = wxString::FromUTF8(buffer); + return result; + } + + wxString getsection(int idx) const + { + char buffer[INI_BUFFERSIZE]; + ini_getsection(idx, buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); + wxString result = wxString::FromUTF8(buffer); + return result; + } + + wxString getkey(const wxString& Section, int idx) const + { + char buffer[INI_BUFFERSIZE]; + ini_getkey(Section.utf8_str(), idx, buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); + wxString result = wxString::FromUTF8(buffer); + return result; + } + +#if defined INI_REAL + INI_REAL getf(const wxString& Section, wxString& Key, INI_REAL DefValue=0) const + { return ini_getf(Section.utf8_str(), Key.utf8_str(), DefValue, iniFilename.utf8_str()); } +#endif + +#if ! defined INI_READONLY + bool put(const wxString& Section, const wxString& Key, long Value) const + { return ini_putl(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; } + + bool put(const wxString& Section, const wxString& Key, int Value) const + { return ini_putl(Section.utf8_str(), Key.utf8_str(), (long)Value, iniFilename.utf8_str()) != 0; } + + bool put(const wxString& Section, const wxString& Key, bool Value) const + { return ini_putl(Section.utf8_str(), Key.utf8_str(), (long)Value, iniFilename.utf8_str()) != 0; } + + bool put(const wxString& Section, const wxString& Key, const wxString& Value) const + { return ini_puts(Section.utf8_str(), Key.utf8_str(), Value.utf8_str(), iniFilename.utf8_str()) != 0; } + + bool put(const wxString& Section, const wxString& Key, const char* Value) const + { return ini_puts(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; } + +#if defined INI_REAL + bool put(const wxString& Section, const wxString& Key, INI_REAL Value) const + { return ini_putf(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; } +#endif + + bool del(const wxString& Section, const wxString& Key) const + { return ini_puts(Section.utf8_str(), Key.utf8_str(), 0, iniFilename.utf8_str()) != 0; } + + bool del(const wxString& Section) const + { return ini_puts(Section.utf8_str(), 0, 0, iniFilename.utf8_str()) != 0; } +#endif + +private: + wxString iniFilename; +}; + +#endif /* WXMININI_H */ diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/doc/minIni.pdf b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/doc/minIni.pdf new file mode 100644 index 00000000..603d13ea Binary files /dev/null and b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/doc/minIni.pdf differ diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/include/minIni.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/include/minIni.h new file mode 100644 index 00000000..b4f10673 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/minIni/include/minIni.h @@ -0,0 +1,21 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "../dev/minIni.h" + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/.gitignore b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/.gitignore new file mode 100644 index 00000000..0806f7bf --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/.gitignore @@ -0,0 +1,13 @@ +# Editor files +*.swp +*~ + +# Objects +*.o +*.a +*.so + +# Lib +lib +release +debug \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/Makefile b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/Makefile new file mode 100644 index 00000000..1c291cd2 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/Makefile @@ -0,0 +1,132 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +SOURCES := src +DATA := data +INCLUDES := include + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIC -ftls-model=local-exec + +CFLAGS := -g -Wall -Werror \ + -ffunction-sections \ + -fdata-sections \ + $(ARCH) \ + $(BUILD_CFLAGS) + +CFLAGS += $(INCLUDE) + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +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)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +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 := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +.PHONY: clean all lib/lib$(TARGET).a lib/lib$(TARGET)d.a + +#--------------------------------------------------------------------------------- +all: lib/lib$(TARGET).a lib/lib$(TARGET)d.a + +lib: + @[ -d $@ ] || mkdir -p $@ + +release: + @[ -d $@ ] || mkdir -p $@ + +debug: + @[ -d $@ ] || mkdir -p $@ + +lib/lib$(TARGET).a : lib release $(SOURCES) $(INCLUDES) + @$(MAKE) BUILD=release OUTPUT=$(CURDIR)/$@ \ + BUILD_CFLAGS="-DNDEBUG=1 -O2" \ + DEPSDIR=$(CURDIR)/release \ + --no-print-directory -C release \ + -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr release debug lib + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +$(OFILES_SRC) : $(HFILES) + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt.h new file mode 100644 index 00000000..ea82f8e2 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt.h @@ -0,0 +1,15 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include "nxExt/apm_ext.h" +#include "nxExt/ipc_server.h" +#include "nxExt/cpp/lockable_mutex.h" diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt/apm_ext.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt/apm_ext.h new file mode 100644 index 00000000..b550fd3f --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt/apm_ext.h @@ -0,0 +1,39 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +Result apmExtInitialize(void); +void apmExtExit(void); + +// Silently fail +Result apmExtSysRequestPerformanceMode(u32 mode); +Result apmExtSysSetCpuBoostMode(u32 mode); + +Result apmExtGetPerformanceMode(u32 *out_mode); +Result apmExtGetCurrentPerformanceConfiguration(u32 *out_conf); + +inline bool apmExtIsCPUBoosted(u32 conf_id) { // CPU boosted to 1785 MHz + return (conf_id == 0x92220009 || conf_id == 0x9222000A); +}; +inline bool apmExtIsBoostMode(u32 conf_id) { // GPU throttled to 76.8 MHz + return (conf_id >= 0x92220009 && conf_id <= 0x9222000C); +}; + +#ifdef __cplusplus +} +#endif diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt/cpp/lockable_mutex.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt/cpp/lockable_mutex.h new file mode 100644 index 00000000..44f19f2d --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt/cpp/lockable_mutex.h @@ -0,0 +1,64 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#ifdef __cplusplus + +#include +#include + +class LockableMutex +{ +public: + LockableMutex() + { + mutexInit(&this->m); + } + + virtual ~LockableMutex() {} + + void Lock() + { + mutexLock(&this->m); + } + + bool TryLock() + { + return mutexTryLock(&this->m); + } + + void Unlock() + { + mutexUnlock(&this->m); + } + + // snake_case aliases in order to implement Lockable + + void lock() + { + this->Lock(); + } + + bool try_lock() + { + return this->TryLock(); + } + + void unlock() + { + this->Unlock(); + } + +private: + Mutex m; +}; + +#endif diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt/ipc_server.h b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt/ipc_server.h new file mode 100644 index 00000000..31397961 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/include/nxExt/ipc_server.h @@ -0,0 +1,64 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#pragma once + +#include + +#define IPC_SERVER_EXT_RESPONSE_MAX_DATA_SIZE (0x100 - 0x10 - sizeof(IpcServerRawHeader)) + +typedef struct +{ + u64 magic; + union + { + u64 cmdId; + u64 result; + }; +} IpcServerRawHeader; + +typedef struct +{ + SmServiceName srvName; + Handle handles[MAX_WAIT_OBJECTS]; + u32 max; + u32 count; +} IpcServer; + +typedef struct +{ + u64 cmdId; + void* ptr; + size_t size; +} IpcServerRequestData; + +typedef struct +{ + HipcParsedRequest hipc; + IpcServerRequestData data; +} IpcServerRequest; + +typedef Result (*IpcServerRequestHandler)(void* userdata, const IpcServerRequest* r, u8* out_data, size_t* out_dataSize); + +Result ipcServerInit(IpcServer* server, const char* name, u32 max_sessions); +Result ipcServerExit(IpcServer* server); +Result ipcServerProcess(IpcServer* server, IpcServerRequestHandler handler, void* userdata); +Result ipcServerParseCommand(const IpcServerRequest* r, size_t *out_datasize, void** out_data, u64* out_cmd); + +#ifdef __cplusplus +} +#endif diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/src/apm_ext.c b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/src/apm_ext.c new file mode 100644 index 00000000..96102dd8 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/src/apm_ext.c @@ -0,0 +1,64 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "nxExt/apm_ext.h" + +#include + +static Service g_apmSrv; +static Service g_apmSysSrv; +static atomic_size_t g_refCnt; + +Result apmExtInitialize(void) +{ + g_refCnt++; + + if (serviceIsActive(&g_apmSrv)) + return 0; + + Result rc = smGetService(&g_apmSrv, "apm"); + + if(R_SUCCEEDED(rc)) + rc = smGetService(&g_apmSysSrv, "apm:sys"); + + if (R_FAILED(rc)) + apmExtExit(); + + return rc; +} + +void apmExtExit(void) +{ + if (--g_refCnt == 0) + { + serviceClose(&g_apmSrv); + serviceClose(&g_apmSysSrv); + } +} + +Result apmExtSysSetCpuBoostMode(u32 mode) +{ + return serviceDispatchIn(&g_apmSysSrv, 6, mode); +} + +Result apmExtGetPerformanceMode(u32 *out_mode) +{ + return serviceDispatchOut(&g_apmSrv, 1, *out_mode); +} + +Result apmExtSysRequestPerformanceMode(u32 mode) +{ + return serviceDispatchIn(&g_apmSysSrv, 0, mode); +} + +Result apmExtGetCurrentPerformanceConfiguration(u32 *out_conf) +{ + return serviceDispatchOut(&g_apmSysSrv, 7, *out_conf); +} diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/src/ipc_server.c b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/src/ipc_server.c new file mode 100644 index 00000000..1953c707 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/lib/nxExt/src/ipc_server.c @@ -0,0 +1,204 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "nxExt/ipc_server.h" +#include + +Result ipcServerInit(IpcServer* server, const char* name, u32 max_sessions) +{ + if(max_sessions < 1 || max_sessions > (MAX_WAIT_OBJECTS - 1)) + { + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + } + + server->srvName = smEncodeName(name); + server->max = max_sessions + 1; + server->count = 0; + + Result rc = smRegisterService(&server->handles[0], server->srvName, false, max_sessions); + if(R_SUCCEEDED(rc)) + { + server->count = 1; + } + return rc; +} + +Result ipcServerExit(IpcServer* server) +{ + for(u32 i = 0; i < server->count; i++) + { + svcCloseHandle(server->handles[i]); + } + server->count = 0; + return smUnregisterService(server->srvName); +} + +static Result _ipcServerAddSession(IpcServer* server, Handle session) +{ + if(server->count >= server->max) + { + return MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + } + + server->handles[server->count] = session; + server->count++; + return 0; +} + +static Result _ipcServerDeleteSession(IpcServer* server, u32 index) +{ + if(!index || index >= server->count) + { + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + } + + svcCloseHandle(server->handles[index]); + + for(u32 j = index; j < (server->count - 1); j++) + { + server->handles[j] = server->handles[j + 1]; + } + server->count--; + return 0; +} + +static Result _ipcServerParseRequest(IpcServerRequest* r) +{ + u8* base = armGetTls(); + + r->hipc = hipcParseRequest(base); + r->data.cmdId = 0; + r->data.size = 0; + r->data.ptr = NULL; + + if(r->hipc.meta.type == CmifCommandType_Request) + { + IpcServerRawHeader* header = cmifGetAlignedDataStart(r->hipc.data.data_words, base); + size_t dataSize = r->hipc.meta.num_data_words * 4; + + if(!header || dataSize < sizeof(IpcServerRawHeader) || header->magic != CMIF_IN_HEADER_MAGIC) + { + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + } + + r->data.cmdId = header->cmdId; + if(dataSize > sizeof(IpcServerRawHeader)) + { + r->data.size = dataSize - sizeof(IpcServerRawHeader); + r->data.ptr = ((u8*)header) + sizeof(IpcServerRawHeader); + } + } + + return 0; +} + +static void _ipcServerPrepareResponse(Result rc, void* data, size_t dataSize) +{ + u8* base = armGetTls(); + HipcRequest hipc = hipcMakeRequestInline(base, + .type = CmifCommandType_Request, + .num_data_words = (sizeof(IpcServerRawHeader) + dataSize + 0x10) / 4, + ); + + IpcServerRawHeader* rawHeader = cmifGetAlignedDataStart(hipc.data_words, base); + rawHeader->magic = CMIF_OUT_HEADER_MAGIC; + rawHeader->result = rc; + + if(R_SUCCEEDED(rc)) + { + memcpy(((u8*)rawHeader) + sizeof(IpcServerRawHeader), data, dataSize); + } +} + +static Result _ipcServerProcessNewSession(IpcServer* server) +{ + Handle session; + Result rc = svcAcceptSession(&session, server->handles[0]); + if(R_SUCCEEDED(rc) && R_FAILED(rc = _ipcServerAddSession(server, session))) + { + svcCloseHandle(session); + } + return rc; +} + +static Result _ipcServerProcessSession(IpcServer* server, IpcServerRequestHandler handler, void* userdata, u32 handleIndex) +{ + s32 unusedIndex; + IpcServerRequest r; + size_t dataSize = 0; + u8 data[IPC_SERVER_EXT_RESPONSE_MAX_DATA_SIZE]; + bool close = false; + + Result rc = svcReplyAndReceive(&unusedIndex, &server->handles[handleIndex], 1, 0, UINT64_MAX); + if(R_SUCCEEDED(rc)) + { + rc = _ipcServerParseRequest(&r); + } + + if(R_SUCCEEDED(rc)) + { + switch(r.hipc.meta.type) + { + case CmifCommandType_Request: + _ipcServerPrepareResponse( + handler(userdata, &r, data, &dataSize), + data, + dataSize + ); + break; + case CmifCommandType_Close: + _ipcServerPrepareResponse(0, NULL, 0); + close = true; + break; + default: + _ipcServerPrepareResponse(MAKERESULT(11, 403), NULL, 0); + break; + } + + rc = svcReplyAndReceive(&unusedIndex, &server->handles[handleIndex], 0, server->handles[handleIndex], 0); + if(rc == KERNELRESULT(TimedOut)) + { + rc = 0; + } + } + + if(R_FAILED(rc) || close) + { + _ipcServerDeleteSession(server, handleIndex); + } + + return rc; +} + +Result ipcServerProcess(IpcServer* server, IpcServerRequestHandler handler, void* userdata) +{ + s32 handleIndex = -1; + Result rc = svcWaitSynchronization(&handleIndex, server->handles, server->count, UINT64_MAX); + + if(R_SUCCEEDED(rc) && (handleIndex < 0 || handleIndex >= server->count)) + { + rc = MAKERESULT(Module_Libnx, LibnxError_NotFound); + } + + if(R_SUCCEEDED(rc)) + { + if(handleIndex) + { + rc = _ipcServerProcessSession(server, handler, userdata, handleIndex); + } + else + { + rc = _ipcServerProcessNewSession(server); + } + } + + return rc; +} + diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/perms.json b/Source/sys-clk-OC(deprecated)/sysmodule/perms.json new file mode 100644 index 00000000..b37c0408 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/perms.json @@ -0,0 +1,103 @@ +{ + "name": "sysclkOC", + "title_id": "0x00FF0000636C6BFF", + "title_id_range_min": "0x00FF0000636C6BFF", + "title_id_range_max": "0x00FF0000636C6BFF", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 49, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": [ + "*" + ], + "service_host": [ + "sysclkOC" + ], + "kernel_capabilities": [ + { + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 0, + "highest_cpu_id": 3 + } + }, + { + "type": "syscalls", + "value": { + "svcSetHeapSize": "0x01", + "svcSetMemoryPermission": "0x02", + "svcSetMemoryAttribute": "0x03", + "svcMapMemory": "0x04", + "svcUnmapMemory": "0x05", + "svcQueryMemory": "0x06", + "svcExitProcess": "0x07", + "svcCreateThread": "0x08", + "svcStartThread": "0x09", + "svcExitThread": "0x0a", + "svcSleepThread": "0x0b", + "svcGetThreadPriority": "0x0c", + "svcSetThreadPriority": "0x0d", + "svcGetThreadCoreMask": "0x0e", + "svcSetThreadCoreMask": "0x0f", + "svcGetCurrentProcessorNumber": "0x10", + "svcSignalEvent": "0x11", + "svcClearEvent": "0x12", + "svcMapSharedMemory": "0x13", + "svcUnmapSharedMemory": "0x14", + "svcCreateTransferMemory": "0x15", + "svcCloseHandle": "0x16", + "svcResetSignal": "0x17", + "svcWaitSynchronization": "0x18", + "svcCancelSynchronization": "0x19", + "svcArbitrateLock": "0x1a", + "svcArbitrateUnlock": "0x1b", + "svcWaitProcessWideKeyAtomic": "0x1c", + "svcSignalProcessWideKey": "0x1d", + "svcGetSystemTick": "0x1e", + "svcConnectToNamedPort": "0x1f", + "svcSendSyncRequestLight": "0x20", + "svcSendSyncRequest": "0x21", + "svcSendSyncRequestWithUserBuffer": "0x22", + "svcSendAsyncRequestWithUserBuffer": "0x23", + "svcGetProcessId": "0x24", + "svcGetThreadId": "0x25", + "svcBreak": "0x26", + "svcOutputDebugString": "0x27", + "svcReturnFromException": "0x28", + "svcGetInfo": "0x29", + "svcSetThreadActivity": "0x32", + "svcWaitForAddress": "0x34", + "svcSignalToAddress": "0x35", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcCreateInterruptEvent": "0x53", + "svcReadWriteRegister": "0x4E", + "svcQueryIoMapping": "0x55", + "svcCreateDeviceAddressSpace": "0x56", + "svcAttachDeviceAddressSpace": "0x57", + "svcDetachDeviceAddressSpace": "0x58", + "svcMapDeviceAddressSpaceAligned": "0x5a", + "svcUnmapDeviceAddressSpace": "0x5c", + "svcGetSystemInfo": "0x6f", + "svcCallSecureMonitor": "0x7f" + } + }, + { + "type": "min_kernel_version", + "value": "0x0060" + } + ] +} \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/clock_manager.cpp b/Source/sys-clk-OC(deprecated)/sysmodule/src/clock_manager.cpp new file mode 100644 index 00000000..c9bfb32b --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/clock_manager.cpp @@ -0,0 +1,384 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include +#include "errors.h" +#include "clock_manager.h" +#include "file_utils.h" +#include "clocks.h" +#include "process_management.h" +#include + +ClockManager* ClockManager::instance = NULL; + +ClockManager* ClockManager::GetInstance() +{ + return instance; +} + +void ClockManager::Exit() +{ + if(instance) + { + delete instance; + } +} + +void ClockManager::Initialize() +{ + if(!instance) + { + instance = new ClockManager(); + } +} + +ClockManager::ClockManager() +{ + this->config = Config::CreateDefault(); + this->context = new SysClkContext; + this->context->applicationId = 0; + this->context->profile = SysClkProfile_Handheld; + this->context->enabled = false; + for(unsigned int i = 0; i < SysClkModule_EnumMax; i++) + { + this->context->freqs[i] = 0; + this->context->overrideFreqs[i] = 0; + } + this->context->perfConfId = 0; + this->running = false; + this->lastTempLogNs = 0; + this->lastCsvWriteNs = 0; + + this->oc = new SysClkOcExtra; + this->oc->systemCoreBoostCPU = false; + this->oc->batteryChargingDisabledOverride = false; + this->oc->realProfile = SysClkProfile_Handheld; + + this->rnxSync = new ReverseNXSync; + this->governor = new Governor(); +} + +ClockManager::~ClockManager() +{ + delete this->governor; + delete this->rnxSync; + delete this->oc; + delete this->context; + delete this->config; +} + +void ClockManager::SetRunning(bool running) +{ + this->running = running; +} + +bool ClockManager::Running() +{ + return this->running; +} + +uint32_t ClockManager::GetHz(SysClkModule module) +{ + /* Temp override setting */ + uint32_t hz = this->context->overrideFreqs[module]; + + /* Per-Game setting */ + if (!hz) + hz = this->config->GetAutoClockHz(this->context->applicationId, module, this->context->profile); + + /* Global profile */ + if (!hz) + hz = this->config->GetAutoClockHz(SYSCLK_GLOBAL_PROFILE_TID, module, this->context->profile); + + /* Return pre-set hz */ + ReverseNXMode mode; + if (!hz && (mode = this->rnxSync->GetMode())) + { + switch (module) + { + case SysClkModule_CPU: + hz = 1020'000'000; + break; + case SysClkModule_GPU: + hz = (mode == ReverseNX_Docked || + this->oc->realProfile == SysClkProfile_Docked) ? + 768'000'000 : 460'800'000; + break; + case SysClkModule_MEM: + hz = MEM_CLOCK_DOCK; + break; + default: + break; + } + } + + if (hz) + { + /* Considering realProfile frequency limit */ + hz = Clocks::GetNearestHz(module, this->oc->realProfile, hz); + } + + /* Handle CPU Auto Boost, no user-defined hz required */ + if (module == SysClkModule_CPU) + { + if (this->oc->systemCoreBoostCPU && hz < Clocks::boostCpuFreq) + return Clocks::boostCpuFreq; + if (!hz) + /* Trigger RefreshContext() and Tick(), resetting default CPU frequency */ + return 1020'000'000; + } + + return hz; +} + +void ClockManager::Tick() +{ + std::scoped_lock lock{this->contextMutex}; + + if (this->RefreshContext() && this->context->enabled) + { + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + uint32_t hz = GetHz((SysClkModule)module); + + if (module == SysClkModule_CPU) { + this->governor->SetMinHz(*Clocks::freqRange[module].first, SysClkModule_CPU); + if (Clocks::GetIsMariko() && hz > (uint32_t)1020'000'000) { + this->governor->SetMinHz(1020'000'000, SysClkModule_CPU); + } + } + + this->governor->SetMaxHz(hz, (SysClkModule)module); + + if (hz && hz != this->context->freqs[module] && !this->governor->IsHandledByGovernor((SysClkModule)module)) + { + // Skip setting CPU or GPU clocks in CpuBoostMode if CPU <= boostCPUFreq or GPU >= 76.8MHz + bool skipBoost = apmExtIsBoostMode(this->context->perfConfId) && + ((module == SysClkModule_CPU && hz <= Clocks::boostCpuFreq) || module == SysClkModule_GPU); + + if (!skipBoost) { + FileUtils::LogLine("[mgr] %s clock set : %u.%u MHz", Clocks::GetModuleName((SysClkModule)module, true), hz/1000000, hz/100000 - hz/1000000*10); + Clocks::SetHz((SysClkModule)module, hz); + uint32_t hz_now = Clocks::GetCurrentHz((SysClkModule)module); + if (hz != hz_now) + FileUtils::LogLine("[mgr] Cannot set %s clock to %u.%u MHz", Clocks::GetModuleName((SysClkModule)module, true), hz/1000000, hz/100000 - hz/1000000*10); + this->context->freqs[module] = hz_now; + } + } + } + } +} + +void ClockManager::WaitForNextTick() +{ + /* Self-check system core (#3) usage via idleticks at intervals (Not enabled at higher CPU freq or without charger) */ + uint64_t tickWaitTimeMs = this->GetConfig()->GetConfigValue(SysClkConfigValue_PollingIntervalMs); + + if (this->governor->IsHandledByGovernor(SysClkModule_CPU)) { + svcSleepThread(tickWaitTimeMs * 1000'000ULL); + return; + } + + bool boostOK = + this->GetConfig()->GetConfigValue(SysClkConfigValue_AutoCPUBoost) && + this->context->enabled && + this->oc->realProfile != SysClkProfile_Handheld && + this->context->freqs[SysClkModule_CPU] <= Clocks::boostCpuFreq; + + if (boostOK) { + uint32_t core3Util = CpuCoreUtil(3, tickWaitTimeMs * 1000'000ULL).Get(); + bool lastBoost = this->oc->systemCoreBoostCPU; + this->oc->systemCoreBoostCPU = (core3Util >= BOOST_THRESHOLD); + + if (lastBoost && !this->oc->systemCoreBoostCPU) + Clocks::SetHz(SysClkModule_CPU, GetHz(SysClkModule_CPU)); + + if (!lastBoost && this->oc->systemCoreBoostCPU) + Clocks::SetHz(SysClkModule_CPU, Clocks::boostCpuFreq); + + return; + } + + if (this->oc->systemCoreBoostCPU) { + this->oc->systemCoreBoostCPU = false; + Clocks::SetHz(SysClkModule_CPU, GetHz(SysClkModule_CPU)); + } + svcSleepThread(tickWaitTimeMs * 1000'000ULL); +} + +bool ClockManager::RefreshContext() +{ + PsmExt::ChargingHandler(this->GetInstance()); + + bool hasChanged = false; + SysClkProfile realProfile = Clocks::GetCurrentProfile(); + if (realProfile != this->oc->realProfile) + { + FileUtils::LogLine("[mgr] Profile change: %s", Clocks::GetProfileName(realProfile, true)); + this->oc->realProfile = realProfile; + // Signal that power state has been changed, reset the override + this->SetBatteryChargingDisabledOverride(false); + hasChanged = true; + } + + hasChanged = this->config->Refresh(); + if (hasChanged) { + this->rnxSync->ToggleSync(this->GetConfig()->GetConfigValue(SysClkConfigValue_SyncReverseNXMode)); + bool allowUnsafe = this->GetConfig()->GetConfigValue(SysClkConfigValue_AllowUnsafeFrequencies); + Clocks::SetAllowUnsafe(allowUnsafe); + + this->governor->SetAutoCPUBoost(this->GetConfig()->GetConfigValue(SysClkConfigValue_AutoCPUBoost)); + this->governor->SetCPUBoostHz(Clocks::GetNearestHz(SysClkModule_CPU, this->oc->realProfile, Clocks::boostCpuFreq)); + } + + bool enabled = this->GetConfig()->Enabled(); + if(enabled != this->context->enabled) + { + this->context->enabled = enabled; + FileUtils::LogLine("[mgr] " TARGET " status: %s", enabled ? "enabled" : "disabled"); + hasChanged = true; + } + + std::uint64_t applicationId = ProcessManagement::GetCurrentApplicationId(); + if (applicationId != this->context->applicationId) + { + FileUtils::LogLine("[mgr] TitleID change: %016lX", applicationId); + this->context->applicationId = applicationId; + hasChanged = true; + + /* Clear ReverseNX state */ + this->rnxSync->Reset(applicationId); + } + + SysClkOcGovernorConfig governorConfig = SysClkOcGovernorConfig_AllDisabled; + if (this->GetConfig()->GetConfigValue(SysClkConfigValue_GovernorExperimental)) { + auto governorHandheldOnly = this->GetConfig()->GetConfigValue(SysClkConfigValue_GovernorHandheldOnly); + governorConfig = SysClkOcGovernorConfig_Default; + SysClkOcGovernorConfig governorConfigTitle = this->GetConfig()->GetTitleGovernorConfig(applicationId); + if (governorConfig != governorConfigTitle) + governorConfig = governorConfigTitle; + // Set governor config to disabled if Handheld Only is true + if (governorHandheldOnly && (realProfile != SysClkProfile_Handheld)) + governorConfig = SysClkOcGovernorConfig_AllDisabled; + } + this->governor->SetConfig(governorConfig); + + /* Update PerformanceConfigurationId */ + { + uint32_t confId = 0; + Result rc = 0; + rc = apmExtGetCurrentPerformanceConfiguration(&confId); + ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); + if (this->context->perfConfId != confId) + { + this->context->perfConfId = confId; + this->governor->SetPerfConf(confId); + hasChanged = true; + } + } + + { + SysClkProfile current = this->context->profile; + SysClkProfile expected = this->rnxSync->GetProfile(this->oc->realProfile); + this->context->profile = expected; + if (current != expected) + hasChanged = true; + } + + // let ptm module handle boost clocks rather than resetting + if (hasChanged && !apmExtIsBoostMode(this->context->perfConfId)) { + Clocks::ResetToStock(); + } + + std::uint32_t hz = 0; + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + hz = Clocks::GetCurrentHz((SysClkModule)module); + + if (hz != 0 && hz != this->context->freqs[module]) + { + this->context->freqs[module] = hz; + if (!this->governor->IsHandledByGovernor((SysClkModule)module)) { + FileUtils::LogLine("[mgr] %s clock change: %u.%u MHz", Clocks::GetModuleName((SysClkModule)module, true), hz/1000000, hz/100000 - hz/1000000*10); + hasChanged = true; + } + } + + hz = this->GetConfig()->GetOverrideHz((SysClkModule)module); + if (hz != this->context->overrideFreqs[module]) + { + if(hz) + { + FileUtils::LogLine("[mgr] %s override change: %u.%u MHz", Clocks::GetModuleName((SysClkModule)module, true), hz/1000000, hz/100000 - hz/1000000*10); + } + else + { + FileUtils::LogLine("[mgr] %s override disabled", Clocks::GetModuleName((SysClkModule)module, true)); + Clocks::ResetToStock(module); + } + this->context->overrideFreqs[module] = hz; + hasChanged = true; + } + } + + // temperatures do not and should not force a refresh, hasChanged untouched + std::uint32_t millis = 0; + std::uint64_t ns = armTicksToNs(armGetSystemTick()); + std::uint64_t tempLogInterval = this->GetConfig()->GetConfigValue(SysClkConfigValue_TempLogIntervalMs) * 1000000ULL; + bool shouldLogTemp = tempLogInterval && ((ns - this->lastTempLogNs) > tempLogInterval); + for (unsigned int sensor = 0; sensor < SysClkThermalSensor_EnumMax; sensor++) + { + millis = Clocks::GetTemperatureMilli((SysClkThermalSensor)sensor); + if(shouldLogTemp) + { + FileUtils::LogLine("[mgr] %s temp: %u.%u °C", Clocks::GetThermalSensorName((SysClkThermalSensor)sensor, true), millis/1000, (millis - millis/1000*1000) / 100); + } + this->context->temps[sensor] = millis; + } + + if(shouldLogTemp) + { + this->lastTempLogNs = ns; + } + + std::uint64_t csvWriteInterval = this->GetConfig()->GetConfigValue(SysClkConfigValue_CsvWriteIntervalMs) * 1000000ULL; + + if(csvWriteInterval && ((ns - this->lastCsvWriteNs) > csvWriteInterval)) + { + FileUtils::WriteContextToCsv(this->context); + this->lastCsvWriteNs = ns; + } + + return hasChanged; +} + +void ClockManager::SetRNXRTMode(ReverseNXMode mode) { + this->rnxSync->SetRTMode(mode); +} + +SysClkContext ClockManager::GetCurrentContext() +{ + std::scoped_lock lock{this->contextMutex}; + return *this->context; +} + +Config* ClockManager::GetConfig() +{ + return this->config; +} + +bool ClockManager::GetBatteryChargingDisabledOverride() { + return this->oc->batteryChargingDisabledOverride; +} + +Result ClockManager::SetBatteryChargingDisabledOverride(bool toggle_true) { + this->oc->batteryChargingDisabledOverride = toggle_true; + return 0; +} diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/clock_manager.h b/Source/sys-clk-OC(deprecated)/sysmodule/src/clock_manager.h new file mode 100644 index 00000000..ee752a26 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/clock_manager.h @@ -0,0 +1,61 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include +#include + +#include "config.h" +#include "clocks.h" +#include + +#include "oc_extra.h" + +// Forward declaration +class ReverseNXSync; +class Governor; + +class ClockManager +{ + public: + static ClockManager* GetInstance(); + static void Initialize(); + static void Exit(); + + void SetRunning(bool running); + bool Running(); + void Tick(); + void WaitForNextTick(); + void SetRNXRTMode(ReverseNXMode mode); + SysClkContext GetCurrentContext(); + Config* GetConfig(); + bool GetBatteryChargingDisabledOverride(); + Result SetBatteryChargingDisabledOverride(bool toggle_true); + + protected: + ClockManager(); + virtual ~ClockManager(); + + bool RefreshContext(); + uint32_t GetHz(SysClkModule); + + static ClockManager *instance; + std::atomic_bool running; + LockableMutex contextMutex; + Config *config; + SysClkContext *context; + std::uint64_t lastTempLogNs; + std::uint64_t lastCsvWriteNs; + + SysClkOcExtra *oc; + ReverseNXSync *rnxSync; + Governor *governor; +}; diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/clocks.cpp b/Source/sys-clk-OC(deprecated)/sysmodule/src/clocks.cpp new file mode 100644 index 00000000..4699b99d --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/clocks.cpp @@ -0,0 +1,452 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include +#include +#include "clocks.h" +#include "errors.h" +#include "file_utils.h" + +Result Clocks::GetRange(SysClkModule module, SysClkProfile profile, uint32_t** min, uint32_t** max) +{ + switch (module) { + case SysClkModule_CPU: + case SysClkModule_GPU: + case SysClkModule_MEM: + *min = freqRange[module].min; + *max = freqRange[module].max[profile]; + break; + default: + ERROR_THROW("No such PcvModule: %u", module); + } + + if (!*min || !*max || *max < *min || size_t(*max - *min + 1) > sizeof(SysClkFrequencyTable)) + return SYSCLK_ERROR(InternalFrequencyTableError); + + return 0; +} + +void Clocks::UpdateFreqRange() { + freqRange[SysClkModule_MEM].InitDefault(SysClkModule_MEM); + if (isMariko) { + freqRange[SysClkModule_MEM].first = freqRange[SysClkModule_MEM].min = freqRange[SysClkModule_MEM].FindFreq(1600'000'000); + } + + freqRange[SysClkModule_CPU].InitDefault(SysClkModule_CPU); + uint32_t* cpu_max_freq = freqRange[SysClkModule_CPU].last; + uint32_t CPU_SAFE_MAX = isMariko ? 1963'500'000 : 1785'000'000; + uint32_t CPU_UNSAFE_MAX[SysClkProfile_EnumMax]; + for (auto &m : CPU_UNSAFE_MAX) { + m = *cpu_max_freq; + } + if (!isMariko) { + CPU_UNSAFE_MAX[SysClkProfile_Handheld] = 1785'000'000; + } + + freqRange[SysClkModule_GPU].InitDefault(SysClkModule_GPU); + uint32_t* gpu_max_freq = freqRange[SysClkModule_GPU].last; + uint32_t GPU_SAFE_MAX[SysClkProfile_EnumMax]; + if (isMariko) { + for (auto &m : GPU_SAFE_MAX) { + m = 998'400'000; + } + } else { + GPU_SAFE_MAX[SysClkProfile_Handheld] = \ + GPU_SAFE_MAX[SysClkProfile_HandheldCharging] = 460'800'000; + GPU_SAFE_MAX[SysClkProfile_HandheldChargingUSB] = 768'000'000; + GPU_SAFE_MAX[SysClkProfile_HandheldChargingOfficial] = \ + GPU_SAFE_MAX[SysClkProfile_Docked] = 921'600'000; + }; + uint32_t GPU_UNSAFE_MAX[SysClkProfile_EnumMax]; + for (auto &m : GPU_UNSAFE_MAX) { + m = *gpu_max_freq; + } + if (isMariko) { + GPU_UNSAFE_MAX[SysClkProfile_Handheld] = 998'400'000; + } else { + memcpy(GPU_UNSAFE_MAX, GPU_SAFE_MAX, sizeof(GPU_UNSAFE_MAX)); + GPU_UNSAFE_MAX[SysClkProfile_HandheldChargingOfficial] = \ + GPU_UNSAFE_MAX[SysClkProfile_Docked] = *gpu_max_freq; + } + + const bool use_unsafe = allowUnsafe; + for (int i = 0; i < int(SysClkProfile_EnumMax); i++) { + freqRange[SysClkModule_CPU].max[i] = std::min(cpu_max_freq, + freqRange[SysClkModule_CPU].FindFreq(use_unsafe ? CPU_UNSAFE_MAX[i] : CPU_SAFE_MAX, SysClkProfile(i))); + freqRange[SysClkModule_GPU].max[i] = std::min(gpu_max_freq, + freqRange[SysClkModule_GPU].FindFreq(use_unsafe ? GPU_UNSAFE_MAX[i] : GPU_SAFE_MAX[i], SysClkProfile(i))); + } +} + +Result Clocks::GetTable(SysClkModule module, SysClkProfile profile, SysClkFrequencyTable* out_table) { + uint32_t* min = NULL; + uint32_t* max = NULL; + if (Result res = GetRange(module, profile, &min, &max)) { + return res; + } + + memset(out_table, 0, sizeof(SysClkFrequencyTable)); + uint32_t* p = min; + size_t idx = 0; + while(p <= max) + out_table->freq[idx++] = *p++; + + return 0; +} + +void Clocks::SetAllowUnsafe(bool allow) { + if (allowUnsafe != allow) { + allowUnsafe = allow; + UpdateFreqRange(); + } +}; + +void Clocks::Initialize() +{ + Result rc = 0; + + u64 hardware_type = 0; + rc = splInitialize(); + ASSERT_RESULT_OK(rc, "splInitialize"); + rc = splGetConfig(SplConfigItem_HardwareType, &hardware_type); + ASSERT_RESULT_OK(rc, "splGetConfig"); + splExit(); + + switch (hardware_type) { + case 0: // Icosa + case 1: // Copper + isMariko = false; + break; + case 2: // Hoag + case 3: // Iowa + case 4: // Calcio + case 5: // Aula + isMariko = true; + break; + default: + ERROR_THROW("Unknown hardware type: 0x%X!", hardware_type); + return; + } + + if(hosversionAtLeast(8,0,0)) + { + rc = clkrstInitialize(); + ASSERT_RESULT_OK(rc, "pcvInitialize"); + } + else + { + rc = pcvInitialize(); + ASSERT_RESULT_OK(rc, "pcvInitialize"); + } + + rc = apmExtInitialize(); + ASSERT_RESULT_OK(rc, "apmExtInitialize"); + + rc = psmInitialize(); + ASSERT_RESULT_OK(rc, "psmInitialize"); + + rc = tsInitialize(); + ASSERT_RESULT_OK(rc, "tsInitialize"); + + if(hosversionAtLeast(5,0,0)) + { + rc = tcInitialize(); + ASSERT_RESULT_OK(rc, "tcInitialize"); + } + + FileUtils::ParseLoaderKip(); + + UpdateFreqRange(); +} + +void Clocks::Exit() +{ + if(hosversionAtLeast(8,0,0)) + { + pcvExit(); + } + else + { + clkrstExit(); + } + + apmExtExit(); + psmExit(); + tsExit(); + + if(hosversionAtLeast(5,0,0)) + { + tcExit(); + } +} + +const char* Clocks::GetModuleName(SysClkModule module, bool pretty) +{ + const char* result = sysclkFormatModule(module, pretty); + + if(!result) + { + ERROR_THROW("No such SysClkModule: %u", module); + } + + return result; +} + +const char* Clocks::GetProfileName(SysClkProfile profile, bool pretty) +{ + const char* result = sysclkFormatProfile(profile, pretty); + + if(!result) + { + ERROR_THROW("No such SysClkProfile: %u", profile); + } + + return result; +} + +const char* Clocks::GetThermalSensorName(SysClkThermalSensor sensor, bool pretty) +{ + const char* result = sysclkFormatThermalSensor(sensor, pretty); + + if(!result) + { + ERROR_THROW("No such SysClkThermalSensor: %u", sensor); + } + + return result; +} + +PcvModule Clocks::GetPcvModule(SysClkModule sysclkModule) +{ + switch(sysclkModule) + { + case SysClkModule_CPU: + return PcvModule_CpuBus; + case SysClkModule_GPU: + return PcvModule_GPU; + case SysClkModule_MEM: + return PcvModule_EMC; + default: + ERROR_THROW("No such SysClkModule: %u", sysclkModule); + } + + return (PcvModule)0; +} + +PcvModuleId Clocks::GetPcvModuleId(SysClkModule sysclkModule) +{ + PcvModuleId pcvModuleId; + Result rc = pcvGetModuleId(&pcvModuleId, GetPcvModule(sysclkModule)); + ASSERT_RESULT_OK(rc, "pcvGetModuleId"); + + return pcvModuleId; +} + +SysClkApmConfiguration* Clocks::GetEmbeddedApmConfig(uint32_t confId) +{ + SysClkApmConfiguration* apmConfiguration = NULL; + for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) + { + if(sysclk_g_apm_configurations[i].id == confId) + { + apmConfiguration = &sysclk_g_apm_configurations[i]; + break; + } + } + + if(!apmConfiguration) + { + ERROR_THROW("Unknown apm configuration: %x", confId); + } + return apmConfiguration; +} + +uint32_t Clocks::GetStockClock(SysClkApmConfiguration* apm, SysClkModule module) +{ + switch (module) { + case SysClkModule_CPU: + return apm->cpu_hz; + case SysClkModule_GPU: + return apm->gpu_hz; + case SysClkModule_MEM: + return GetIsMariko() ? MEM_CLOCK_MARIKO_MIN : apm->mem_hz; + default: + ERROR_THROW("Unknown SysClkModule: %x", module); + return 0; + } +} + +void Clocks::ResetToStock(unsigned int module) +{ + if(hosversionAtLeast(9,0,0)) + { + std::uint32_t confId = 0; + Result rc = apmExtGetCurrentPerformanceConfiguration(&confId); + ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); + + SysClkApmConfiguration* apmConfiguration = GetEmbeddedApmConfig(confId); + + if (module == SysClkModule_EnumMax || module == SysClkModule_CPU) + { + Clocks::SetHz(SysClkModule_CPU, GetStockClock(apmConfiguration, SysClkModule_CPU)); + } + if (module == SysClkModule_EnumMax || module == SysClkModule_GPU) + { + Clocks::SetHz(SysClkModule_GPU, GetStockClock(apmConfiguration, SysClkModule_GPU)); + } + if (module == SysClkModule_EnumMax || module == SysClkModule_MEM) + { + Clocks::SetHz(SysClkModule_MEM, GetStockClock(apmConfiguration, SysClkModule_MEM)); + } + } + else + { + Result rc = 0; + std::uint32_t mode = 0; + rc = apmExtGetPerformanceMode(&mode); + ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); + + rc = apmExtSysRequestPerformanceMode(mode); + ASSERT_RESULT_OK(rc, "apmExtSysRequestPerformanceMode"); + } +} + +SysClkProfile Clocks::GetCurrentProfile() +{ + std::uint32_t mode = 0; + Result rc = apmExtGetPerformanceMode(&mode); + ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); + + if(mode) + { + return SysClkProfile_Docked; + } + + PsmChargerType chargerType; + + rc = psmGetChargerType(&chargerType); + ASSERT_RESULT_OK(rc, "psmGetChargerType"); + + switch(chargerType) + { + case PsmChargerType_EnoughPower: + return SysClkProfile_HandheldChargingOfficial; + case PsmChargerType_LowPower: + case PsmChargerType_NotSupported: + return SysClkProfile_HandheldChargingUSB; + default: + return SysClkProfile_Handheld; + } +} + +void Clocks::SetHz(SysClkModule module, std::uint32_t hz) +{ + Result rc = 0; + + if(hosversionAtLeast(8,0,0)) + { + ClkrstSession session = {0}; + + rc = clkrstOpenSession(&session, Clocks::GetPcvModuleId(module), 3); + ASSERT_RESULT_OK(rc, "clkrstOpenSession"); + rc = clkrstSetClockRate(&session, hz); + ASSERT_RESULT_OK(rc, "clkrstSetClockRate"); + + clkrstCloseSession(&session); + } + else + { + rc = pcvSetClockRate(Clocks::GetPcvModule(module), hz); + ASSERT_RESULT_OK(rc, "pcvSetClockRate"); + } +} + +std::uint32_t Clocks::GetCurrentHz(SysClkModule module) +{ + Result rc = 0; + std::uint32_t hz = 0; + + if(hosversionAtLeast(8,0,0)) + { + ClkrstSession session = {0}; + + rc = clkrstOpenSession(&session, Clocks::GetPcvModuleId(module), 3); + ASSERT_RESULT_OK(rc, "clkrstOpenSession"); + + rc = clkrstGetClockRate(&session, &hz); + ASSERT_RESULT_OK(rc, "clkrstGetClockRate"); + + clkrstCloseSession(&session); + } + else + { + rc = pcvGetClockRate(Clocks::GetPcvModule(module), &hz); + ASSERT_RESULT_OK(rc, "pcvGetClockRate"); + } + + return hz; +} + +std::uint32_t Clocks::GetNearestHz(SysClkModule module, SysClkProfile profile, std::uint32_t inHz) +{ + uint32_t *min = nullptr, *max = nullptr; + if (GetRange(module, profile, &min, &max)) + ERROR_THROW("table lookup failed for SysClkModule: %u", module); + + return *GetNearestHzPtr(min, max, inHz); +} + +std::int32_t Clocks::GetTsTemperatureMilli(TsLocation location) +{ + Result rc; + std::int32_t millis = 0; + + if(hosversionAtLeast(14,0,0)) + { + rc = tsGetTemperature(location, &millis); + ASSERT_RESULT_OK(rc, "tsGetTemperature(%u)", location); + millis *= 1000; + } + else + { + rc = tsGetTemperatureMilliC(location, &millis); + ASSERT_RESULT_OK(rc, "tsGetTemperatureMilliC(%u)", location); + } + + return millis; +} + +std::uint32_t Clocks::GetTemperatureMilli(SysClkThermalSensor sensor) +{ + std::int32_t millis = 0; + + if(sensor == SysClkThermalSensor_SOC) + { + millis = GetTsTemperatureMilli(TsLocation_External); + } + else if(sensor == SysClkThermalSensor_PCB) + { + millis = GetTsTemperatureMilli(TsLocation_Internal); + } + else if(sensor == SysClkThermalSensor_Skin) + { + if(hosversionAtLeast(5,0,0)) + { + Result rc = tcGetSkinTemperatureMilliC(&millis); + ASSERT_RESULT_OK(rc, "tcGetSkinTemperatureMilliC"); + } + } + else + { + ERROR_THROW("No such SysClkThermalSensor: %u", sensor); + } + + return std::max(0, millis); +} \ No newline at end of file diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/clocks.h b/Source/sys-clk-OC(deprecated)/sysmodule/src/clocks.h new file mode 100644 index 00000000..38bd9771 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/clocks.h @@ -0,0 +1,106 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once +#include +#include +#include + +#define MAX_MEM_CLOCK 1862'400'000 +#define MEM_CLOCK_MARIKO_MIN 1600'000'000 +#define MEM_CLOCK_DOCK 1600'000'000 +#define BOOST_THRESHOLD 95'0 + +class Clocks +{ +public: + static inline uint32_t boostCpuFreq = 1785000000; + static inline uint32_t maxMemFreq = 0; + + static inline uint32_t* GetNearestHzPtr(uint32_t* hzListStart, uint32_t* hzListEnd, uint32_t freq) { + uint32_t* p = hzListStart; + while (p < hzListEnd) { + if (freq <= *p) + return p; + p++; + } + return hzListEnd; + } + + static inline SysClkFrequencyTable freqTable[SysClkModule_EnumMax] = { + {}, // CPU + {}, // GPU + { // MEM + 665600000, + 800000000, + 1065600000, + 1331200000, + 1600000000, + }, + }; + + typedef struct FreqRange { + uint32_t* first; + uint32_t* last; + uint32_t* min; + uint32_t* max[SysClkProfile_EnumMax]; + + void InitDefault(SysClkModule module) { + SysClkFrequencyTable* table_head = &freqTable[module]; + uint32_t* p = this->first = this->min = &table_head->freq[0]; + + // Get pointer to last value + for (int i = 0; i < FREQ_TABLE_MAX_ENTRY_COUNT; i++) { + if (!*(++p)) { + --p; + break; + } + } + + this->last = p; + for (auto& m: this->max) { + m = p; + } + }; + + uint32_t* FindFreq(uint32_t freq, SysClkProfile profile = SysClkProfile_Docked) { + return GetNearestHzPtr(this->min, this->max[profile], freq); + }; + } FreqRange; + + static inline FreqRange freqRange[SysClkModule_EnumMax]; + static void UpdateFreqRange(); + + static Result GetRange(SysClkModule module, SysClkProfile profile, uint32_t** min, uint32_t** max); + static Result GetTable(SysClkModule module, SysClkProfile profile, SysClkFrequencyTable* out_table); + static void SetAllowUnsafe(bool allow); + static bool GetIsMariko() { return isMariko; }; + static void Exit(); + static void Initialize(); + static SysClkApmConfiguration* GetEmbeddedApmConfig(uint32_t confId); + static uint32_t GetStockClock(SysClkApmConfiguration* apm, SysClkModule module); + static void ResetToStock(unsigned int module = SysClkModule_EnumMax); + static SysClkProfile GetCurrentProfile(); + static std::uint32_t GetCurrentHz(SysClkModule module); + static void SetHz(SysClkModule module, std::uint32_t hz); + static const char* GetProfileName(SysClkProfile profile, bool pretty); + static const char* GetModuleName(SysClkModule module, bool pretty); + static const char* GetThermalSensorName(SysClkThermalSensor sensor, bool pretty); + static std::uint32_t GetNearestHz(SysClkModule module, SysClkProfile profile, std::uint32_t inHz); + static std::uint32_t GetTemperatureMilli(SysClkThermalSensor sensor); + +protected: + static inline bool allowUnsafe; + static inline bool isMariko; + static std::int32_t GetTsTemperatureMilli(TsLocation location); + static PcvModule GetPcvModule(SysClkModule sysclkModule); + static PcvModuleId GetPcvModuleId(SysClkModule sysclkModule); + static std::uint32_t GetMaxAllowedHz(SysClkModule module, SysClkProfile profile); +}; diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/config.cpp b/Source/sys-clk-OC(deprecated)/sysmodule/src/config.cpp new file mode 100644 index 00000000..966b06f0 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/config.cpp @@ -0,0 +1,538 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "clocks.h" +#include "file_utils.h" + +Config::Config(std::string path) +{ + this->path = path; + this->loaded = false; + this->profileMhzMap = std::map, std::uint32_t>(); + this->profileCountMap = std::map(); + this->profileGovernorMap = std::map(); + this->mtime = 0; + this->enabled = false; + for(unsigned int i = 0; i < SysClkModule_EnumMax; i++) + { + this->overrideFreqs[i] = 0; + } + + for(unsigned int i = 0; i < SysClkConfigValue_EnumMax; i++) + { + this->configValues[i] = sysclkDefaultConfigValue((SysClkConfigValue)i); + } +} + +Config::~Config() +{ + std::scoped_lock lock{this->configMutex}; + this->Close(); +} + +Config *Config::CreateDefault() +{ + //if (R_FAILED(FileUtils::mkdir_p(FILE_CONFIG_DIR))) + // ERROR_THROW("Cannot create " FILE_CONFIG_DIR); + + return new Config(FILE_CONFIG_DIR "/config.ini"); +} + +void Config::Load() +{ + FileUtils::LogLine("[cfg] Reading %s", this->path.c_str()); + + this->Close(); + this->mtime = this->CheckModificationTime(); + if(!this->mtime) + { + FileUtils::LogLine("[cfg] Error finding file"); + } + else if (!ini_browse(&BrowseIniFunc, this, this->path.c_str())) + { + FileUtils::LogLine("[cfg] Error loading file"); + } + + // Erista: Disable Mariko only features + // if (!Clocks::GetIsMariko()) { } + + this->loaded = true; +} + +void Config::Close() +{ + this->loaded = false; + this->profileMhzMap.clear(); + this->profileCountMap.clear(); + this->profileGovernorMap.clear(); + + for(unsigned int i = 0; i < SysClkConfigValue_EnumMax; i++) + { + this->configValues[i] = sysclkDefaultConfigValue((SysClkConfigValue)i); + } +} + +bool Config::Refresh() +{ + std::scoped_lock lock{this->configMutex}; + if (!this->loaded || this->mtime != this->CheckModificationTime()) + { + this->Load(); + return true; + } + return false; +} + +bool Config::HasProfilesLoaded() +{ + std::scoped_lock lock{this->configMutex}; + return this->loaded; +} + +time_t Config::CheckModificationTime() +{ + time_t mtime = 0; + struct stat st; + if (stat(this->path.c_str(), &st) == 0) + { + mtime = st.st_mtime; + } + + return mtime; +} + +std::uint32_t Config::FindClockMhz(std::uint64_t tid, SysClkModule module, SysClkProfile profile) +{ + if (this->loaded) + { + std::map, std::uint32_t>::const_iterator it = this->profileMhzMap.find(std::make_tuple(tid, profile, module)); + if (it != this->profileMhzMap.end()) + { + return it->second; + } + } + + return 0; +} + +std::uint32_t Config::FindClockHzFromProfiles(std::uint64_t tid, SysClkModule module, std::initializer_list profiles) +{ + std::uint32_t mhz = 0; + + if (this->loaded) + { + for(auto profile: profiles) + { + mhz = FindClockMhz(tid, module, profile); + + if(mhz) + { + break; + } + } + } + + return std::max((std::uint32_t)0, mhz * 1000000); +} + +std::uint32_t Config::GetAutoClockHz(std::uint64_t tid, SysClkModule module, SysClkProfile profile) +{ + std::scoped_lock lock{this->configMutex}; + switch(profile) + { + case SysClkProfile_Handheld: + return FindClockHzFromProfiles(tid, module, {SysClkProfile_Handheld}); + case SysClkProfile_HandheldCharging: + case SysClkProfile_HandheldChargingUSB: + return FindClockHzFromProfiles(tid, module, {SysClkProfile_HandheldChargingUSB, SysClkProfile_HandheldCharging, SysClkProfile_Handheld}); + case SysClkProfile_HandheldChargingOfficial: + return FindClockHzFromProfiles(tid, module, {SysClkProfile_HandheldChargingOfficial, SysClkProfile_HandheldCharging, SysClkProfile_Handheld}); + case SysClkProfile_Docked: + return FindClockHzFromProfiles(tid, module, {SysClkProfile_Docked}); + default: + ERROR_THROW("Unhandled SysClkProfile: %u", profile); + } + + return 0; +} + +SysClkOcGovernorConfig Config::GetTitleGovernorConfig(std::uint64_t tid) +{ + if (this->loaded) + { + std::map::const_iterator it = this->profileGovernorMap.find(tid); + if (it != this->profileGovernorMap.end()) + { + return it->second; + } + } + + return SysClkOcGovernorConfig_Default; +} + +void Config::GetProfiles(std::uint64_t tid, SysClkTitleProfileList* out_profiles) +{ + std::scoped_lock lock{this->configMutex}; + + for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++) + { + for(unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + out_profiles->mhzMap[profile][module] = FindClockMhz(tid, (SysClkModule)module, (SysClkProfile)profile); + } + } + + std::map::const_iterator it = this->profileGovernorMap.find(tid); + SysClkOcGovernorConfig governor = SysClkOcGovernorConfig_Default; + // Found + if (it != this->profileGovernorMap.end()) + governor = it->second; + out_profiles->governorConfig = governor; +} + +bool Config::SetProfiles(std::uint64_t tid, SysClkTitleProfileList* profiles, bool immediate) +{ + std::scoped_lock lock{this->configMutex}; + uint8_t numProfiles = 0; + + // String pointer array passed to ini + char* iniKeys[static_cast(SysClkProfile_EnumMax) * static_cast(SysClkModule_EnumMax) + 1 + 1]; + char* iniValues[static_cast(SysClkProfile_EnumMax) * static_cast(SysClkModule_EnumMax) + 1 + 1]; + + // Char arrays to build strings + char keysStr[static_cast(SysClkProfile_EnumMax) * static_cast(SysClkModule_EnumMax) * 0x40]; + char valuesStr[static_cast(SysClkProfile_EnumMax) * static_cast(SysClkModule_EnumMax) * 0x10]; + char section[17] = {0}; + + // Iteration pointers + char** ik = &iniKeys[0]; + char** iv = &iniValues[0]; + char* sk = &keysStr[0]; + char* sv = &valuesStr[0]; + std::uint32_t* mhz = &profiles->mhz[0]; + + snprintf(section, sizeof(section), "%016lX", tid); + + for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++) + { + for(unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + if(*mhz) + { + numProfiles++; + + // Put key and value as string + snprintf(sk, 0x40, "%s_%s", Clocks::GetProfileName((SysClkProfile)profile, false), Clocks::GetModuleName((SysClkModule)module, false)); + snprintf(sv, 0x10, "%d", *mhz); + + // Add them to the ini key/value str arrays + *ik = sk; + *iv = sv; + ik++; + iv++; + + // We used those chars, get to the next ones + sk += 0x40; + sv += 0x10; + } + + mhz++; + } + } + + if (profiles->governorConfig != SysClkOcGovernorConfig_Default) { + snprintf(sk, 0x40, "%s", CONFIG_KEY_TITLE_GOVERNOR_CONFIG); + snprintf(sv, 0x10, "%d", profiles->governorConfig); + *ik++ = sk; + *iv++ = sv; + } + + *ik = NULL; + *iv = NULL; + + if(!ini_putsection(section, (const char**)iniKeys, (const char**)iniValues, this->path.c_str())) + { + return false; + } + + // Only actually apply changes in memory after a succesful save + if(immediate) + { + mhz = &profiles->mhz[0]; + this->profileCountMap[tid] = numProfiles; + for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++) + { + for(unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + if(*mhz) + { + this->profileMhzMap[std::make_tuple(tid, (SysClkProfile)profile, (SysClkModule)module)] = *mhz; + } + else + { + this->profileMhzMap.erase(std::make_tuple(tid, (SysClkProfile)profile, (SysClkModule)module)); + } + mhz++; + } + } + + if (profiles->governorConfig == SysClkOcGovernorConfig_Default) + this->profileGovernorMap.erase(tid); + else + this->profileGovernorMap[tid] = profiles->governorConfig; + } + + return true; +} + +std::uint8_t Config::GetProfileCount(std::uint64_t tid) +{ + std::map::iterator it = this->profileCountMap.find(tid); + if (it == this->profileCountMap.end()) + { + return 0; + } + + return it->second; +} + +int Config::BrowseIniFunc(const char* section, const char* key, const char* value, void *userdata) +{ + Config* config = (Config*)userdata; + std::uint64_t input; + if(!strcmp(section, CONFIG_VAL_SECTION)) + { + for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++) + { + if(!strcmp(key, sysclkFormatConfigValue((SysClkConfigValue)kval, false))) + { + input = strtoul(value, NULL, 0); + if(!sysclkValidConfigValue((SysClkConfigValue)kval, input)) + { + input = sysclkDefaultConfigValue((SysClkConfigValue)kval); + FileUtils::LogLine("[cfg] Invalid value for key '%s' in section '%s': using default %d", key, section, input); + } + config->configValues[kval] = input; + return 1; + } + } + + FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Unrecognized config value", key, section); + return 1; + } + + std::uint64_t tid = strtoul(section, NULL, 16); + + if(!tid || strlen(section) != 16) + { + FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Invalid TitleID", key, section); + return 1; + } + + if (!strcmp(key, CONFIG_KEY_TITLE_GOVERNOR_CONFIG)) { + input = strtoul(value, NULL, 0); + if ((input & SysClkOcGovernorConfig_Mask) != input) { + input = SysClkOcGovernorConfig_Default; + FileUtils::LogLine("[cfg] Invalid value for key '%s' in section '%s': using default %d", key, section, input); + } + config->profileGovernorMap[tid] = (SysClkOcGovernorConfig)input; + return 1; + } + + SysClkProfile parsedProfile = SysClkProfile_EnumMax; + SysClkModule parsedModule = SysClkModule_EnumMax; + + for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++) + { + const char* profileCode = Clocks::GetProfileName((SysClkProfile)profile, false); + size_t profileCodeLen = strlen(profileCode); + + if(!strncmp(key, profileCode, profileCodeLen) && key[profileCodeLen] == '_') + { + const char* subkey = key + profileCodeLen + 1; + + for(unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + const char* moduleCode = Clocks::GetModuleName((SysClkModule)module, false); + size_t moduleCodeLen = strlen(moduleCode); + if(!strncmp(subkey, moduleCode, moduleCodeLen) && subkey[moduleCodeLen] == '\0') + { + parsedProfile = (SysClkProfile)profile; + parsedModule = (SysClkModule)module; + } + } + } + } + + if(parsedModule == SysClkModule_EnumMax || parsedProfile == SysClkProfile_EnumMax) + { + FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Unrecognized key", key, section); + return 1; + } + + std::uint32_t mhz = strtoul(value, NULL, 10); + if(!mhz) + { + FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Invalid value", key, section); + return 1; + } + + // Mem freq > 1600'000'000 will be regarded as Clocks::maxMemFreq for consistency + if (parsedModule == SysClkModule_MEM && mhz > 1600) { + mhz = Clocks::maxMemFreq / 1000'000; + } + + config->profileMhzMap[std::make_tuple(tid, parsedProfile, parsedModule)] = mhz; + std::map::iterator it = config->profileCountMap.find(tid); + if (it == config->profileCountMap.end()) + { + config->profileCountMap[tid] = 1; + } + else + { + it->second++; + } + + return 1; +} + +void Config::SetEnabled(bool enabled) +{ + this->enabled = enabled; +} + +bool Config::Enabled() +{ + return this->enabled; +} + +void Config::SetOverrideHz(SysClkModule module, std::uint32_t hz) +{ + std::scoped_lock lock{this->overrideMutex}; + if(!SYSCLK_ENUM_VALID(SysClkModule, module)) + { + ERROR_THROW("Unhandled SysClkModule: %u", module); + } + this->overrideFreqs[module] = hz; +} + +std::uint32_t Config::GetOverrideHz(SysClkModule module) +{ + std::scoped_lock lock{this->overrideMutex}; + if(!SYSCLK_ENUM_VALID(SysClkModule, module)) + { + ERROR_THROW("Unhandled SysClkModule: %u", module); + } + + return this->overrideFreqs[module]; +} + +std::uint64_t Config::GetConfigValue(SysClkConfigValue kval) +{ + std::scoped_lock lock{this->configMutex}; + if(!SYSCLK_ENUM_VALID(SysClkConfigValue, kval)) + { + ERROR_THROW("Unhandled SysClkConfigValue: %u", kval); + } + + return this->configValues[kval]; +} + +const char* Config::GetConfigValueName(SysClkConfigValue kval, bool pretty) +{ + const char* result = sysclkFormatConfigValue(kval, pretty); + + if(!result) + { + ERROR_THROW("No such SysClkConfigValue: %u", kval); + } + + return result; +} + +void Config::GetConfigValues(SysClkConfigValueList* out_configValues) +{ + std::scoped_lock lock{this->configMutex}; + + for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++) + { + out_configValues->values[kval] = this->configValues[kval]; + } +} + +bool Config::SetConfigValues(SysClkConfigValueList* configValues, bool immediate) +{ + std::scoped_lock lock{this->configMutex}; + + // String pointer array passed to ini + const char* iniKeys[SysClkConfigValue_EnumMax + 1]; + char* iniValues[SysClkConfigValue_EnumMax + 1]; + + // char arrays to build strings + char valuesStr[SysClkConfigValue_EnumMax * 0x20]; + + // Iteration pointers + char* sv = &valuesStr[0]; + const char** ik = &iniKeys[0]; + char** iv = &iniValues[0]; + + for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++) + { + if(!sysclkValidConfigValue((SysClkConfigValue)kval, configValues->values[kval]) || configValues->values[kval] == sysclkDefaultConfigValue((SysClkConfigValue)kval)) + { + continue; + } + + // Put key and value as string + // And add them to the ini key/value str arrays + snprintf(sv, 0x20, "%ld", configValues->values[kval]); + *ik = sysclkFormatConfigValue((SysClkConfigValue)kval, false); + *iv = sv; + + // We used those chars, get to the next ones + sv += 0x20; + ik++; + iv++; + } + + *ik = NULL; + *iv = NULL; + + if(!ini_putsection(CONFIG_VAL_SECTION, (const char**)iniKeys, (const char**)iniValues, this->path.c_str())) + { + return false; + } + + // Only actually apply changes in memory after a succesful save + if(immediate) + { + for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++) + { + if(sysclkValidConfigValue((SysClkConfigValue)kval, configValues->values[kval])) + { + this->configValues[kval] = configValues->values[kval]; + } + else + { + this->configValues[kval] = sysclkDefaultConfigValue((SysClkConfigValue)kval); + } + } + } + + return true; +} diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/config.h b/Source/sys-clk-OC(deprecated)/sysmodule/src/config.h new file mode 100644 index 00000000..acc7a150 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/config.h @@ -0,0 +1,73 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include "clocks.h" + +#define CONFIG_VAL_SECTION "values" + +#define CONFIG_KEY_TITLE_GOVERNOR_CONFIG "governor_config" + +class Config +{ + public: + Config(std::string path); + virtual ~Config(); + + static Config *CreateDefault(); + + bool Refresh(); + + bool HasProfilesLoaded(); + + std::uint8_t GetProfileCount(std::uint64_t tid); + void GetProfiles(std::uint64_t tid, SysClkTitleProfileList* out_profiles); + bool SetProfiles(std::uint64_t tid, SysClkTitleProfileList* profiles, bool immediate); + std::uint32_t GetAutoClockHz(std::uint64_t tid, SysClkModule module, SysClkProfile profile); + SysClkOcGovernorConfig GetTitleGovernorConfig(std::uint64_t tid); + + void SetEnabled(bool enabled); + bool Enabled(); + void SetOverrideHz(SysClkModule module, std::uint32_t hz); + std::uint32_t GetOverrideHz(SysClkModule module); + + std::uint64_t GetConfigValue(SysClkConfigValue val); + const char* GetConfigValueName(SysClkConfigValue val, bool pretty); + void GetConfigValues(SysClkConfigValueList* out_configValues); + bool SetConfigValues(SysClkConfigValueList* configValues, bool immediate); + protected: + void Load(); + void Close(); + + time_t CheckModificationTime(); + std::uint32_t FindClockMhz(std::uint64_t tid, SysClkModule module, SysClkProfile profile); + std::uint32_t FindClockHzFromProfiles(std::uint64_t tid, SysClkModule module, std::initializer_list profiles); + static int BrowseIniFunc(const char* section, const char* key, const char* value, void *userdata); + + std::map, std::uint32_t> profileMhzMap; + std::map profileCountMap; + std::map profileGovernorMap; + bool loaded; + std::string path; + time_t mtime; + LockableMutex configMutex; + LockableMutex overrideMutex; + std::atomic_bool enabled; + std::uint32_t overrideFreqs[SysClkModule_EnumMax]; + std::uint64_t configValues[SysClkConfigValue_EnumMax]; +}; diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/errors.cpp b/Source/sys-clk-OC(deprecated)/sysmodule/src/errors.cpp new file mode 100644 index 00000000..706aaf7d --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/errors.cpp @@ -0,0 +1,37 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "errors.h" +#include +#include + +void Errors::ThrowException(const char *format, ...) +{ + va_list args; + va_start(args, format); + const char *msg = Errors::FormatMessage(format, args); + va_end(args); + + throw std::runtime_error(msg); +} + +const char *Errors::FormatMessage(const char *format, va_list args) +{ + size_t len = vsnprintf(NULL, 0, format, args) * sizeof(char); + char *buf = (char *)malloc(len + 1); + if (buf == NULL) + { + return format; + } + + vsnprintf(buf, len + 1, format, args); + + return buf; +} diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/errors.h b/Source/sys-clk-OC(deprecated)/sysmodule/src/errors.h new file mode 100644 index 00000000..e9631124 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/errors.h @@ -0,0 +1,36 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include +#include + +#ifdef DEBUG +#define ERROR_THROW(format, ...) Errors::ThrowException(format "\n in %s:%u", ##__VA_ARGS__, __FILE__, __LINE__) +#else +#define ERROR_THROW(format, ...) Errors::ThrowException(format, ##__VA_ARGS__) +#endif + +#define ERROR_RESULT_THROW(rc, format, ...) ERROR_THROW(format "\n RC: [0x%x] %04d-%04d", ##__VA_ARGS__, rc, R_MODULE(rc), R_DESCRIPTION(rc)) +#define ASSERT_RESULT_OK(rc, format, ...) \ + if (R_FAILED(rc)) \ + { \ + ERROR_RESULT_THROW(rc, "ASSERT_RESULT_OK: " format, ##__VA_ARGS__); \ + } + +class Errors +{ + public: + static void ThrowException(const char *format, ...); + + protected: + static const char *FormatMessage(const char *format, va_list args); +}; diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/file_utils.cpp b/Source/sys-clk-OC(deprecated)/sysmodule/src/file_utils.cpp new file mode 100644 index 00000000..9cadebb6 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/file_utils.cpp @@ -0,0 +1,404 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "file_utils.h" +#include "clocks.h" +#include +#include +#include "errors.h" + +static LockableMutex g_log_mutex; +static LockableMutex g_csv_mutex; +static std::atomic_bool g_has_initialized = false; +static bool g_log_enabled = false; +static std::uint64_t g_last_flag_check = 0; + +extern "C" void __libnx_init_time(void); + +static void _FileUtils_InitializeThreadFunc(void *args) +{ + FileUtils::Initialize(); +} + +bool FileUtils::IsInitialized() +{ + return g_has_initialized; +} + +void FileUtils::LogLine(const char *format, ...) +{ + va_list args; + va_start(args, format); + if (g_has_initialized) + { + g_log_mutex.Lock(); + + FileUtils::RefreshFlags(false); + + if(g_log_enabled) + { + FILE *file = fopen(FILE_LOG_FILE_PATH, "a"); + + if (file) + { + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + struct tm* nowTm = localtime(&now.tv_sec); + + fprintf(file, "[%04d-%02d-%02d %02d:%02d:%02d.%03ld] ", nowTm->tm_year+1900, nowTm->tm_mon+1, nowTm->tm_mday, nowTm->tm_hour, nowTm->tm_min, nowTm->tm_sec, now.tv_nsec / 1000000UL); + vfprintf(file, format, args); + fprintf(file, "\n"); + fclose(file); + } + } + + g_log_mutex.Unlock(); + } + va_end(args); +} + +void FileUtils::WriteContextToCsv(const SysClkContext* context) +{ + std::scoped_lock lock{g_csv_mutex}; + + FILE *file = fopen(FILE_CONTEXT_CSV_PATH, "a"); + + if (file) + { + // Print header + if(!ftell(file)) + { + fprintf(file, "timestamp,profile,app_tid"); + + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + fprintf(file, ",%s_hz", sysclkFormatModule((SysClkModule)module, false)); + } + + for (unsigned int sensor = 0; sensor < SysClkThermalSensor_EnumMax; sensor++) + { + fprintf(file, ",%s_milliC", sysclkFormatThermalSensor((SysClkThermalSensor)sensor, false)); + } + + fprintf(file, "\n"); + } + + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + + fprintf(file, "%ld%03ld,%s,%016lx", now.tv_sec, now.tv_nsec / 1000000UL, sysclkFormatProfile(context->profile, false), context->applicationId); + + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + fprintf(file, ",%d", context->freqs[module]); + } + + for (unsigned int sensor = 0; sensor < SysClkThermalSensor_EnumMax; sensor++) + { + fprintf(file, ",%d", context->temps[sensor]); + } + + fprintf(file, "\n"); + fclose(file); + } +} + +void FileUtils::RefreshFlags(bool force) +{ + std::uint64_t now = armTicksToNs(armGetSystemTick()); + if(!force && (now - g_last_flag_check) < FILE_FLAG_CHECK_INTERVAL_NS) + { + return; + } + + FILE *file = fopen(FILE_LOG_FLAG_PATH, "r"); + if (file) + { + g_log_enabled = true; + fclose(file); + } else { + g_log_enabled = false; + } + + g_last_flag_check = now; +} + +void FileUtils::InitializeAsync() +{ + Thread initThread = {0}; + threadCreate(&initThread, _FileUtils_InitializeThreadFunc, NULL, NULL, 0x2000, 0x15, 0); + threadStart(&initThread); +} + +Result FileUtils::Initialize() +{ + Result rc = 0; + + if (R_SUCCEEDED(rc)) + { + rc = timeInitialize(); + } + + __libnx_init_time(); + timeExit(); + + if (R_SUCCEEDED(rc)) + { + rc = fsInitialize(); + } + + if (R_SUCCEEDED(rc)) + { + rc = fsdevMountSdmc(); + } + + if (R_SUCCEEDED(rc)) + { + FileUtils::RefreshFlags(true); + g_has_initialized = true; + FileUtils::LogLine("=== " TARGET " " TARGET_VERSION " ==="); + } + + return rc; +} + +void FileUtils::Exit() +{ + if (!g_has_initialized) + { + return; + } + + g_has_initialized = false; + g_log_enabled = false; + + fsdevUnmountAll(); + fsExit(); +} + +void FileUtils::ParseLoaderKip() { + const char* dirs[] = { "/", "/atmosphere/", "/atmosphere/kips/", "/bootloader/" }; + char* full_path = new char[0x200]; + SCOPE_EXIT { delete[] full_path; }; + + for (auto const& dir : dirs) { + struct dirent *entry = NULL; + DIR *dp = opendir(dir); + if (!dp) + continue; + SCOPE_EXIT { closedir(dp); }; + + while ((entry = readdir(dp))) { + if (entry->d_type != DT_REG) + continue; + + snprintf(full_path, 0x200, "%s%s", dir, entry->d_name); + + FILE* fp = fopen(full_path, "r"); + if (!fp) + continue; + + fseek(fp, 0, SEEK_END); + long res = ftell(fp); + fclose(fp); + if (res == -1) + continue; + + size_t filesize = (size_t)res; + if (filesize < 0x1000 || filesize > 512 * 1024) + continue; + + const char kip_ext[] = {'.', 'k', 'i', 'p'}; + size_t file_name_len = strnlen(reinterpret_cast(&entry->d_name), 256); + const char* file_ext = &entry->d_name[file_name_len - sizeof(kip_ext)]; + + if (strncasecmp((const char*)kip_ext, file_ext, sizeof(kip_ext))) + continue; + + if (R_SUCCEEDED(CustParser(full_path, filesize))) { + LogLine("Parsed cust config from \"%s\"", full_path); + return; + } + } + } + + ERROR_THROW("Cannot locate loader.kip in /, /atmosphere/, /atmosphere/kips/ and /bootloader/"); +} + +Result FileUtils::CustParser(const char* filepath, size_t filesize) { + enum ParseError { + ParseError_Success = 0, + ParseError_OpenReadFailed, + ParseError_WrongKipMagic, + ParseError_CustNotFound, + ParseError_WrongCustRev, + }; + + FILE* fp = fopen(filepath, "r"); + if (!fp) + return ParseError_OpenReadFailed; + SCOPE_EXIT { fclose(fp); }; + + constexpr uint8_t KIP_MAGIC[] = {'K', 'I', 'P', '1', 'L', 'o', 'a', 'd', 'e', 'r'}; + constexpr size_t BLOCK_SIZE = 0x1000; + + char* tmp_block = new char[BLOCK_SIZE]; + SCOPE_EXIT { delete[] tmp_block; }; + fread(tmp_block, sizeof(char), BLOCK_SIZE, fp); + + if (memcmp(KIP_MAGIC, tmp_block, sizeof(KIP_MAGIC))) + return ParseError_WrongKipMagic; + + CustTable table {}; + + fpos_t cust_pos = 0; + long block_pos = 0; + while ((block_pos = ftell(fp)) >= 0 && (size_t)block_pos < filesize) { + for (size_t i = 0; i < BLOCK_SIZE; i += sizeof(table.cust)) { + if (memcmp(table.cust, &tmp_block[i], sizeof(table.cust))) + continue; + + fgetpos(fp, &cust_pos); + cust_pos = cust_pos + i - BLOCK_SIZE; + goto found; + } + fread(tmp_block, sizeof(char), BLOCK_SIZE, fp); + } + + found: + if (!cust_pos) + return ParseError_CustNotFound; + + memset(reinterpret_cast(&table), 0, sizeof(CustTable)); + fsetpos(fp, &cust_pos); + if (!fread(reinterpret_cast(&table), 1, sizeof(CustTable), fp)) + return ParseError_OpenReadFailed; + + if (table.custRev != CUST_REV) + return ParseError_WrongCustRev; + + if (table.commonCpuBoostClock) + Clocks::boostCpuFreq = table.commonCpuBoostClock * 1000; + + CustomizeCpuDvfsTable* cpu_dvfs_table = nullptr; + CustomizeGpuDvfsTable* gpu_dvfs_table = nullptr; + + if (Clocks::GetIsMariko()) { + if (table.marikoEmcMaxClock) + Clocks::maxMemFreq = table.marikoEmcMaxClock * 1000; + if (table.marikoEmcVddqVolt && table.marikoEmcVddqVolt >= 550'000 && table.marikoEmcVddqVolt <= 650'000) { + u32 mvolt = table.marikoEmcVddqVolt / 1000; + Result res = I2c_BuckConverter_SetMvOut(&I2c_Mariko_DRAM_VDDQ, mvolt); + LogLine("Set EMC Vddq volt to %u mV: %s", mvolt, R_FAILED(res) ? "Failed" : "OK"); + } + if (table.commonEmcMemVolt && table.commonEmcMemVolt >= 1100'000 && table.commonEmcMemVolt <= 1250'000) { + u32 mvolt = table.commonEmcMemVolt / 1000; + Result res = I2c_BuckConverter_SetMvOut(&I2c_Mariko_DRAM_VDD2, mvolt); + LogLine("Set MEM Vdd2 volt to %u mV: %s", mvolt, R_FAILED(res) ? "Failed" : "OK"); + } + + cpu_dvfs_table = table.marikoCpuUV ? &table.marikoCpuDvfsTableSLT : &table.marikoCpuDvfsTable; + switch (table.marikoGpuUV) { + case 0: + gpu_dvfs_table = &table.marikoGpuDvfsTable; + break; + case 1: + gpu_dvfs_table = &table.marikoGpuDvfsTableSLT; + break; + case 2: + gpu_dvfs_table = &table.marikoGpuDvfsTableHiOPT; + break; + default: + gpu_dvfs_table = &table.marikoGpuDvfsTable; + break; + } + } else { + if (table.eristaEmcMaxClock) + Clocks::maxMemFreq = table.eristaEmcMaxClock * 1000; + + cpu_dvfs_table = &table.eristaCpuDvfsTable; + gpu_dvfs_table = &table.eristaGpuDvfsTable; + } + + // Fill Clocks::freqTable + cvb_entry_t* cpu_dvfs_entry = reinterpret_cast(cpu_dvfs_table); + for (size_t i = 0, j = 0; i < FREQ_TABLE_MAX_ENTRY_COUNT; i++) { + // Skip CPU frequencies < 408 MHz that are not usable + uint32_t freq = cpu_dvfs_entry[i].freq; + if (freq < 408'000) + continue; + Clocks::freqTable[SysClkModule_CPU].freq[j++] = freq * 1000; + } + + cvb_entry_t* gpu_dvfs_entry = reinterpret_cast(gpu_dvfs_table); + for (size_t i = 0; i < FREQ_TABLE_MAX_ENTRY_COUNT; i++) { + Clocks::freqTable[SysClkModule_GPU].freq[i] = gpu_dvfs_entry[i].freq * 1000; + } + + // Appending maximum mem freq to freqTable + uint32_t* mem_entry = &Clocks::freqTable[SysClkModule_MEM].freq[0]; + while (*(++mem_entry)); + *mem_entry = Clocks::maxMemFreq; + + return ParseError_Success; +} + +Result FileUtils::mkdir_p(const char* dirpath) { + // https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950 + auto mkdir_wrapper = [](char* path) { + errno = 0; + size_t len = strnlen(path, 0x1000); + bool isCWDir = (len == 0) || (len == 1 && (path[0] == '.' || path[0] == '/')); + if (isCWDir) + return 0; + + if (R_SUCCEEDED(mkdir(path, S_IRWXU))) + return 0; + + struct stat st; + if (errno == EEXIST && + R_SUCCEEDED(stat(path, &st)) && + S_ISDIR(st.st_mode)) { + errno = 0; + return 0; + } + + errno = ENOTDIR; + return -1; + }; + + errno = 0; + Result res = 0; + + size_t path_len = strnlen(dirpath, 0x1000); + char* path_copy = new char[path_len]; + SCOPE_EXIT { delete[] path_copy; }; + memcpy(path_copy, dirpath, path_len); + char* p = path_copy; + while (*p) { + if (*p == '/') { + // Temporarily truncate + *p = '\0'; + + if (R_FAILED(mkdir_wrapper(path_copy))) { + res = -1; + return res; + } + + *p = '/'; + } + p++; + } + + if (R_FAILED(mkdir_wrapper(path_copy))) + res = -1; + + return res; +} diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/file_utils.h b/Source/sys-clk-OC(deprecated)/sysmodule/src/file_utils.h new file mode 100644 index 00000000..6da285ef --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/file_utils.h @@ -0,0 +1,97 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +#define FILE_CONFIG_DIR "/config/sys-clk-oc" +#define FILE_FLAG_CHECK_INTERVAL_NS 5000000000ULL +#define FILE_CONTEXT_CSV_PATH FILE_CONFIG_DIR "/context.csv" +#define FILE_LOG_FLAG_PATH FILE_CONFIG_DIR "/log.flag" +#define FILE_LOG_FILE_PATH FILE_CONFIG_DIR "/log.txt" + +typedef struct cvb_coefficients { + s32 c0 = 0; + s32 c1 = 0; + s32 c2 = 0; + s32 c3 = 0; + s32 c4 = 0; + s32 c5 = 0; +} cvb_coefficients; + +typedef struct cvb_entry_t { + u64 freq; + cvb_coefficients cvb_dfll_param; + cvb_coefficients cvb_pll_param; +} cvb_entry_t; +static_assert(sizeof(cvb_entry_t) == 0x38); + +using CustomizeCpuDvfsTable = cvb_entry_t[FREQ_TABLE_MAX_ENTRY_COUNT]; +using CustomizeGpuDvfsTable = cvb_entry_t[FREQ_TABLE_MAX_ENTRY_COUNT]; + +constexpr uint32_t CUST_REV = 10; + +typedef struct CustTable { + u8 cust[4] = {'C', 'U', 'S', 'T'}; + u32 custRev; + u32 mtcConf; + u32 commonCpuBoostClock; + u32 commonEmcMemVolt; + u32 eristaCpuMaxVolt; + u32 eristaEmcMaxClock; + u32 marikoCpuMaxVolt; + u32 marikoEmcMaxClock; + u32 marikoEmcVddqVolt; + u32 marikoCpuUV; + u32 marikoGpuUV; + u32 marikoEmcDvbShift; + u32 ramTimingPresetOne; + u32 ramTimingPresetTwo; + u32 ramTimingPresetThree; + u32 ramTimingPresetFour; + u32 ramTimingPresetFive; + u32 ramTimingPresetSix; + u32 ramTimingPresetSeven; + u32 marikoGpuVoltArray[17]; + CustomizeCpuDvfsTable eristaCpuDvfsTable; + CustomizeCpuDvfsTable marikoCpuDvfsTable; + CustomizeCpuDvfsTable marikoCpuDvfsTableSLT; + CustomizeGpuDvfsTable eristaGpuDvfsTable; + CustomizeGpuDvfsTable marikoGpuDvfsTable; + CustomizeGpuDvfsTable marikoGpuDvfsTableSLT; + CustomizeGpuDvfsTable marikoGpuDvfsTableHiOPT; + //void* eristaMtcTable; + //void* marikoMtcTable; +} CustTable; +//static_assert(sizeof(CustTable) == sizeof(u8) * 4 + sizeof(u32) * 9 + sizeof(CustomizeCpuDvfsTable) * 4 + sizeof(void*) * 2); +//static_assert(sizeof(CustTable) == 7000); + +class FileUtils +{ + public: + static void Exit(); + static Result Initialize(); + static bool IsInitialized(); + static void InitializeAsync(); + static void LogLine(const char *format, ...); + static void WriteContextToCsv(const SysClkContext* context); + static void ParseLoaderKip(); + static Result mkdir_p(const char* dirpath); + protected: + static void RefreshFlags(bool force); + static Result CustParser(const char* path, size_t filesize); +}; diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/ipc_service.cpp b/Source/sys-clk-OC(deprecated)/sysmodule/src/ipc_service.cpp new file mode 100644 index 00000000..26557f2d --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/ipc_service.cpp @@ -0,0 +1,343 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "ipc_service.h" +#include +#include +#include "file_utils.h" +#include "clock_manager.h" +#include "errors.h" + +IpcService::IpcService() +{ + std::int32_t priority; + Result rc = svcGetThreadPriority(&priority, CUR_THREAD_HANDLE); + ASSERT_RESULT_OK(rc, "svcGetThreadPriority"); + rc = ipcServerInit(&this->server, SYSCLK_IPC_SERVICE_NAME, 42); + ASSERT_RESULT_OK(rc, "ipcServerInit"); + rc = threadCreate(&this->thread, &IpcService::ProcessThreadFunc, this, NULL, 0x2000, priority, -2); + ASSERT_RESULT_OK(rc, "threadCreate"); + this->running = false; +} + +void IpcService::SetRunning(bool running) +{ + std::scoped_lock lock{this->threadMutex}; + if(this->running == running) + { + return; + } + + this->running = running; + + if(running) + { + Result rc = threadStart(&this->thread); + ASSERT_RESULT_OK(rc, "threadStart"); + } + else + { + svcCancelSynchronization(this->thread.handle); + threadWaitForExit(&this->thread); + } +} + +IpcService::~IpcService() +{ + this->SetRunning(false); + Result rc = threadClose(&this->thread); + ASSERT_RESULT_OK(rc, "threadClose"); + rc = ipcServerExit(&this->server); + ASSERT_RESULT_OK(rc, "ipcServerExit"); +} + +void IpcService::ProcessThreadFunc(void *arg) +{ + Result rc; + IpcService* ipcSrv = (IpcService*)arg; + while(true) + { + rc = ipcServerProcess(&ipcSrv->server, &IpcService::ServiceHandlerFunc, arg); + if(R_FAILED(rc)) + { + if(rc == KERNELRESULT(Cancelled)) + { + return; + } + if(rc != KERNELRESULT(ConnectionClosed)) + { + FileUtils::LogLine("[ipc] ipcServerProcess: [0x%x] %04d-%04d", rc, R_MODULE(rc), R_DESCRIPTION(rc)); + } + } + } +} + +Result IpcService::ServiceHandlerFunc(void* arg, const IpcServerRequest* r, u8* out_data, size_t* out_dataSize) +{ + IpcService* ipcSrv = (IpcService*)arg; + + switch(r->data.cmdId) + { + case SysClkIpcCmd_GetApiVersion: + *out_dataSize = sizeof(u32); + return ipcSrv->GetApiVersion((u32*)out_data); + + case SysClkIpcCmd_GetVersionString: + if(r->hipc.meta.num_recv_buffers >= 1) + { + return ipcSrv->GetVersionString( + (char*)hipcGetBufferAddress(r->hipc.data.recv_buffers), + hipcGetBufferSize(r->hipc.data.recv_buffers) + ); + } + break; + + case SysClkIpcCmd_GetCurrentContext: + *out_dataSize = sizeof(SysClkContext); + return ipcSrv->GetCurrentContext((SysClkContext*)out_data); + + case SysClkIpcCmd_Exit: + return ipcSrv->Exit(); + + case SysClkIpcCmd_GetProfileCount: + if(r->data.size >= sizeof(std::uint64_t)) + { + *out_dataSize = sizeof(std::uint8_t); + return ipcSrv->GetProfileCount((std::uint64_t*)r->data.ptr, (std::uint8_t*)out_data); + } + break; + + case SysClkIpcCmd_GetProfiles: + if(r->data.size >= sizeof(std::uint64_t)) + { + *out_dataSize = sizeof(SysClkTitleProfileList); + return ipcSrv->GetProfiles((std::uint64_t*)r->data.ptr, (SysClkTitleProfileList*)out_data); + } + break; + + case SysClkIpcCmd_SetProfiles: + if(r->data.size >= sizeof(SysClkIpc_SetProfiles_Args)) + { + return ipcSrv->SetProfiles((SysClkIpc_SetProfiles_Args*)r->data.ptr); + } + break; + + case SysClkIpcCmd_SetEnabled: + if(r->data.size >= sizeof(std::uint8_t)) + { + return ipcSrv->SetEnabled((std::uint8_t*)r->data.ptr); + } + break; + + case SysClkIpcCmd_SetOverride: + if(r->data.size >= sizeof(SysClkIpc_SetOverride_Args)) + { + return ipcSrv->SetOverride((SysClkIpc_SetOverride_Args*)r->data.ptr); + } + break; + + case SysClkIpcCmd_GetConfigValues: + *out_dataSize = sizeof(SysClkConfigValueList); + return ipcSrv->GetConfigValues((SysClkConfigValueList*)out_data); + + case SysClkIpcCmd_SetConfigValues: + if(r->data.size >= sizeof(SysClkConfigValueList)) + { + return ipcSrv->SetConfigValues((SysClkConfigValueList*)r->data.ptr); + } + break; + + case SysClkIpcCmd_SetReverseNXRTMode: + if (r->data.size >= sizeof(ReverseNXMode)) { + ReverseNXMode mode = *((ReverseNXMode*)r->data.ptr); + return ipcSrv->SetReverseNXRTMode(mode); + } + break; + case SysClkIpcCmd_GetFrequencyTable: + if(r->data.size >= sizeof(SysClkIpc_GetFrequencyTable_Args)) + { + SysClkIpc_GetFrequencyTable_Args* in_args = (SysClkIpc_GetFrequencyTable_Args*)r->data.ptr; + *out_dataSize = sizeof(SysClkFrequencyTable); + return ipcSrv->GetFrequencyTable(in_args, (SysClkFrequencyTable*)out_data); + } + break; + case SysClkIpcCmd_GetIsMariko: + *out_dataSize = sizeof(bool); + return ipcSrv->GetIsMariko((bool*)out_data); + case SysClkIpcCmd_GetBatteryChargingDisabledOverride: + *out_dataSize = sizeof(bool); + return ipcSrv->GetBatteryChargingDisabledOverride((bool*)out_data); + case SysClkIpcCmd_SetBatteryChargingDisabledOverride: + if (r->data.size >= sizeof(bool)) { + bool toggle_true = *((bool*)(r->data.ptr)); + return ipcSrv->SetBatteryChargingDisabledOverride(toggle_true); + } + break; + } + + return SYSCLK_ERROR(Generic); +} + +Result IpcService::GetApiVersion(u32* out_version) +{ + *out_version = SYSCLK_IPC_API_VERSION; + + return 0; +} + +Result IpcService::GetVersionString(char* out_buf, size_t bufSize) +{ + if(bufSize) + { + strncpy(out_buf, TARGET_VERSION, bufSize-1); + } + + return 0; +} + +Result IpcService::GetCurrentContext(SysClkContext* out_ctx) +{ + ClockManager* clockMgr = ClockManager::GetInstance(); + *out_ctx = clockMgr->GetCurrentContext(); + + return 0; +} + +Result IpcService::Exit() +{ + ClockManager* clockMgr = ClockManager::GetInstance(); + clockMgr->SetRunning(false); + + return 0; +} + +Result IpcService::GetProfileCount(std::uint64_t* tid, std::uint8_t* out_count) +{ + Config* config = ClockManager::GetInstance()->GetConfig(); + if(!config->HasProfilesLoaded()) + { + return SYSCLK_ERROR(ConfigNotLoaded); + } + + *out_count = config->GetProfileCount(*tid); + + return 0; +} + +Result IpcService::GetProfiles(std::uint64_t* tid, SysClkTitleProfileList* out_profiles) +{ + Config* config = ClockManager::GetInstance()->GetConfig(); + if(!config->HasProfilesLoaded()) + { + return SYSCLK_ERROR(ConfigNotLoaded); + } + + config->GetProfiles(*tid, out_profiles); + + return 0; +} + +Result IpcService::SetProfiles(SysClkIpc_SetProfiles_Args* args) +{ + Config* config = ClockManager::GetInstance()->GetConfig(); + if(!config->HasProfilesLoaded()) + { + return SYSCLK_ERROR(ConfigNotLoaded); + } + + SysClkTitleProfileList profiles = args->profiles; + + if(!config->SetProfiles(args->tid, &profiles, true)) + { + return SYSCLK_ERROR(ConfigSaveFailed); + } + + return 0; +} + +Result IpcService::SetEnabled(std::uint8_t* enabled) +{ + Config* config = ClockManager::GetInstance()->GetConfig(); + config->SetEnabled(*enabled); + + return 0; +} + +Result IpcService::SetOverride(SysClkIpc_SetOverride_Args* args) +{ + SysClkModule module = args->module; + std::uint32_t hz = args->hz; + + if(!SYSCLK_ENUM_VALID(SysClkModule, args->module)) + { + return SYSCLK_ERROR(Generic); + } + + Config* config = ClockManager::GetInstance()->GetConfig(); + config->SetOverrideHz(module, hz); + + return 0; +} + +Result IpcService::GetConfigValues(SysClkConfigValueList* out_configValues) +{ + Config* config = ClockManager::GetInstance()->GetConfig(); + if(!config->HasProfilesLoaded()) + { + return SYSCLK_ERROR(ConfigNotLoaded); + } + + config->GetConfigValues(out_configValues); + + return 0; +} + +Result IpcService::SetConfigValues(SysClkConfigValueList* configValues) +{ + Config* config = ClockManager::GetInstance()->GetConfig(); + if(!config->HasProfilesLoaded()) + { + return SYSCLK_ERROR(ConfigNotLoaded); + } + + SysClkConfigValueList configValuesCopy = *configValues; + + if(!config->SetConfigValues(&configValuesCopy, true)) + { + return SYSCLK_ERROR(ConfigSaveFailed); + } + + return 0; +} + +Result IpcService::SetReverseNXRTMode(ReverseNXMode mode) { + ClockManager::GetInstance()->SetRNXRTMode(mode); + return 0; +} + +Result IpcService::GetFrequencyTable(SysClkIpc_GetFrequencyTable_Args* args, SysClkFrequencyTable* out_table) { + return Clocks::GetTable(args->module, args->profile, out_table); +} + +Result IpcService::GetIsMariko(bool* out_is_mariko) { + *out_is_mariko = Clocks::GetIsMariko(); + return 0; +} + +Result IpcService::GetBatteryChargingDisabledOverride(bool* out_is_true) { + *out_is_true = ClockManager::GetInstance()->GetBatteryChargingDisabledOverride(); + return 0; +} + + +Result IpcService::SetBatteryChargingDisabledOverride(bool toggle_true) { + return ClockManager::GetInstance()->SetBatteryChargingDisabledOverride(toggle_true); +} + diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/ipc_service.h b/Source/sys-clk-OC(deprecated)/sysmodule/src/ipc_service.h new file mode 100644 index 00000000..fb975c69 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/ipc_service.h @@ -0,0 +1,49 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include +#include +#include + +class IpcService +{ + public: + IpcService(); + virtual ~IpcService(); + void SetRunning(bool running); + + protected: + static void ProcessThreadFunc(void *arg); + static Result ServiceHandlerFunc(void* arg, const IpcServerRequest* r, std::uint8_t* out_data, size_t* out_dataSize); + + Result GetApiVersion(u32* out_version); + Result GetVersionString(char* out_buf, size_t bufSize); + Result GetCurrentContext(SysClkContext* out_ctx); + Result Exit(); + Result GetProfileCount(std::uint64_t* tid, std::uint8_t* out_count); + Result GetProfiles(std::uint64_t* tid, SysClkTitleProfileList* out_profiles); + Result SetProfiles(SysClkIpc_SetProfiles_Args* args); + Result SetEnabled(std::uint8_t* enabled); + Result SetOverride(SysClkIpc_SetOverride_Args* args); + Result GetConfigValues(SysClkConfigValueList* out_configValues); + Result SetConfigValues(SysClkConfigValueList* configValues); + Result SetReverseNXRTMode(ReverseNXMode mode); + Result GetFrequencyTable(SysClkIpc_GetFrequencyTable_Args* args, SysClkFrequencyTable* out_table); + Result GetIsMariko(bool* out_is_mariko); + Result GetBatteryChargingDisabledOverride(bool* out_is_true); + Result SetBatteryChargingDisabledOverride(bool toggle_true); + + bool running; + Thread thread; + LockableMutex threadMutex; + IpcServer server; +}; diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/main.cpp b/Source/sys-clk-OC(deprecated)/sysmodule/src/main.cpp new file mode 100644 index 00000000..46c258aa --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/main.cpp @@ -0,0 +1,135 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include +#include +#include + +#include + +#include "errors.h" +#include "file_utils.h" +#include "clocks.h" +#include "process_management.h" +#include "clock_manager.h" +#include "ipc_service.h" +#include "oc_extra.h" + +#define INNER_HEAP_SIZE 0x50000 + +extern "C" +{ + extern std::uint32_t __start__; + + //set applet type to use nvdrv* service + std::uint32_t __nx_applet_type = AppletType_SystemApplication; + TimeServiceType __nx_time_service_type = TimeServiceType_System; + std::uint32_t __nx_fs_num_sessions = 1; + + size_t nx_inner_heap_size = INNER_HEAP_SIZE; + char nx_inner_heap[INNER_HEAP_SIZE]; + + // set transfermem size to 32kib as [Fizeau](https://github.com/averne/Fizeau/) + // or LibnxError_OutOfMemory + u32 __nx_nv_transfermem_size = 0x8000; + + void __libnx_initheap(void) + { + void *addr = nx_inner_heap; + size_t size = nx_inner_heap_size; + + /* Newlib Heap Management */ + extern char *fake_heap_start; + extern char *fake_heap_end; + + fake_heap_start = (char *)addr; + fake_heap_end = (char *)addr + size; + } + + void __appInit(void) + { + if (R_FAILED(smInitialize())) + { + fatalThrow(MAKERESULT(Module_Libnx, LibnxError_InitFail_SM)); + } + + Result rc = setsysInitialize(); + if (R_SUCCEEDED(rc)) + { + SetSysFirmwareVersion fw; + rc = setsysGetFirmwareVersion(&fw); + if (R_SUCCEEDED(rc)) + hosversionSet(MAKEHOSVERSION(fw.major, fw.minor, fw.micro)); + setsysExit(); + } + + rc = i2cInitialize(); + if (R_FAILED(rc)) + fatalThrow(rc); + } + + void __appExit(void) + { + i2cExit(); + smExit(); + } +} + +int main(int argc, char **argv) +{ + Result rc = FileUtils::Initialize(); + if (R_FAILED(rc)) + { + fatalThrow(rc); + return 1; + } + + try + { + Clocks::Initialize(); + ProcessManagement::Initialize(); + + ProcessManagement::WaitForQLaunch(); + ClockManager::Initialize(); + FileUtils::LogLine("Ready"); + + ClockManager *clockMgr = ClockManager::GetInstance(); + IpcService *ipcSrv = new IpcService(); + clockMgr->SetRunning(true); + clockMgr->GetConfig()->SetEnabled(true); + ipcSrv->SetRunning(true); + + while (clockMgr->Running()) + { + clockMgr->Tick(); + clockMgr->WaitForNextTick(); + } + + ipcSrv->SetRunning(false); + delete ipcSrv; + ClockManager::Exit(); + ProcessManagement::Exit(); + Clocks::Exit(); + } + catch (const std::exception &ex) + { + FileUtils::LogLine("[!] %s", ex.what()); + } + catch (...) + { + std::exception_ptr p = std::current_exception(); + FileUtils::LogLine("[!?] %s", p ? p.__cxa_exception_type()->name() : "..."); + } + + FileUtils::LogLine("Exit"); + svcSleepThread(1000000ULL); + FileUtils::Exit(); + return 0; +} diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/oc_extra.cpp b/Source/sys-clk-OC(deprecated)/sysmodule/src/oc_extra.cpp new file mode 100644 index 00000000..f6f1770e --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/oc_extra.cpp @@ -0,0 +1,358 @@ +#include "oc_extra.h" + +CpuCoreUtil::CpuCoreUtil(int coreid = -2, uint64_t ns = 1000'000ULL) + : m_core_id(coreid), m_wait_time_ns(ns) { } + +uint32_t CpuCoreUtil::Get() { + struct _ctx { + uint64_t systick; + uint64_t idletick; + } begin, end; + + begin.systick = armGetSystemTick(); + begin.idletick = GetIdleTickCount(); + + svcSleepThread(m_wait_time_ns); + + end.systick = armGetSystemTick(); + end.idletick = GetIdleTickCount(); + + uint64_t diff_idletick = end.idletick - begin.idletick; + uint64_t diff_systick = end.systick - begin.systick; + return UTIL_MAX - diff_idletick * 10 * 100ULL / diff_systick; +} + +uint64_t CpuCoreUtil::GetIdleTickCount() { + uint64_t idletick = 0; + svcGetInfo(&idletick, InfoType_IdleTickCount, INVALID_HANDLE, m_core_id); + return idletick; +} + + +GpuCoreUtil::GpuCoreUtil(uint32_t nvgpu_field) + : m_nvgpu_field(nvgpu_field) { } + +uint32_t GpuCoreUtil::Get() { + uint32_t load; + nvIoctl(m_nvgpu_field, NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD, &load); + return load; +} + + +ReverseNXSync::ReverseNXSync() + : m_rt_mode(ReverseNX_NotFound), m_tool_mode(ReverseNX_NotFound) { + FILE *fp = fopen("/atmosphere/contents/0000000000534C56/flags/boot2.flag", "r"); + m_tool_enabled = fp ? true : false; + if (fp) + fclose(fp); +} + +SysClkProfile ReverseNXSync::GetProfile(SysClkProfile real) { + switch (this->GetMode()) { + case ReverseNX_Docked: + return SysClkProfile_Docked; + case ReverseNX_Handheld: + if (real == SysClkProfile_Docked) + return SysClkProfile_HandheldChargingOfficial; + default: + return real; + } +} + +ReverseNXMode ReverseNXSync::GetMode() { + if (!this->m_sync_enabled) + return ReverseNX_NotFound; + if (this->m_rt_mode) + return this->m_rt_mode; + return this->m_tool_mode; +} + +ReverseNXMode ReverseNXSync::GetToolModeFromPatch(const char* patch_path) { + constexpr uint32_t DOCKED_MAGIC = 0x320003E0; + constexpr uint32_t HANDHELD_MAGIC = 0x52A00000; + FILE *fp = fopen(patch_path, "rb"); + if (fp) { + uint32_t buf = 0; + fread(&buf, sizeof(buf), 1, fp); + fclose(fp); + + if (buf == DOCKED_MAGIC) + return ReverseNX_Docked; + if (buf == HANDHELD_MAGIC) + return ReverseNX_Handheld; + } + + return ReverseNX_NotFound; +} + +ReverseNXMode ReverseNXSync::RecheckToolMode() { + ReverseNXMode mode = ReverseNX_NotFound; + if (this->m_tool_enabled) { + const char* fileName = "_ZN2nn2oe16GetOperationModeEv.asm64"; // or _ZN2nn2oe18GetPerformanceModeEv.asm64 + const char* filePath = new char[72]; + SCOPE_EXIT { delete[] filePath; }; + /* Check per-game patch */ + snprintf((char*)filePath, 72, "/SaltySD/patches/%016lX/%s", this->m_app_id, fileName); + mode = this->GetToolModeFromPatch(filePath); + if (!mode) { + /* Check global patch */ + snprintf((char*)filePath, 72, "/SaltySD/patches/%s", fileName); + mode = this->GetToolModeFromPatch(filePath); + } + } + + return mode; +} + + +void PsmExt::ChargingHandler(ClockManager* instance) { + u32 current; + Result res = I2c_Bq24193_GetFastChargeCurrentLimit(¤t); + if (R_SUCCEEDED(res)) { + current -= current % 100; + u32 chargingCurrent = instance->GetConfig()->GetConfigValue(SysClkConfigValue_ChargingCurrentLimit); + if (current != chargingCurrent) + I2c_Bq24193_SetFastChargeCurrentLimit(chargingCurrent); + } + + PsmChargeInfo* info = new PsmChargeInfo; + Service* session = psmGetServiceSession(); + serviceDispatchOut(session, Psm_GetBatteryChargeInfoFields, *info); + + if (PsmIsChargerConnected(info)) { + u32 chargeNow = 0; + if (R_SUCCEEDED(psmGetBatteryChargePercentage(&chargeNow))) { + bool isCharging = PsmIsCharging(info); + u32 chargingLimit = instance->GetConfig()->GetConfigValue(SysClkConfigValue_ChargingLimitPercentage); + bool forceDisabled = instance->GetBatteryChargingDisabledOverride(); + if (isCharging && (forceDisabled || chargingLimit <= chargeNow)) + serviceDispatch(session, Psm_DisableBatteryCharging); + if (!isCharging && chargingLimit > chargeNow) + serviceDispatch(session, Psm_EnableBatteryCharging); + } + } + + delete info; +} + +namespace GovernorImpl { + +// Schedutil: https://github.com/torvalds/linux/blob/master/kernel/sched/cpufreq_schedutil.c +// C = 1.25, tipping-point 80.0% (used in Linux schedutil), 1.25 -> 1 + (1 >> 2) +// C = 1.5, tipping-point 66.7%, 1.5 -> 1 + (1 >> 1) +// Utilization is frequency-invariant : +// target_freq = C * max_freq * util / max +// Approximate the would-be frequency-invariant utilization (normalized) : +// target_freq = C * curr_freq * util_raw / max +void BaseGovernor::ApplyNewFreqFromNormUtil(uint32_t normUtil) { + uint32_t curr_hz = m_target_hz; + + auto FindHzInTable = [](uint32_t* list, uint32_t hz) -> uint32_t { + uint32_t* p = list; + for (; *p != 0; p++) { + if (hz <= *p) + return *p; + } + return *(--p); + }; + + //uint32_t next_freq = m_ref_hz / UTIL_MAX * normUtil; + uint32_t next_freq = max_hz / UTIL_MAX * normUtil; + next_freq += next_freq >> 1; + + uint32_t new_hz; + if (next_freq >= max_hz) + new_hz = max_hz; + else if (next_freq <= min_hz) + new_hz = min_hz; + else + new_hz = FindHzInTable(m_hz_list, next_freq); + + if (new_hz != curr_hz) + ApplyTargetFreq(new_hz); +} + +void CpuGovernor::GovernorWorker::Start() { + if (this->running) + return; + + this->running = true; + Result rc = 0; + for (int id = 0; id < CORE_NUMS; id++) { + WorkerContext* s = &contexts[id]; + s->super = this->super; + s->id = id; + int prio = (id == CORE_NUMS - 1) ? 0x3F : 0x3B; // Pre-emptive MT + rc = threadCreate(&threads[id], &WorkerContext::Loop, (void*)s, NULL, 0x400, prio, id); + ASSERT_RESULT_OK(rc, "threadCreate"); + rc = threadStart(&threads[id]); + ASSERT_RESULT_OK(rc, "threadStart"); + } +} + +void CpuGovernor::GovernorWorker::Stop() { + if (!this->running) + return; + + this->running = false; + svcSleepThread(TICK_TIME_NS); + + for (auto &t : threads) { + threadWaitForExit(&t); + threadClose(&t); + } +} + +void CpuGovernor::Apply() { + uint32_t util = 0; + for (auto& ctx : this->m_worker.contexts) { + uint32_t core_util = ctx.util; + if (util < core_util) + util = core_util; + } + + this->m_util.Update(util); + if (this->auto_boost && this->m_worker.contexts[SYS_CORE_ID].util > BOOST_THRESHOLD) + this->ApplyBoost(); + else + this->ApplyNewFreqFromNormUtil(this->m_util.Get()); +} + +void CpuGovernor::WorkerContext::Loop(void* args) { + WorkerContext* s = static_cast(args); + CpuGovernor* self = s->super; + GovernorWorker* worker = &(self->m_worker); + int coreid = s->id; + + while (worker->running) { + uint64_t tick = s->tick = armGetSystemTick(); + s->util = self->CalcNormalizedUtil(CpuCoreUtil(coreid, TICK_TIME_NS).Get()); + + if (apmExtIsCPUBoosted(self->m_manager->GetPerfConf())) { + svcSleepThread(TICK_TIME_NS); + continue; + } + + // Check if other cores are stuck + for (int id = 0; id < CORE_NUMS; id++) { + if (id == coreid) + continue; + + uint64_t diff = std::abs((int64_t)worker->contexts[id].tick - (int64_t)tick); + if (diff < SYSTICK_HZ / SAMPLE_RATE * 10) + continue; + + // Stuck on system core and auto boost enabled, apply boost + if (id == SYS_CORE_ID && self->auto_boost) { + self->ApplyBoost(); + break; + } + + // Stuck on other cores or auto boost disabled, apply max hz + self->ApplyTargetFreq(self->max_hz); + break; + } + } +} + +void GpuGovernor::Apply() { + uint32_t util = this->CalcNormalizedUtil(GpuCoreUtil(m_nvgpu_field).Get()); + this->m_util.Update(util); + this->ApplyNewFreqFromNormUtil(this->m_util.Get()); +} + +} + +void Governor::SetConfig(SysClkOcGovernorConfig config) { + if (m_config == config) + return; + + m_config = config; + m_cpu_gov->m_worker.onConfigUpdated(config); + m_manager.onConfigUpdated(config); +}; + +void Governor::SetPerfConf(uint32_t id) { + m_perf_conf_id = id; + m_apm_conf = Clocks::GetEmbeddedApmConfig(id); +} + +void Governor::SetMaxHz(uint32_t maxHz, SysClkModule module) { + if (!maxHz) // Fallback to apm configuration + maxHz = Clocks::GetStockClock(m_apm_conf, (SysClkModule)module); + + switch (module) { + case SysClkModule_CPU: + m_cpu_gov->max_hz = maxHz; + break; + case SysClkModule_GPU: + m_gpu_gov->max_hz = maxHz; + m_gpu_gov->min_hz = (maxHz <= 153'600'000) ? maxHz : 153'600'000; + break; + default: + break; + } +} + +void Governor::SetMinHz(uint32_t minHz, SysClkModule module) { + if (module == SysClkModule_CPU) { + m_cpu_gov->min_hz = minHz; + } +} + +void Governor::GovernorManager::Start() { + if (this->running) + return; + + this->running = true; + Result rc = threadCreate(&thread, &ContextManager, (void*)this, NULL, 0x400, 0x3F, 3); + ASSERT_RESULT_OK(rc, "threadCreate"); + rc = threadStart(&thread); + ASSERT_RESULT_OK(rc, "threadStart"); +} + +void Governor::GovernorManager::Stop() { + if (!this->running) + return; + + this->running = false; + svcSleepThread(TICK_TIME_NS); + threadWaitForExit(&thread); + threadClose(&thread); +} + +void Governor::GovernorManager::ContextManager(void* args) { + Governor* self = static_cast(args); + + constexpr uint64_t UPDATE_CONTEXT_RATE = SAMPLE_RATE / 2; + uint64_t update_ticks = UPDATE_CONTEXT_RATE; + bool cpuBoosted = false, gpuThrottled = false; + + while (self->m_manager.running) { + bool shouldUpdateContext = ++update_ticks >= UPDATE_CONTEXT_RATE; + if (shouldUpdateContext) { + update_ticks = 0; + + uint32_t hz = self->m_gpu_gov->RefreshContext(); + // Sleep mode detected, wait 10 ticks + while (!hz) { + svcSleepThread(10 * TICK_TIME_NS); + hz = self->m_gpu_gov->RefreshContext(); + } + + uint32_t perf_conf = self->GetPerfConf(); + if ((gpuThrottled = apmExtIsBoostMode(perf_conf)) && self->IsHandledByGovernor(SysClkModule_GPU)) + self->m_gpu_gov->ApplyBoost(); + + if ((cpuBoosted = apmExtIsCPUBoosted(perf_conf)) && self->IsHandledByGovernor(SysClkModule_CPU)) + self->m_cpu_gov->ApplyBoost(); + } + + if (!gpuThrottled && self->IsHandledByGovernor(SysClkModule_GPU)) + self->m_gpu_gov->Apply(); + if (!cpuBoosted && self->IsHandledByGovernor(SysClkModule_CPU)) + self->m_cpu_gov->Apply(); + + svcSleepThread(TICK_TIME_NS); + } +}; diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/oc_extra.h b/Source/sys-clk-OC(deprecated)/sysmodule/src/oc_extra.h new file mode 100644 index 00000000..b30a623b --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/oc_extra.h @@ -0,0 +1,367 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "file_utils.h" +#include "clocks.h" + +// Forward declaration +class ClockManager; +class Governor; +#include "clock_manager.h" + + +class CpuCoreUtil { +public: + CpuCoreUtil (int coreid, uint64_t ns); + uint32_t Get(); + +protected: + const int m_core_id; + const uint64_t m_wait_time_ns; + static constexpr uint64_t IDLETICKS_PER_MS = 192; + static constexpr uint32_t UTIL_MAX = 100'0; + + uint64_t GetIdleTickCount(); +}; + + +class GpuCoreUtil { +public: + GpuCoreUtil (uint32_t nvgpu_field); + uint32_t Get(); + +protected: + uint32_t m_nvgpu_field; + static constexpr uint64_t NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD = 0x80044715; +}; + + +class ReverseNXSync { +public: + ReverseNXSync (); + + void ToggleSync(bool enable) { m_sync_enabled = enable; }; + void Reset(uint64_t app_id) { m_app_id = app_id; SetRTMode(ReverseNX_NotFound); GetToolMode(); } + ReverseNXMode GetRTMode() { return m_rt_mode; }; + void SetRTMode(ReverseNXMode mode) { m_rt_mode = mode; }; + ReverseNXMode GetToolMode() { return m_tool_mode = RecheckToolMode(); }; + SysClkProfile GetProfile(SysClkProfile real); + ReverseNXMode GetMode(); + +protected: + std::atomic m_rt_mode; + ReverseNXMode m_tool_mode; + uint64_t m_app_id = 0; + bool m_tool_enabled; + bool m_sync_enabled; + + ReverseNXMode GetToolModeFromPatch(const char* patch_path); + ReverseNXMode RecheckToolMode(); +}; + + +namespace PsmExt { + void ChargingHandler(ClockManager* instance); +} + + +constexpr uint64_t SAMPLE_RATE = 200; +constexpr uint64_t TICK_TIME_NS = 1000'000'000 / SAMPLE_RATE; +constexpr uint64_t SYSTICK_HZ = 19200000; + +namespace GovernorImpl { + constexpr uint32_t UTIL_MAX = 1000; + + class BaseGovernor { + public: + BaseGovernor(SysClkModule module) : m_module(module) { + m_hz_list = Clocks::freqTable[module].freq; + m_ref_hz = *Clocks::freqRange[module].last; + }; + + uint32_t RefreshContext() { return this->m_target_hz = Clocks::GetCurrentHz(this->m_module); }; + + uint32_t min_hz, max_hz, boost_hz; + + protected: + uint32_t CalcNormalizedUtil(uint32_t rawUtil) { + //return ((uint64_t)rawUtil * m_target_hz / m_ref_hz); + return ((uint64_t)rawUtil * m_target_hz / max_hz); + }; + + void ApplyNewFreqFromNormUtil(uint32_t norm); + + void ApplyTargetFreq(uint32_t hz) { + if (!hz) + return; + + m_target_hz = hz; + Clocks::SetHz(m_module, hz); + }; + + SysClkModule m_module; + uint32_t* m_hz_list; + uint32_t m_target_hz, m_ref_hz; + + friend Governor; + }; + + class CpuGovernor : public BaseGovernor { + public: + CpuGovernor(Governor* manager) + : BaseGovernor(SysClkModule_CPU), m_manager(manager) { + boost_hz = Clocks::boostCpuFreq; + m_worker.super = this; + }; + + ~CpuGovernor() { this->m_worker.Stop(); }; + + void Apply(); + + void ApplyBoost() { + ApplyTargetFreq((max_hz > boost_hz) ? max_hz : boost_hz); + }; + + bool auto_boost; + + protected: + static constexpr int CORE_NUMS = 4; + static constexpr int SYS_CORE_ID = CORE_NUMS - 1; + + // PELT: https://github.com/torvalds/linux/blob/master/kernel/sched/pelt.c + // Util_acc_n = Util_0 + Util_1 * D + Util_2 * D^2 + ... + Util_n * D^n + // To approximate D (decay multiplier): + // After 50 ms (if SAMPLE_RATE == 200, 10 samples) + // UTIL_MAX * D^10 ≈ 1 (UTIL_MAX decayed to 1) + // D = 4129 / 8192 + // Util_acc_max = Util_acc_inf = 2012 + typedef struct PeltUtil { + uint32_t util_acc = 0; + + static constexpr uint32_t DECAY_DIVIDENT = 4129; + static constexpr uint32_t DECAY_DIVISOR = 8192; + static constexpr uint32_t UTIL_ACC_MAX = 2012; + + uint32_t Get() { return (util_acc * UTIL_MAX / UTIL_ACC_MAX); }; + void Update(uint32_t util) { util_acc = util_acc * DECAY_DIVIDENT / DECAY_DIVISOR + util; }; + } PeltUtil; + PeltUtil m_util; + + typedef struct { + CpuGovernor*super; + int id; + uint32_t util; + uint64_t tick; + + static void Loop(void* args); + } WorkerContext; + + typedef struct GovernorWorker { + Thread threads[CORE_NUMS]; + WorkerContext contexts[CORE_NUMS]; + bool running; + CpuGovernor* super; + + void Start(); + void Stop(); + + void onConfigUpdated(SysClkOcGovernorConfig config) { + bool expected = (config >> SysClkOcGovernorConfig_CPU_Shift) & 1; + if (expected != running) + expected ? Start() : Stop(); + }; + } GovernorWorker; + GovernorWorker m_worker; + + Governor* m_manager; + + friend Governor; + }; + + class GpuGovernor : public BaseGovernor { + public: + GpuGovernor() : BaseGovernor(SysClkModule_GPU) { + min_hz = 153'600'000; + boost_hz = 76'800'000; + + nvInitialize(); + Result rc = nvOpen(&m_nvgpu_field, "/dev/nvhost-ctrl-gpu"); + if (R_FAILED(rc)) { + ASSERT_RESULT_OK(rc, "nvOpen"); + nvExit(); + } + }; + + ~GpuGovernor() { + nvClose(m_nvgpu_field); + nvExit(); + }; + + void ApplyBoost() { + ApplyTargetFreq(boost_hz); + }; + + void Apply(); + + protected: + // Get average value from a sliding window in O(1) + template + class SWindowAvg { + public: + SWindowAvg() {} + + void Add(T item) { + T pop = m_queue[m_next]; + m_queue[m_next] = item; + m_next = (m_next + 1) % WINDOW_SIZE; + m_sum -= pop; + m_sum += item; + } + + T Get() { return m_sum / WINDOW_SIZE; } + + protected: + size_t m_next = 0; + T m_sum = 0; + T m_queue[WINDOW_SIZE] = {}; + }; + + // Get max value from a sliding window in O(1) + template + class SWindowMax { + protected: + typedef struct { + T item; + T max; + } s_Entry; + + struct s_Stack { + s_Entry m_stack[WINDOW_SIZE] = {}; + size_t m_next = WINDOW_SIZE; + + bool empty() { return m_next == 0; }; + s_Entry top() { return m_stack[m_next-1]; }; + s_Entry pop() { return m_stack[--m_next]; }; + void push(s_Entry item) { + if (m_next == WINDOW_SIZE) + return; + m_stack[m_next++] = item; + }; + }; + + s_Stack enqStack; + s_Stack deqStack; + + void Push(s_Stack& stack, T item) { + s_Entry n = { + .item = item, + .max = enqStack.empty() ? item : std::max(item, enqStack.top().max) + }; + stack.push(n); + } + + T Pop() { + if (deqStack.empty()) { + while (!enqStack.empty()) + Push(deqStack, enqStack.pop().max); + } + return deqStack.pop().item; + } + + public: + SWindowMax() {} + + void Add(T item) { Pop(); Push(enqStack, item); } + + T Get() { + if (!enqStack.empty()) { + T enqMax = enqStack.top().max; + if (!deqStack.empty()) { + T deqMax = deqStack.top().max; + return std::max(deqMax, enqMax); + } + return enqMax; + } + if (!deqStack.empty()) + return deqStack.top().max; + return 0; + } + }; + + typedef struct MaxWindow { + SWindowMax window {}; + uint32_t util_acc = 0; + + // After 160 ms (if SAMPLE_RATE == 200, 32 samples) + // UTIL_MAX * D^32 ≈ 1 (UTIL_MAX decayed to 1) + // D = 6880 / 8192 + // Util_acc_max = Util_acc_inf = 6145 + static constexpr uint32_t DECAY_DIVIDENT = 6880; + static constexpr uint32_t DECAY_DIVISOR = 8192; + static constexpr uint32_t UTIL_ACC_MAX = 6145; + + uint32_t Get() { return ((util_acc * UTIL_MAX / UTIL_ACC_MAX) + window.Get()) / 2; }; + void Update(uint32_t util) { window.Add(util); util_acc = util_acc * DECAY_DIVIDENT / DECAY_DIVISOR + util; }; + } MaxWindow; + MaxWindow m_util; + + uint32_t m_nvgpu_field; + }; + +} + +class Governor { +public: + Governor() { + m_cpu_gov = new GovernorImpl::CpuGovernor(this); + m_gpu_gov = new GovernorImpl::GpuGovernor(); + }; + + ~Governor() { + m_manager.Stop(); + delete m_cpu_gov; + delete m_gpu_gov; + }; + + SysClkOcGovernorConfig GetConfig() { return m_config; }; + inline bool IsHandledByGovernor(SysClkModule module) { return GetGovernorEnabled(this->GetConfig(), module); }; + void SetConfig(SysClkOcGovernorConfig config); + + void SetPerfConf(uint32_t id); + uint32_t GetPerfConf() { return m_perf_conf_id; }; + + void SetMaxHz(uint32_t maxHz, SysClkModule module); + void SetMinHz(uint32_t minHz, SysClkModule module); + + void SetAutoCPUBoost(bool enabled) { m_cpu_gov->auto_boost = enabled; }; + void SetCPUBoostHz(uint32_t boostHz) { m_cpu_gov->boost_hz = boostHz; }; + +protected: + typedef struct GovernorManager { + bool running = false; + Thread thread; + + void Start(); + void Stop(); + void onConfigUpdated(SysClkOcGovernorConfig config) { + bool shouldRun = (config != SysClkOcGovernorConfig_AllDisabled); + shouldRun ? Start() : Stop(); + }; + static void ContextManager(void* args); + } GovernorManager; + GovernorManager m_manager; + + SysClkOcGovernorConfig m_config = SysClkOcGovernorConfig_AllDisabled; + + uint32_t m_perf_conf_id; + SysClkApmConfiguration* m_apm_conf; + + GovernorImpl::CpuGovernor* m_cpu_gov; + GovernorImpl::GpuGovernor* m_gpu_gov; +}; diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/process_management.cpp b/Source/sys-clk-OC(deprecated)/sysmodule/src/process_management.cpp new file mode 100644 index 00000000..8cb369cc --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/process_management.cpp @@ -0,0 +1,67 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "process_management.h" +#include "file_utils.h" +#include "errors.h" + +void ProcessManagement::Initialize() +{ + Result rc = 0; + + rc = pmdmntInitialize(); + ASSERT_RESULT_OK(rc, "pmdmntInitialize"); + + rc = pminfoInitialize(); + ASSERT_RESULT_OK(rc, "pminfoInitialize"); +} + +void ProcessManagement::WaitForQLaunch() +{ + Result rc = 0; + std::uint64_t pid = 0; + do + { + rc = pmdmntGetProcessId(&pid, PROCESS_MANAGEMENT_QLAUNCH_TID); + svcSleepThread(500000000ULL); + } while (R_FAILED(rc)); +} + +std::uint64_t ProcessManagement::GetCurrentApplicationId() +{ + Result rc = 0; + std::uint64_t pid = 0; + std::uint64_t tid = 0; + rc = pmdmntGetApplicationProcessId(&pid); + + if (rc == 0x20f) + { + return PROCESS_MANAGEMENT_QLAUNCH_TID; + } + + ASSERT_RESULT_OK(rc, "pmdmntGetApplicationProcessId"); + + rc = pminfoGetProgramId(&tid, pid); + + if (rc == 0x20f) + { + return PROCESS_MANAGEMENT_QLAUNCH_TID; + } + + ASSERT_RESULT_OK(rc, "pminfoGetProgramId"); + + return tid; +} + +void ProcessManagement::Exit() +{ + pmdmntExit(); + pminfoExit(); +} diff --git a/Source/sys-clk-OC(deprecated)/sysmodule/src/process_management.h b/Source/sys-clk-OC(deprecated)/sysmodule/src/process_management.h new file mode 100644 index 00000000..0727c276 --- /dev/null +++ b/Source/sys-clk-OC(deprecated)/sysmodule/src/process_management.h @@ -0,0 +1,24 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once +#include +#include + +#define PROCESS_MANAGEMENT_QLAUNCH_TID 0x0100000000001000ULL + +class ProcessManagement +{ + public: + static void Initialize(); + static void WaitForQLaunch(); + static std::uint64_t GetCurrentApplicationId(); + static void Exit(); +}; diff --git a/Source/sys-clk-OC/README.md b/Source/sys-clk-OC/README.md index bae84764..e0743624 100644 --- a/Source/sys-clk-OC/README.md +++ b/Source/sys-clk-OC/README.md @@ -1,18 +1,114 @@ +# sys-clk-OC -# sys-clk-oc +Switch sysmodule allowing you to set cpu/gpu clocks according to the running application and docked state. -Switch sysmodule allowing you to set cpu/gpu/mem clocks according to the running application and docked state. -## Exclusive Features for sys-clk-oc +## Clock table (MHz) + +official means HOS official, unless specified + +### CPU clocks + +* 2397 → OC max for Mariko (with CPU UV) +* 2295 → OC max for Mariko +* 2193 +* 2091 → OC max for Erista +* 1963 → official and safe max for Mariko +* 1887 +* 1785 → official boost mode, safe max for Erista +* 1683 +* 1581 +* 1428 +* 1326 +* 1224 → sdev OC +* 1122 +* 1020 → official docked & handheld +* 918 +* 816 +* 714 +* 612 + +### GPU clocks + +* 1305 → N/A +* 1267 → official max for Mariko (Tegra X1+ official max) +* 1228 → recommended max for Hiopt +* 1152 +* 1075 → recommended max for SLT +* 998 → safe max for Mariko, max for Erista (Tegra X1 official max) +* 921 → safe max for Erista +* 844 +* 768 → official docked +* 691 +* 614 → recommended Mariko max for handheld +* 537 +* 460 → max handheld +* 384 → official handheld +* 307 → official handheld +* 230 +* 153 +* 76 → boost mode + +### MEM clocks + +From Hekate Minerva module [sys_sdrammtc.c](https://github.com/CTCaer/hekate/blob/master/modules/hekate_libsys_minerva/sys_sdrammtc.c#L65) + +- 3200 → l4t and closed version max for Mariko +- 2931 +- 2665 +- 2502 → max for Mariko +- 2400 +- 2361 → l4t and closed version max for Erista +- 2131 → JEDEC. max for Erista and official max for Mariko. lpddr4(x) official max +- 2099 +- 2064 +- 1996 → OCS mariko default +- 1932 +- 1894 +- 1862 → JEDEC. official max for Erista; OCS erista default +- 1795 +- 1728 +- 1600 → official docked & official boost mode +- 1331 → JEDEC. official handheld +- 1065 +- 800 +- 665 + +## Capping + +To protect the battery from excessive strain, clocks requested from config may be capped before applying, depending on your current profile: + +### Erista (Safe) +| | Handheld | Charging (USB) | Charging (Official) | Docked | +|:-------:|:--------:|:--------------:|:-------------------:|:------:| +| **MEM** | - | - | - | - | +| **CPU** | 1785 | 1785 | 1785 | 1785 | +| **GPU** | 460 | 768 | 921 | 921 | + + +### Erista (Unsafe allowed) +| | Handheld | Charging (USB) | Charging (Official) | Docked | +|:-------:|:--------:|:--------------:|:-------------------:|:------:| +| **MEM** | - | - | - | - | +| **CPU** | 1785 | 1785 | - | - | +| **GPU** | 460 | 768 | - | - | + + +### Mariko (Safe) +| | Handheld | Charging (USB) | Charging (Official) | Docked | +|:-------:|:--------:|:--------------:|:-------------------:|:------:| +| **MEM** | - | - | - | - | +| **CPU** | 1963 | 1963 | 1963 | 1963 | +| **GPU** | 768 | 921 | 998 | 998 | + + +### Mariko (Unsafe allowed) +| | Handheld | Charging (USB) | Charging (Official) | Docked | +|:-------:|:--------:|:--------------:|:-------------------:|:------:| +| **MEM** | - | - | - | - | +| **CPU** | 1963 | 1963 | - | - | +| **GPU** | 768 | 921 | - | - | -### Real Volts -voltage readings from devices: CPU/GPU/MEM/SOC -### Real Temps -temperature readings from thermal sensors: CPU/GPU/MEM/PLL/AO -### Cpu volt bug fix -fixes bug present in official driver -### Reversenx sync -automatically changes profile when reversenx state is changed ## Installation @@ -39,14 +135,10 @@ Copy the `atmosphere`, and `switch` folders at the root of your sdcard, overwrit `/config/sys-clk/context.csv` -* sys-clk manager app (accessible from the hbmenu) - - `/switch/sys-clk-manager.nro` - * sys-clk overlay (accessible from anywhere by invoking the [Tesla menu](https://gbatemp.net/threads/tesla-the-nintendo-switch-overlay-menu.557362/)) `/switch/.overlays/sys-clk-overlay.ovl` - + * sys-clk core sysmodule `/atmosphere/contents/00FF0000636C6BFF/exefs.nsp` @@ -54,7 +146,7 @@ Copy the `atmosphere`, and `switch` folders at the root of your sdcard, overwrit ## Config -Presets can be customized by adding them to the ini config file located at `/config/sys-clk/config.ini`, using the following template for each app +Presets can be customized by adding them to the ini config file located at `/config/sys-clk/config.ini`, using the following template for each app ``` [Application Title ID] @@ -79,7 +171,7 @@ handheld_mem= A list of games title id can be found in the [Switchbrew wiki](https://switchbrew.org/wiki/Title_list/Games). * Frequencies are expressed in mhz, and will be scaled to the nearest possible values, described in the clock table below. * If any key is omitted, value is empty or set to 0, it will be ignored, and stock clocks will apply. -* If charging, sys-clk will look for the frequencies in that order, picking the first found +* If charging, sys-clk will look for the frequencies in that order, picking the first found 1. Charger specific config (USB or Official) `handheld_charging_usb_X` or `handheld_charging_official_X` 2. Non specific charging config `handheld_charging_X` 3. Handheld config `handheld_X` @@ -87,7 +179,6 @@ A list of games title id can be found in the [Switchbrew wiki](https://switchbre ### Example 1: Zelda BOTW * Overclock CPU when docked or charging -* Overclock MEM to docked clocks when handheld Leads to a smoother framerate overall (ex: in the korok forest) @@ -106,79 +197,16 @@ handheld_mem=1600 [0100BA0003EEA000] handheld_cpu=816 handheld_gpu=153 -handheld_mem=800 ``` ### Advanced The `[values]` section allows you to alter timings in sys-clk, you should not need to edit any of these unless you know what you are doing. Possible values are: -| Key | Desc | Default | -|:-----------------------:|-------------------------------------------------------------------------------|:-------:| -|**temp_log_interval_ms** | Defines how often sys-clk logs temperatures, in milliseconds (`0` to disable) | 0 ms | -|**freq_log_interval_ms** | Defines how often sys-clk logs real freqs, in milliseconds (`0` to disable) | 0 ms | -|**power_log_interval_ms**| Defines how often sys-clk logs power usage, in milliseconds (`0` to disable) | 0 ms | -|**csv_write_interval_ms**| Defines how often sys-clk writes to the CSV, in milliseconds (`0` to disable) | 0 ms | -|**poll_interval_ms** | Defines how fast sys-clk checks and applies profiles, in milliseconds | 300 ms | -|**uncapped_clocks** | Removes clock cappings | OFF | -|**override_boost_mode** | Overrides official boost mode with user set profile clocks | OFF | -|**auto_cpu_boost** | Sets cpu clock to boost clock when core#3 load ≥ 90% | OFF | -|**sync_reversenx** | Overrides profile to match reversenx state | ON | - - -## Capping - -To protect the battery from excessive strain, clocks requested from config may be capped before applying, depending on your current profile: - -### Erista: -| | Handheld | Charging (USB) | Charging (Official) | Docked | -|:-----:|:--------:|:--------------:|:-------------------:|:------:| -|**MEM**| - | - | - | - | -|**CPU**| 1785 MHz | 1785 MHz | - | - | -|**GPU**| 460 MHz | 768 MHz | - | - | - -### Mariko: -| | Handheld | Charging (USB) | Charging (Official) | Docked | -|:-----:|:--------:|:--------------:|:-------------------:|:------:| -|**MEM**| - | - | - | - | -|**CPU**| 1963 MHz | 1963 MHz | - | - | -|**GPU**| 768 MHz | 921 MHz | - | - | - -## Clock table (MHz) - -### MEM clocks -* 1600 → official docked, boost mode, max clock -* 1331 → official handheld -* 1065 -* 800 -* 665 - -### CPU clocks -* 1785 → max clock, boost mode -* 1683 -* 1581 -* 1428 -* 1326 -* 1224 → sdev oc -* 1122 -* 1020 → official docked & handheld -* 918 -* 816 -* 714 -* 612 - -### GPU clocks -* 921 → max clock -* 844 -* 768 → official docked -* 691 -* 614 -* 537 -* 460 → max handheld -* 384 → official handheld -* 307 → official handheld -* 230 -* 153 -* 76 → boost mode - -**Notes:** +| Key | Desc | Default | +|:------------------------:|-------------------------------------------------------------------------------|:---------:| +|**allow_unsafe_freq** | Allow unsafe frequencies (CPU > 1963.5 MHz, GPU > 921.6 MHz) | OFF | +|**uncapped_clocks** | Remove CPU/GPU clock cappings | OFF | +|**temp_log_interval_ms** | Defines how often sys-clk log temperatures, in milliseconds (`0` to disable) | 0 ms | +|**csv_write_interval_ms** | Defines how often sys-clk writes to the CSV, in milliseconds (`0` to disable) | 0 ms | +|**poll_interval_ms** | Defines how fast sys-clk checks and applies profiles, in milliseconds | 500 ms | \ No newline at end of file diff --git a/pages/dist/index.html b/pages/dist/index.html index 62e77adc..843c9bcb 100644 --- a/pages/dist/index.html +++ b/pages/dist/index.html @@ -1 +1 @@ -Switch OC Suite | Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. Licensed under GPL v2.

Switch OC Suite

Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. Licensed under GPL v2.

README

🚨DISCLAIMER: THIS IS PROVIDED AS IS. USE AT YOUR OWN RISK!🚨

  • Overclocking in general will shorten the lifespan of some hardware components. YOU ARE RESPONSIBLE for any problem or potential damage if unsafe frequencies are ENABLED in sys-clk-OC. Issues like asking for bypassing limit will BE IGNORED OR CLOSED WITHOUT REPLY.
  • Due to HorizonOS design, instabilities from unsafe RAM clocks may cause filesystem corruption. Always make backup before enabling DRAM OC.

Features

For Erista variant (HAC-001)
  • CPU Overclock (Safe: 1785 MHz)
    • Unsafe
      • Due to the limit of board power draw or power IC
      • Unlockable frequencies up to 2091 MHz
      • See README for sys-clk-OC
  • DRAM Overclock (Safe: 1862.4 MHz)
For Mariko variant (HAC-001-01, HDH-001, HEG-001)
  • CPU / GPU Overclock (Safe: 1963 / 998 MHz)
    • Unsafe
      • Due to the limit of board power draw or power IC
      • Unlockable frequencies up to 2295 / 1267 MHz
      • See README for sys-clk-OC
  • DRAM Overclock (Safe: 1996.8 MHz)
Modded sys-clk and ReverseNX-RT
  • Auto CPU Boost
    • For faster game loading
    • Enable CPU Boost (1785 MHz) when CPU Core#3 (System Core) is stressed (mainly I/O operations).
    • Effective only when charger is connected or governor is enabled.
    • This feature is considered unsafe on Erista, especially when combined with high GPU frequency or with governor enabled.
  • CPU & GPU frequency governor (Experimental)
    • Adjust frequency based on load. Might decrease power draw but can introduce stutters. Can be turned off for specific titles.
  • Set charging current (100 mA - 2000 mA) and charging limit (20% - 100%)
    • Long-term use of charge limit may render the battery gauge inaccurate. Performing full cycles could help recalibration, or try battery_desync_fix_nx.
  • Global Profile
    • Designated a dummy title id 0xA111111111111111.
    • Priority: "Temp overrides" > "Application profile" > "Global profile" > "System default".
  • Sync ReverseNX Mode
    • No need to change clocks manually after toggling modes in ReverseNX (-RT and -Tool)
System Settings (Optional)See system_settings.md

Installation

  1. Download latest release.
  2. Grab x.x.x_loader.kip for your Atmosphere version, rename it to loader.kip and place it in /atmosphere/kips/.
  3. (optional) You can customize via online loader configurator
    DefaultsMarikoErista
    CPU OC2295 MHz Max2091 MHz Max
    CPU Boost1785 MHzN/A
    CPU Volt1235 mV Max1235 mV Max
    GPU OC1267 MHz Max998 MHz Max
    RAM OC1996 MHz1862 MHz
    RAM VoltDisabledDisabled
    RAM TimingAuto-AdjustedAuto-Adjusted
    CPU UVDisabledN/A
    GPU UVDisabledN/A
  4. Hekate-ipl bootloader Only (fss0) (Not required for AMS fusee)
    • At boot entry section in bootloader/hekate_ipl.ini, Add kip1=atmosphere/kips/loader.kip to any line that works.
  5. Install [sys-clk-oc]
    official [sys-clk] (2.0.0+) is compatible but not recommended (no bugfixes or additional features).
  6. (optional) Copy SdOut.zip for useful utilities.
How to build this project
  1. Grab necessary patches from the repo, then compile sys-clk, ReverseNX-RT and Atmosphere loader with devkitpro.
  2. Before compiling Atmosphere loader, run patch.py in Atmosphere/stratosphere/loader/source/ to insert oc module into loader sysmodule.
  3. When compilation is done, uncompress the kip to make it work with configurator: hactool -t kip1 Atmosphere/stratosphere/loader/out/nintendo_nx_arm64_armv8a/release/loader.kip --uncompress=./loader.kip

Frequently Asked Questions

How to enable unsafe frequencies in sys-clk-OC?
I would like to bypass limit enforced in sys-clk to improve handheld performance without charger connected.
  • Bypassing clock cappings will be bad for battery.
  • See the end of README in sys-clk-OC. Place this line uncapped_clocks=1 under [values] section in /config/sys-clk/config.ini

Configurator

Configure frequencies and voltages to suit your hardware and preferences.

Build with Pico. All trademarks, logos and brand names are the property of their respective owners, used for identification purposes only.
+Switch OC Suite | Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. Licensed under GPL v2.

Switch OC Suite

Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. Licensed under GPL v2.

README

🚨DISCLAIMER: THIS IS PROVIDED AS IS. USE AT YOUR OWN RISK!🚨

  • Overclocking in general will shorten the lifespan of some hardware components. YOU ARE RESPONSIBLE for any problem or potential damage if unsafe frequencies are ENABLED in sys-clk-OC. Issues like asking for bypassing limit will BE IGNORED OR CLOSED WITHOUT REPLY.
  • Due to HorizonOS design, instabilities from unsafe RAM clocks may cause filesystem corruption. Always make backup before enabling DRAM OC.

Features

For Erista variant (HAC-001)
  • CPU Overclock (Safe: 1785 MHz)
    • Unsafe
      • Due to the limit of board power draw or power IC
      • Unlockable frequencies up to 2091 MHz
      • See README for sys-clk-OC
  • DRAM Overclock (Safe: 1862.4 MHz)
For Mariko variant (HAC-001-01, HDH-001, HEG-001)
  • CPU / GPU Overclock (Safe: 1963 / 998 MHz)
    • Unsafe
      • Due to the limit of board power draw or power IC
      • Unlockable frequencies up to 2295 / 1267 MHz
      • See README for sys-clk-OC
  • DRAM Overclock (Safe: 1996.8 MHz)
Modded sys-clk and ReverseNX-RT
  • Auto CPU Boost
    • For faster game loading
    • Enable CPU Boost (1785 MHz) when CPU Core#3 (System Core) is stressed (mainly I/O operations).
    • Effective only when charger is connected or governor is enabled.
    • This feature is considered unsafe on Erista, especially when combined with high GPU frequency or with governor enabled.
  • CPU & GPU frequency governor (Experimental)
    • Adjust frequency based on load. Might decrease power draw but can introduce stutters. Can be turned off for specific titles.
  • Set charging current (100 mA - 2000 mA) and charging limit (20% - 100%)
    • Long-term use of charge limit may render the battery gauge inaccurate. Performing full cycles could help recalibration, or try battery_desync_fix_nx.
  • Global Profile
    • Designated a dummy title id 0xA111111111111111.
    • Priority: "Temp overrides" > "Application profile" > "Global profile" > "System default".
  • Sync ReverseNX Mode
    • No need to change clocks manually after toggling modes in ReverseNX (-RT and -Tool)
System Settings (Optional)See system_settings.md

Installation

  1. Download latest release.
  2. Grab x.x.x_loader.kip for your Atmosphere version, rename it to loader.kip and place it in /atmosphere/kips/.
  3. (optional) You can customize via online loader configurator
    DefaultsMarikoErista
    CPU OC2295 MHz Max2091 MHz Max
    CPU Boost1785 MHzN/A
    CPU Volt1235 mV Max1235 mV Max
    GPU OC1267 MHz Max998 MHz Max
    RAM OC1996 MHz1862 MHz
    RAM VoltDisabledDisabled
    RAM TimingAuto-AdjustedAuto-Adjusted
    CPU UVDisabledN/A
    GPU UVDisabledN/A
  4. Hekate-ipl bootloader Only (fss0) (Not required for AMS fusee)
    • At boot entry section in bootloader/hekate_ipl.ini, Add kip1=atmosphere/kips/loader.kip to any line that works.
  5. Install sys-clk-oc, or install official sys-clk (2.0.0+) instead.
  6. (optional) Copy SdOut.zip for useful utilities.
How to build this project
  1. Grab necessary patches from the repo, then compile sys-clk, ReverseNX-RT and Atmosphere loader with devkitpro.
  2. Before compiling Atmosphere loader, run patch.py in Atmosphere/stratosphere/loader/source/ to insert oc module into loader sysmodule.
  3. When compilation is done, uncompress the kip to make it work with configurator: hactool -t kip1 Atmosphere/stratosphere/loader/out/nintendo_nx_arm64_armv8a/release/loader.kip --uncompress=./loader.kip

Frequently Asked Questions

How to enable unsafe frequencies in sys-clk-OC?
I would like to bypass limit enforced in sys-clk to improve handheld performance without charger connected.
  • Bypassing clock cappings will be bad for battery.
  • See the end of README in sys-clk-OC. Place this line uncapped_clocks=1 under [values] section in /config/sys-clk/config.ini

Configurator

Configure frequencies and voltages to suit your hardware and preferences.

Build with Pico. All trademarks, logos and brand names are the property of their respective owners, used for identification purposes only.
diff --git a/pages/dist/main.js b/pages/dist/main.js index 9bf11a93..81235b26 100644 --- a/pages/dist/main.js +++ b/pages/dist/main.js @@ -1 +1 @@ -var __awaiter=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))((function(a,r){function s(e){try{o(n.next(e))}catch(e){r(e)}}function l(e){try{o(n.throw(e))}catch(e){r(e)}}function o(e){var t;e.done?a(e.value):(t=e.value,t instanceof i?t:new i((function(e){e(t)}))).then(s,l)}o((n=n.apply(e,t||[])).next())}))};const CUST_REV_ADV=11;var CustPlatform;!function(e){e[e.Undefined=0]="Undefined",e[e.Erista=1]="Erista",e[e.Mariko=2]="Mariko",e[e.All=3]="All"}(CustPlatform||(CustPlatform={}));class CustEntry{constructor(e,t,i,n,a,r,s=[0,1e6],l=1,o=!0){this.id=e,this.name=t,this.platform=i,this.size=n,this.desc=a,this.defval=r,this.step=l,this.zeroable=o,this.min=s[0],this.max=s[1]}validate(){let e=new ErrorToolTip(this.id).clear();return Number.isNaN(this.value)||void 0===this.value?(e.setMsg("Invalid value: Not a number").show(),!1):!(!this.zeroable||0!=this.value)||(this.valuethis.max?(e.setMsg(`Expected range: [${this.min}, ${this.max}], got ${this.value}.`).show(),!1):this.value%this.step==0||(e.setMsg(`${this.value} % ${this.step} ≠ 0`).show(),!1))}getInputElement(){return document.getElementById(this.id)}updateValueFromElement(){var e;this.value=Number(null===(e=this.getInputElement())||void 0===e?void 0:e.value)}isAvailableFor(e){return e===CustPlatform.Undefined||this.platform===e||this.platform===CustPlatform.All}createElement(){let e=this.getInputElement();if(!e){let t=document.createElement("div");t.classList.add("grid","cust-element"),e=document.createElement("input"),e.min=String(this.zeroable?0:this.min),e.max=String(this.max),e.id=this.id,e.type="number",e.step=String(this.step);let i=document.createElement("label");i.setAttribute("for",this.id),i.innerHTML=this.name,i.appendChild(e),t.appendChild(i);let n=document.createElement("blockquote");n.innerHTML="
    "+this.desc.map((e=>`
  • ${e}
  • `)).join("")+"
",n.setAttribute("for",this.id),t.appendChild(n),document.getElementById("config-list-basic").appendChild(t),new ErrorToolTip(this.id).addChangeListener()}e.value=String(this.value)}setElementValue(){this.getInputElement().value=String(this.value)}setElementDefaultValue(){this.getInputElement().value=String(this.defval)}removeElement(){let e=this.getInputElement();e&&e.parentElement.parentElement.remove()}showElement(){let e=this.getInputElement();e&&e.parentElement.parentElement.style.removeProperty("display")}hideElement(){let e=this.getInputElement();e&&e.parentElement.parentElement.style.setProperty("display","none")}}class AdvEntry extends CustEntry{createElement(){let e=this.getInputElement();if(!e){let t=document.createElement("div");t.classList.add("grid","cust-element"),e=document.createElement("input"),e.min=String(this.zeroable?0:this.min),e.max=String(this.max),e.id=this.id,e.type="number",e.step=String(this.step);let i=document.createElement("label");i.setAttribute("for",this.id),i.innerHTML=this.name,i.appendChild(e),t.appendChild(i);let n=document.createElement("blockquote");n.innerHTML="
    "+this.desc.map((e=>`
  • ${e}
  • `)).join("")+"
",n.setAttribute("for",this.id),t.appendChild(n),document.getElementById("config-list-advanced").appendChild(t),new ErrorToolTip(this.id).addChangeListener()}e.value=String(this.value)}}class GpuEntry extends CustEntry{constructor(e,t,i=CustPlatform.Mariko,n=4,a=["range: 610 ≤ x ≤ 1000"],r=610,s=[610,1e3],l=5,o=!1){super(e,t,i,n,a,r,s,l,o),this.id=e,this.name=t,this.platform=i,this.size=n,this.desc=a,this.defval=r,this.step=l,this.zeroable=o}createElement(){let e=this.getInputElement();if(!e){let t=document.createElement("div");t.classList.add("grid","cust-element"),e=document.createElement("input"),e.min=String(this.zeroable?0:this.min),e.max=String(this.max),e.id=this.id,e.type="number",e.step=String(this.step);let i=document.createElement("label");i.setAttribute("for",this.id),i.innerHTML=this.name,i.appendChild(e),t.appendChild(i);let n=document.createElement("blockquote");n.innerHTML="
    "+this.desc.map((e=>`
  • ${e}
  • `)).join("")+"
",n.setAttribute("for",this.id),t.appendChild(n),document.getElementById("config-list-gpu").appendChild(t),new ErrorToolTip(this.id).addChangeListener()}e.value=String(this.value)}}var 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 wihout 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."],1785e3,[102e4,3e6],1,!1),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,[11e5,125e4],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,[16e5,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,[16e5,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,[55e4,65e4],5e3),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 Configuation 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)],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)],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")];class ErrorToolTip{constructor(e,t){this.id=e,this.msg=t,this.id=e,this.element=document.getElementById(e),t&&this.setMsg(t)}setMsg(e){return this.msg=e,this}show(){var e,t,i,n,a,r;return null===(e=this.element)||void 0===e||e.setAttribute("aria-invalid","true"),this.msg&&(null===(t=this.element)||void 0===t||t.setAttribute("title",this.msg),null===(n=null===(i=this.element)||void 0===i?void 0:i.parentElement)||void 0===n||n.setAttribute("data-tooltip",this.msg),null===(r=null===(a=this.element)||void 0===a?void 0:a.parentElement)||void 0===r||r.setAttribute("data-placement","top")),this}clear(){var e,t,i,n,a,r;return null===(e=this.element)||void 0===e||e.removeAttribute("aria-invalid"),null===(t=this.element)||void 0===t||t.removeAttribute("title"),null===(n=null===(i=this.element)||void 0===i?void 0:i.parentElement)||void 0===n||n.removeAttribute("data-tooltip"),null===(r=null===(a=this.element)||void 0===a?void 0:a.parentElement)||void 0===r||r.removeAttribute("data-placement"),this}addChangeListener(){var e;null===(e=this.element)||void 0===e||e.addEventListener("change",(e=>{let t=CustTable.filter((e=>e.id===this.id))[0];t.value=Number(this.element.value),t.validate()}))}}class CustStorage{constructor(){this.storage={},this.key="last_saved"}updateFromTable(){let e=e=>{var t;if(e.updateValueFromElement(),!e.validate())throw null===(t=e.getInputElement())||void 0===t||t.focus(),new Error(`Invalid ${e.name}`)};CustTable.forEach(e),AdvTable.forEach(e),GpuTable.forEach(e),this.storage={};let t=Object.fromEntries(CustTable.map((e=>[e.id,e.value])));Object.keys(t).forEach((e=>this.storage[e]=t[e])),t=Object.fromEntries(AdvTable.map((e=>[e.id,e.value]))),Object.keys(t).forEach((e=>this.storage[e]=t[e]))}setTable(){let e=Object.keys(this.storage);e.forEach((e=>CustTable.filter((t=>t.id==e))[0].value=this.storage[e])),e.forEach((e=>AdvTable.filter((t=>t.id==e))[0].value=this.storage[e])),CustTable.filter((t=>!e.includes(t.id))).forEach((e=>e.value=e.defval)),AdvTable.filter((t=>!e.includes(t.id))).forEach((e=>e.value=e.defval)),CustTable.forEach((e=>{var t;if(!e.validate())throw null===(t=e.getInputElement())||void 0===t||t.focus(),new Error(`Invalid ${e.name}`);e.setElementValue()})),AdvTable.forEach((e=>{var t;if(!e.validate())throw null===(t=e.getInputElement())||void 0===t||t.focus(),new Error(`Invalid ${e.name}`);e.setElementValue()})),GpuTable.forEach((e=>{var t;if(!e.validate())throw null===(t=e.getInputElement())||void 0===t||t.focus(),new Error(`Invalid ${e.name}`);e.setElementValue()}))}save(){localStorage.setItem(this.key,JSON.stringify(this.storage))}load(){let e=localStorage.getItem(this.key);if(!e)return null;let t=JSON.parse(e),i=CustTable.map((e=>e.id)),n=Object.keys(t).filter((e=>!i.includes(e)));return n.length&&console.log(`Ignored: ${n}`),Object.keys(t).filter((e=>i.includes(e))).forEach((e=>this.storage[e]=t[e])),i=AdvTable.map((e=>e.id)),n=Object.keys(t).filter((e=>!i.includes(e))),n.length&&console.log(`Ignored: ${n}`),Object.keys(t).filter((e=>i.includes(e))).forEach((e=>this.storage[e]=t[e])),this.storage}}class Cust{constructor(){this.storage=new CustStorage,this.magic=1414747459,this.magicLen=4,this.mapper={2:{get:e=>this.view.getUint16(e,!0),set:(e,t)=>this.view.setUint16(e,t,!0)},4:{get:e=>this.view.getUint32(e,!0),set:(e,t)=>this.view.setUint32(e,t,!0)}}}findMagicOffset(){this.view=new DataView(this.buffer);for(let e=0;e{var t,i;if(!e.offset)throw null===(t=e.getInputElement())||void 0===t||t.focus(),new Error(`Failed to get offset for ${e.name}`);let n=this.mapper[e.size];if(!n)throw null===(i=e.getInputElement())||void 0===i||i.focus(),new Error(`Unknown size at ${e.name}`);n.set(e.offset,e.value)};CustTable.forEach(e),AdvTable.forEach(e),GpuTable.forEach(e),this.storage.save();let t=document.createElement("a");t.href=window.URL.createObjectURL(new Blob([this.buffer],{type:"application/octet-stream"})),t.download="loader.kip",t.click(),this.toggleLoadLastSavedBtn(!0)}removeHTMLForm(){CustTable.forEach((e=>e.removeElement()))}toggleLoadLastSavedBtn(e){let t=document.getElementById("load_saved");e?(t.addEventListener("click",(()=>{this.storage.load()&&this.storage.setTable()})),t.style.removeProperty("display"),t.removeAttribute("disabled")):t.style.setProperty("display","none")}createHTMLForm(){var e,t;CustTable.forEach((e=>e.createElement()));let i=document.createElement("p");i.innerHTML="Advanced configuration",null===(e=document.getElementById("config-list-advanced"))||void 0===e||e.appendChild(i);let n=document.createElement("p");n.innerHTML="Gpu Volt configuration",null===(t=document.getElementById("config-list-gpu"))||void 0===t||t.appendChild(n),AdvTable.forEach((e=>e.createElement())),GpuTable.forEach((e=>e.createElement()));let a=document.getElementById("load_default");a.removeAttribute("disabled"),a.addEventListener("click",(()=>{CustTable.forEach((e=>e.setElementDefaultValue()))})),this.toggleLoadLastSavedBtn(null!==this.storage.load());let r=document.getElementById("save");r.removeAttribute("disabled"),r.addEventListener("click",(()=>{try{this.save()}catch(e){console.error(e),alert(e)}}))}initCustTabs(){const e=Array.from(document.querySelectorAll('nav[role="tablist"] > button'));e.forEach((t=>{t.removeAttribute("disabled");let i=Number(t.getAttribute("data-platform"));t.addEventListener("click",(n=>{const a=["outline"];t.classList.remove(...a),e.filter((e=>e!=t)).forEach((e=>e.classList.add(...a))),CustTable.forEach((e=>{e.isAvailableFor(i)?e.showElement():e.hideElement()}))}))}))}parse(){let e=this.beginOffset+this.magicLen;if(this.rev=this.mapper[4].get(e),11!=this.rev)throw new Error(`Unsupported custRev, expected: 11, got ${this.rev}`);e+=4,document.getElementById("cust_rev").innerHTML=`Cust v${this.rev} is loaded.`;let t=t=>{var i;t.offset=e;let n=this.mapper[t.size];if(!n)throw null===(i=t.getInputElement())||void 0===i||i.focus(),new Error(`Unknown size at ${t}`);t.value=n.get(e),e+=t.size,t.validate()};CustTable.forEach(t),AdvTable.forEach(t),GpuTable.forEach(t)}load(e){try{this.buffer=e,this.findMagicOffset(),this.removeHTMLForm(),this.parse(),this.initCustTabs(),this.createHTMLForm()}catch(e){console.error(e),alert(e)}}}class ReleaseAsset{constructor(e){this.downloadUrl=e.browser_download_url,this.updatedAt=e.updated_at}}class ReleaseInfo{constructor(){this.ocLatestApi="https://api.github.com/repos/hanai3Bi/Switch-OC-Suite/releases/latest"}load(){return __awaiter(this,void 0,void 0,(function*(){try{this.parseOcResponse(yield this.responseFromApi(this.ocLatestApi).catch())}catch(e){console.error(e),alert(e)}}))}responseFromApi(e){return __awaiter(this,void 0,void 0,(function*(){const t=yield fetch(e,{method:"GET",headers:{Accept:"application/json"}});if(t.ok)return yield t.json();throw new Error(`Failed to connect to "${e}": ${t.status}`)}))}parseOcResponse(e){this.ocVer=e.tag_name,this.loaderKipAsset=new ReleaseAsset(e.assets.filter((e=>e.name.endsWith("loader.kip")))[0]),this.sdOutZipAsset=new ReleaseAsset(e.assets.filter((e=>e.name.endsWith("SdOut.zip")))[0]),this.sysclkOCAsset=new ReleaseAsset(e.assets.filter((e=>e.name.endsWith("sys-clk-oc.zip")))[0])}}class DownloadSection{constructor(){this.element=document.getElementById("download_btn_grid")}load(){return __awaiter(this,void 0,void 0,(function*(){for(;!this.isVisible();)yield new Promise((e=>setTimeout(e,1e3)));const e=new ReleaseInfo;yield e.load(),this.update("loader_kip_btn",`loader.kip ${e.ocVer}
${e.loaderKipAsset.updatedAt}`,e.loaderKipAsset.downloadUrl),this.update("sdout_zip_btn",`SdOut.zip ${e.ocVer}
${e.sdOutZipAsset.updatedAt}`,e.sdOutZipAsset.downloadUrl),this.update("ams_btn",`sys-clk-oc ${e.ocVer}
${e.sysclkOCAsset.updatedAt}`,e.sysclkOCAsset.downloadUrl)}))}isVisible(){let e=this.element.getBoundingClientRect();return e.top>0&&e.left>0&&e.bottom-e.height<(window.innerHeight||document.documentElement.clientHeight)&&e.right-e.width<(window.innerWidth||document.documentElement.clientWidth)}update(e,t,i){let n=document.getElementById(e);n.innerHTML=t,n.removeAttribute("aria-busy"),n.setAttribute("href",i)}}const fileInput=document.getElementById("file");fileInput.addEventListener("change",(e=>{var t=new Cust;if(!e.target||!e.target.files)return;let i=new FileReader;i.readAsArrayBuffer(e.target.files[0]),i.onloadend=e=>{e.target.readyState==FileReader.DONE&&t.load(e.target.result)}})),addEventListener("DOMContentLoaded",(e=>__awaiter(this,void 0,void 0,(function*(){yield(new DownloadSection).load()})))); +var __awaiter=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))((function(a,r){function s(e){try{o(n.next(e))}catch(e){r(e)}}function l(e){try{o(n.throw(e))}catch(e){r(e)}}function o(e){var t;e.done?a(e.value):(t=e.value,t instanceof i?t:new i((function(e){e(t)}))).then(s,l)}o((n=n.apply(e,t||[])).next())}))};const CUST_REV_ADV=11;var CustPlatform;!function(e){e[e.Undefined=0]="Undefined",e[e.Erista=1]="Erista",e[e.Mariko=2]="Mariko",e[e.All=3]="All"}(CustPlatform||(CustPlatform={}));class CustEntry{constructor(e,t,i,n,a,r,s=[0,1e6],l=1,o=!0){this.id=e,this.name=t,this.platform=i,this.size=n,this.desc=a,this.defval=r,this.step=l,this.zeroable=o,this.min=s[0],this.max=s[1]}validate(){let e=new ErrorToolTip(this.id).clear();return Number.isNaN(this.value)||void 0===this.value?(e.setMsg("Invalid value: Not a number").show(),!1):!(!this.zeroable||0!=this.value)||(this.valuethis.max?(e.setMsg(`Expected range: [${this.min}, ${this.max}], got ${this.value}.`).show(),!1):this.value%this.step==0||(e.setMsg(`${this.value} % ${this.step} ≠ 0`).show(),!1))}getInputElement(){return document.getElementById(this.id)}updateValueFromElement(){var e;this.value=Number(null===(e=this.getInputElement())||void 0===e?void 0:e.value)}isAvailableFor(e){return e===CustPlatform.Undefined||this.platform===e||this.platform===CustPlatform.All}createElement(){let e=this.getInputElement();if(!e){let t=document.createElement("div");t.classList.add("grid","cust-element"),e=document.createElement("input"),e.min=String(this.zeroable?0:this.min),e.max=String(this.max),e.id=this.id,e.type="number",e.step=String(this.step);let i=document.createElement("label");i.setAttribute("for",this.id),i.innerHTML=this.name,i.appendChild(e),t.appendChild(i);let n=document.createElement("blockquote");n.innerHTML="
    "+this.desc.map((e=>`
  • ${e}
  • `)).join("")+"
",n.setAttribute("for",this.id),t.appendChild(n),document.getElementById("config-list-basic").appendChild(t),new ErrorToolTip(this.id).addChangeListener()}e.value=String(this.value)}setElementValue(){this.getInputElement().value=String(this.value)}setElementDefaultValue(){this.getInputElement().value=String(this.defval)}removeElement(){let e=this.getInputElement();e&&e.parentElement.parentElement.remove()}showElement(){let e=this.getInputElement();e&&e.parentElement.parentElement.style.removeProperty("display")}hideElement(){let e=this.getInputElement();e&&e.parentElement.parentElement.style.setProperty("display","none")}}class AdvEntry extends CustEntry{createElement(){let e=this.getInputElement();if(!e){let t=document.createElement("div");t.classList.add("grid","cust-element"),e=document.createElement("input"),e.min=String(this.zeroable?0:this.min),e.max=String(this.max),e.id=this.id,e.type="number",e.step=String(this.step);let i=document.createElement("label");i.setAttribute("for",this.id),i.innerHTML=this.name,i.appendChild(e),t.appendChild(i);let n=document.createElement("blockquote");n.innerHTML="
    "+this.desc.map((e=>`
  • ${e}
  • `)).join("")+"
",n.setAttribute("for",this.id),t.appendChild(n),document.getElementById("config-list-advanced").appendChild(t),new ErrorToolTip(this.id).addChangeListener()}e.value=String(this.value)}}class GpuEntry extends CustEntry{constructor(e,t,i=CustPlatform.Mariko,n=4,a=["range: 610 ≤ x ≤ 1000"],r=610,s=[610,1e3],l=5,o=!1){super(e,t,i,n,a,r,s,l,o),this.id=e,this.name=t,this.platform=i,this.size=n,this.desc=a,this.defval=r,this.step=l,this.zeroable=o}createElement(){let e=this.getInputElement();if(!e){let t=document.createElement("div");t.classList.add("grid","cust-element"),e=document.createElement("input"),e.min=String(this.zeroable?0:this.min),e.max=String(this.max),e.id=this.id,e.type="number",e.step=String(this.step);let i=document.createElement("label");i.setAttribute("for",this.id),i.innerHTML=this.name,i.appendChild(e),t.appendChild(i);let n=document.createElement("blockquote");n.innerHTML="
    "+this.desc.map((e=>`
  • ${e}
  • `)).join("")+"
",n.setAttribute("for",this.id),t.appendChild(n),document.getElementById("config-list-gpu").appendChild(t),new ErrorToolTip(this.id).addChangeListener()}e.value=String(this.value)}}var 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 wihout 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."],1785e3,[102e4,3e6],1,!1),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,[11e5,125e4],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,[16e5,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,[16e5,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,[55e4,65e4],5e3),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 Configuation 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","Acceptable range: 0 ~ 100"],0,[0,100],1)],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)],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")];class ErrorToolTip{constructor(e,t){this.id=e,this.msg=t,this.id=e,this.element=document.getElementById(e),t&&this.setMsg(t)}setMsg(e){return this.msg=e,this}show(){var e,t,i,n,a,r;return null===(e=this.element)||void 0===e||e.setAttribute("aria-invalid","true"),this.msg&&(null===(t=this.element)||void 0===t||t.setAttribute("title",this.msg),null===(n=null===(i=this.element)||void 0===i?void 0:i.parentElement)||void 0===n||n.setAttribute("data-tooltip",this.msg),null===(r=null===(a=this.element)||void 0===a?void 0:a.parentElement)||void 0===r||r.setAttribute("data-placement","top")),this}clear(){var e,t,i,n,a,r;return null===(e=this.element)||void 0===e||e.removeAttribute("aria-invalid"),null===(t=this.element)||void 0===t||t.removeAttribute("title"),null===(n=null===(i=this.element)||void 0===i?void 0:i.parentElement)||void 0===n||n.removeAttribute("data-tooltip"),null===(r=null===(a=this.element)||void 0===a?void 0:a.parentElement)||void 0===r||r.removeAttribute("data-placement"),this}addChangeListener(){var e;null===(e=this.element)||void 0===e||e.addEventListener("change",(e=>{let t=CustTable.filter((e=>e.id===this.id))[0];t.value=Number(this.element.value),t.validate()}))}}class CustStorage{constructor(){this.storage={},this.key="last_saved"}updateFromTable(){let e=e=>{var t;if(e.updateValueFromElement(),!e.validate())throw null===(t=e.getInputElement())||void 0===t||t.focus(),new Error(`Invalid ${e.name}`)};CustTable.forEach(e),AdvTable.forEach(e),GpuTable.forEach(e),this.storage={};let t=Object.fromEntries(CustTable.map((e=>[e.id,e.value])));Object.keys(t).forEach((e=>this.storage[e]=t[e])),t=Object.fromEntries(AdvTable.map((e=>[e.id,e.value]))),Object.keys(t).forEach((e=>this.storage[e]=t[e]))}setTable(){let e=Object.keys(this.storage);e.forEach((e=>CustTable.filter((t=>t.id==e))[0].value=this.storage[e])),e.forEach((e=>AdvTable.filter((t=>t.id==e))[0].value=this.storage[e])),CustTable.filter((t=>!e.includes(t.id))).forEach((e=>e.value=e.defval)),AdvTable.filter((t=>!e.includes(t.id))).forEach((e=>e.value=e.defval)),CustTable.forEach((e=>{var t;if(!e.validate())throw null===(t=e.getInputElement())||void 0===t||t.focus(),new Error(`Invalid ${e.name}`);e.setElementValue()})),AdvTable.forEach((e=>{var t;if(!e.validate())throw null===(t=e.getInputElement())||void 0===t||t.focus(),new Error(`Invalid ${e.name}`);e.setElementValue()})),GpuTable.forEach((e=>{var t;if(!e.validate())throw null===(t=e.getInputElement())||void 0===t||t.focus(),new Error(`Invalid ${e.name}`);e.setElementValue()}))}save(){localStorage.setItem(this.key,JSON.stringify(this.storage))}load(){let e=localStorage.getItem(this.key);if(!e)return null;let t=JSON.parse(e),i=CustTable.map((e=>e.id)),n=Object.keys(t).filter((e=>!i.includes(e)));return n.length&&console.log(`Ignored: ${n}`),Object.keys(t).filter((e=>i.includes(e))).forEach((e=>this.storage[e]=t[e])),i=AdvTable.map((e=>e.id)),n=Object.keys(t).filter((e=>!i.includes(e))),n.length&&console.log(`Ignored: ${n}`),Object.keys(t).filter((e=>i.includes(e))).forEach((e=>this.storage[e]=t[e])),this.storage}}class Cust{constructor(){this.storage=new CustStorage,this.magic=1414747459,this.magicLen=4,this.mapper={2:{get:e=>this.view.getUint16(e,!0),set:(e,t)=>this.view.setUint16(e,t,!0)},4:{get:e=>this.view.getUint32(e,!0),set:(e,t)=>this.view.setUint32(e,t,!0)}}}findMagicOffset(){this.view=new DataView(this.buffer);for(let e=0;e{var t,i;if(!e.offset)throw null===(t=e.getInputElement())||void 0===t||t.focus(),new Error(`Failed to get offset for ${e.name}`);let n=this.mapper[e.size];if(!n)throw null===(i=e.getInputElement())||void 0===i||i.focus(),new Error(`Unknown size at ${e.name}`);n.set(e.offset,e.value)};CustTable.forEach(e),AdvTable.forEach(e),GpuTable.forEach(e),this.storage.save();let t=document.createElement("a");t.href=window.URL.createObjectURL(new Blob([this.buffer],{type:"application/octet-stream"})),t.download="loader.kip",t.click(),this.toggleLoadLastSavedBtn(!0)}removeHTMLForm(){CustTable.forEach((e=>e.removeElement()))}toggleLoadLastSavedBtn(e){let t=document.getElementById("load_saved");e?(t.addEventListener("click",(()=>{this.storage.load()&&this.storage.setTable()})),t.style.removeProperty("display"),t.removeAttribute("disabled")):t.style.setProperty("display","none")}createHTMLForm(){var e,t;CustTable.forEach((e=>e.createElement()));let i=document.createElement("p");i.innerHTML="Advanced configuration",null===(e=document.getElementById("config-list-advanced"))||void 0===e||e.appendChild(i);let n=document.createElement("p");n.innerHTML="Gpu Volt configuration",null===(t=document.getElementById("config-list-gpu"))||void 0===t||t.appendChild(n),AdvTable.forEach((e=>e.createElement())),GpuTable.forEach((e=>e.createElement()));let a=document.getElementById("load_default");a.removeAttribute("disabled"),a.addEventListener("click",(()=>{CustTable.forEach((e=>e.setElementDefaultValue()))})),this.toggleLoadLastSavedBtn(null!==this.storage.load());let r=document.getElementById("save");r.removeAttribute("disabled"),r.addEventListener("click",(()=>{try{this.save()}catch(e){console.error(e),alert(e)}}))}initCustTabs(){const e=Array.from(document.querySelectorAll('nav[role="tablist"] > button'));e.forEach((t=>{t.removeAttribute("disabled");let i=Number(t.getAttribute("data-platform"));t.addEventListener("click",(n=>{const a=["outline"];t.classList.remove(...a),e.filter((e=>e!=t)).forEach((e=>e.classList.add(...a))),CustTable.forEach((e=>{e.isAvailableFor(i)?e.showElement():e.hideElement()}))}))}))}parse(){let e=this.beginOffset+this.magicLen;if(this.rev=this.mapper[4].get(e),11!=this.rev)throw new Error(`Unsupported custRev, expected: 11, got ${this.rev}`);e+=4,document.getElementById("cust_rev").innerHTML=`Cust v${this.rev} is loaded.`;let t=t=>{var i;t.offset=e;let n=this.mapper[t.size];if(!n)throw null===(i=t.getInputElement())||void 0===i||i.focus(),new Error(`Unknown size at ${t}`);t.value=n.get(e),e+=t.size,t.validate()};CustTable.forEach(t),AdvTable.forEach(t),GpuTable.forEach(t)}load(e){try{this.buffer=e,this.findMagicOffset(),this.removeHTMLForm(),this.parse(),this.initCustTabs(),this.createHTMLForm()}catch(e){console.error(e),alert(e)}}}class ReleaseAsset{constructor(e){this.downloadUrl=e.browser_download_url,this.updatedAt=e.updated_at}}class ReleaseInfo{constructor(){this.ocLatestApi="https://api.github.com/repos/hanai3Bi/Switch-OC-Suite/releases/latest"}load(){return __awaiter(this,void 0,void 0,(function*(){try{this.parseOcResponse(yield this.responseFromApi(this.ocLatestApi).catch())}catch(e){console.error(e),alert(e)}}))}responseFromApi(e){return __awaiter(this,void 0,void 0,(function*(){const t=yield fetch(e,{method:"GET",headers:{Accept:"application/json"}});if(t.ok)return yield t.json();throw new Error(`Failed to connect to "${e}": ${t.status}`)}))}parseOcResponse(e){this.ocVer=e.tag_name,this.loaderKipAsset=new ReleaseAsset(e.assets.filter((e=>e.name.endsWith("loader.kip")))[0]),this.sdOutZipAsset=new ReleaseAsset(e.assets.filter((e=>e.name.endsWith("SdOut.zip")))[0]),this.sysclkOCAsset=new ReleaseAsset(e.assets.filter((e=>e.name.endsWith("sys-clk-oc.zip")))[0])}}class DownloadSection{constructor(){this.element=document.getElementById("download_btn_grid")}load(){return __awaiter(this,void 0,void 0,(function*(){for(;!this.isVisible();)yield new Promise((e=>setTimeout(e,1e3)));const e=new ReleaseInfo;yield e.load(),this.update("loader_kip_btn",`loader.kip ${e.ocVer}
${e.loaderKipAsset.updatedAt}`,e.loaderKipAsset.downloadUrl),this.update("sdout_zip_btn",`SdOut.zip ${e.ocVer}
${e.sdOutZipAsset.updatedAt}`,e.sdOutZipAsset.downloadUrl),this.update("ams_btn",`sys-clk-oc ${e.ocVer}
${e.sysclkOCAsset.updatedAt}`,e.sysclkOCAsset.downloadUrl)}))}isVisible(){let e=this.element.getBoundingClientRect();return e.top>0&&e.left>0&&e.bottom-e.height<(window.innerHeight||document.documentElement.clientHeight)&&e.right-e.width<(window.innerWidth||document.documentElement.clientWidth)}update(e,t,i){let n=document.getElementById(e);n.innerHTML=t,n.removeAttribute("aria-busy"),n.setAttribute("href",i)}}const fileInput=document.getElementById("file");fileInput.addEventListener("change",(e=>{var t=new Cust;if(!e.target||!e.target.files)return;let i=new FileReader;i.readAsArrayBuffer(e.target.files[0]),i.onloadend=e=>{e.target.readyState==FileReader.DONE&&t.load(e.target.result)}})),addEventListener("DOMContentLoaded",(e=>__awaiter(this,void 0,void 0,(function*(){yield(new DownloadSection).load()})))); diff --git a/pages/src/index.html b/pages/src/index.html index 3d9a27d3..598b5492 100644 --- a/pages/src/index.html +++ b/pages/src/index.html @@ -297,7 +297,7 @@ Add kip1=atmosphere/kips/loader.kip to any line that works. -
  • Install [sys-clk-oc]
    official [sys-clk] (2.0.0+) is compatible but not recommended (no bugfixes or additional features). +
  • Install sys-clk-oc, or install official sys-clk (2.0.0+) instead.
  • (optional) Copy SdOut.zip for useful utilities.
  • diff --git a/pages/src/main.ts b/pages/src/main.ts index 7a562459..d293f9a0 100644 --- a/pages/src/main.ts +++ b/pages/src/main.ts @@ -341,7 +341,6 @@ var CustTable: Array = [ 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],