diff --git a/Source/Horizon-OC-Monitor/source/Utils.hpp b/Source/Horizon-OC-Monitor/source/Utils.hpp index f583b733..f24c8a39 100644 --- a/Source/Horizon-OC-Monitor/source/Utils.hpp +++ b/Source/Horizon-OC-Monitor/source/Utils.hpp @@ -208,8 +208,8 @@ uintptr_t FPSaddress = 0; uintptr_t FPSavgaddress = 0; uint64_t PID = 0; uint32_t FPS = 0xFE; -float FPSmin = 254; -float FPSmax = 0; +float FPSmin = 254; +float FPSmax = 0; float FPSavg = 254; float FPSavg_old = 254; bool useOldFPSavg = false; @@ -223,15 +223,16 @@ uint32_t realCPU_Hz = 0; uint32_t realGPU_Hz = 0; uint32_t realRAM_Hz = 0; uint32_t partLoad[HocClkPartLoad_EnumMax]; -uint32_t realCPU_mV = 0; -uint32_t realGPU_mV = 0; -uint32_t realRAM_mV = 0; -uint32_t realSOC_mV = 0; +uint32_t realCPU_mV = 0; +uint32_t realGPU_mV = 0; +uint32_t realRAM_mV = 0; +uint32_t realSOC_mV = 0; uint8_t refreshRate = 0; //Read real temps from sys-clk sysmodule int32_t realCPU_Temp = 0; int32_t realGPU_Temp = 0; +u32 realPLLX_Temp = 0; int32_t realRAM_Temp = 0; int compare (const void* elem1, const void* elem2) { @@ -271,20 +272,20 @@ void searchSharedMemoryBlock(uintptr_t base) { NxFps = 0; return; } - + ptrdiff_t search_offset = 0; const uintptr_t memory_end = base + 0x1000; - + while (search_offset < 0x1000) { const uintptr_t current_addr = base + search_offset; - + // Ensure we don't read past the end of shared memory if (current_addr + sizeof(NxFpsSharedBlock) > memory_end) { break; } - + NxFps = (NxFpsSharedBlock*)current_addr; - + // Add bounds checking and magic validation if (NxFps && current_addr >= base && NxFps->MAGIC == 0x465053) { return; @@ -297,24 +298,24 @@ void searchSharedMemoryBlock(uintptr_t base) { //Check if SaltyNX is working bool CheckPort() { Handle saltysd; - + // Try up to 67 times with exponential backoff for better responsiveness for (int i = 0; i < 50; i++) { if (R_SUCCEEDED(svcConnectToNamedPort(&saltysd, "InjectServ"))) { svcCloseHandle(saltysd); return true; } - + // Progressive sleep - start fast, then slow down //if (i < 10) { // svcSleepThread(100'000); // 0.1ms for first 10 attempts //} else if (i < 30) { - // svcSleepThread(500'000); // 0.5ms for next 20 attempts + // svcSleepThread(500'000); // 0.5ms for next 20 attempts //} else { // svcSleepThread(1'000'000); // 1ms for remaining attempts //} } - + return false; } @@ -442,7 +443,7 @@ void BatteryChecker(void*) { if (batCurrentAvg >= 0) { batTimeEstimate = -1; - } + } else { static float batteryTimeEstimateInMinutes = 0; Max17050ReadReg(MAX17050_TTE, &data); @@ -528,7 +529,7 @@ bool usingEOS() { // === ULTRA-FAST VOLTAGE READING === static constexpr PowerDomainId domains[] = { PcvPowerDomainId_Max77621_Cpu, // [0] CPU - PcvPowerDomainId_Max77621_Gpu, // [1] GPU + PcvPowerDomainId_Max77621_Gpu, // [1] GPU PcvPowerDomainId_Max77812_Dram, // [2] VDD2 (EMC/DRAM) PcvPowerDomainId_Max77620_Sd0, // [3] SOC PcvPowerDomainId_Max77620_Sd1 // [4] VDDQ @@ -539,7 +540,7 @@ static constexpr PowerDomainId domains[] = { void Misc(void*) { const uint64_t timeout_ns = TeslaFPS < 10 ? (1'000'000'000 / TeslaFPS) : 100'000'000; const bool isUsingEOS = usingEOS(); - + // Initialize voltage reading if needed bool canReadVoltages = false; if (!isUsingEOS && realVoltsPolling) { @@ -548,10 +549,10 @@ void Misc(void*) { realVoltsPolling = false; } } - + do { mutexLock(&mutex_Misc); - + // CPU, GPU and RAM Frequency if (R_SUCCEEDED(clkrstCheck)) { ClkrstSession clkSession; @@ -573,7 +574,7 @@ void Misc(void*) { pcvGetClockRate(PcvModule_GPU, &GPU_Hz); pcvGetClockRate(PcvModule_EMC, &RAM_Hz); } - + // Get sys-clk data if (R_SUCCEEDED(hocclkCheck)) { HocClkContext hocclkCTX; @@ -585,11 +586,12 @@ void Misc(void*) { partLoad[HocClkPartLoad_EMCCpu] = hocclkCTX.partLoad[HocClkPartLoad_EMCCpu]; realCPU_Temp = hocclkCTX.temps[HocClkThermalSensor_CPU]; realGPU_Temp = hocclkCTX.temps[HocClkThermalSensor_GPU]; + realPLLX_Temp = hocclkCTX.temps[HocClkThermalSensor_PLLX]; realRAM_Temp = hocclkCTX.temps[HocClkThermalSensor_MEM]; - - realCPU_mV = hocclkCTX.voltages[HocClkVoltage_CPU]; - realGPU_mV = hocclkCTX.voltages[HocClkVoltage_GPU]; - realRAM_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2]; + + realCPU_mV = hocclkCTX.voltages[HocClkVoltage_CPU]; + realGPU_mV = hocclkCTX.voltages[HocClkVoltage_GPU]; + realRAM_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2]; realSOC_mV = hocclkCTX.voltages[HocClkVoltage_SOC]; const u32 vdd2_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2] / 1000; // µV to mV const u32 vddq_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDDQ] / 1000; // µV to mV @@ -598,7 +600,7 @@ void Misc(void*) { } } - + // Temperatures if (R_SUCCEEDED(i2cCheck)) { Tmp451GetSocTemp(&SOC_temperatureF); @@ -607,7 +609,7 @@ void Misc(void*) { if (R_SUCCEEDED(tcCheck)) { tcGetSkinTemperatureMilliC(&skin_temperaturemiliC); } - + // RAM Memory Used if (R_SUCCEEDED(Hinted)) { svcGetSystemInfo(&RAM_Total_application_u, 0, INVALID_HANDLE, 0); @@ -619,7 +621,7 @@ void Misc(void*) { svcGetSystemInfo(&RAM_Used_system_u, 1, INVALID_HANDLE, 2); svcGetSystemInfo(&RAM_Used_systemunsafe_u, 1, INVALID_HANDLE, 3); } - + // Fan if (R_SUCCEEDED(pwmCheck)) { double temp = 0; @@ -633,29 +635,29 @@ void Misc(void*) { } } } - + // GPU Load if (R_SUCCEEDED(nvCheck) && GPULoadPerFrame) { nvIoctl(fd, NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD, &GPU_Load_u); } - + // FPS - with proper null checks if (GameRunning) { if (NxFps && SharedMemoryUsed) { FPS = NxFps->FPS; const size_t element_count = sizeof(NxFps->FPSticks) / sizeof(NxFps->FPSticks[0]); - FPSavg_old = static_cast(systemtickfrequency) / + FPSavg_old = static_cast(systemtickfrequency) / (std::accumulate(&NxFps->FPSticks[0], &NxFps->FPSticks[element_count], 0.0f) / element_count); - + const float FPS_in = static_cast(FPS); if (FPSavg_old >= (FPS_in - 0.25f) && FPSavg_old <= (FPS_in + 0.25f)) { FPSavg = FPS_in; } else { FPSavg = FPSavg_old; } - + lastFrameNumber = NxFps->frameNumber; - + if (FPSavg > FPSmax) FPSmax = FPSavg; if (FPSavg < FPSmin) FPSmin = FPSavg; } @@ -664,11 +666,11 @@ void Misc(void*) { FPSmin = 254; FPSmax = 0; } - + mutexUnlock(&mutex_Misc); - + } while (!leventWait(&threadexit, timeout_ns)); - + // Cleanup voltage reading if initialized if (canReadVoltages) { rgltrExit(); @@ -697,7 +699,7 @@ void Misc2(void*) { void Misc3(void*) { const bool isUsingEOS = usingEOS(); - + // Initialize voltage reading if needed bool canReadVoltages = false; if (!isUsingEOS && realVoltsPolling) { @@ -706,24 +708,25 @@ void Misc3(void*) { realVoltsPolling = false; } } - + do { mutexLock(&mutex_Misc); - + // Get sys-clk data if (R_SUCCEEDED(hocclkCheck)) { HocClkContext hocclkCTX; if (R_SUCCEEDED(hocclkIpcGetCurrentContext(&hocclkCTX))) { partLoad[HocClkPartLoad_EMC] = hocclkCTX.partLoad[HocClkPartLoad_EMC]; partLoad[HocClkPartLoad_EMCCpu] = hocclkCTX.partLoad[HocClkPartLoad_EMCCpu]; - + realCPU_Temp = hocclkCTX.temps[HocClkThermalSensor_CPU]; realGPU_Temp = hocclkCTX.temps[HocClkThermalSensor_GPU]; + realPLLX_Temp = hocclkCTX.temps[HocClkThermalSensor_PLLX]; realRAM_Temp = hocclkCTX.temps[HocClkThermalSensor_MEM]; - - realCPU_mV = hocclkCTX.voltages[HocClkVoltage_CPU]; - realGPU_mV = hocclkCTX.voltages[HocClkVoltage_GPU]; - realRAM_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2]; + + realCPU_mV = hocclkCTX.voltages[HocClkVoltage_CPU]; + realGPU_mV = hocclkCTX.voltages[HocClkVoltage_GPU]; + realRAM_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2]; realSOC_mV = hocclkCTX.voltages[HocClkVoltage_SOC]; const u32 vdd2_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2] / 1000; // µV to mV const u32 vddq_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDDQ] / 1000; // µV to mV @@ -732,7 +735,7 @@ void Misc3(void*) { } } - + // Temperatures if (R_SUCCEEDED(i2cCheck)) { Tmp451GetSocTemp(&SOC_temperatureF); @@ -741,7 +744,7 @@ void Misc3(void*) { if (R_SUCCEEDED(tcCheck)) { tcGetSkinTemperatureMilliC(&skin_temperaturemiliC); } - + // Fan if (R_SUCCEEDED(pwmCheck)) { double temp = 0; @@ -755,16 +758,16 @@ void Misc3(void*) { } } } - + // GPU Load if (R_SUCCEEDED(nvCheck)) { nvIoctl(fd, NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD, &GPU_Load_u); } - + mutexUnlock(&mutex_Misc); - + } while (!leventWait(&threadexit, 1'000'000'000)); // 1 second timeout - + // Cleanup voltage reading if initialized if (canReadVoltages) { rgltrExit(); @@ -909,7 +912,7 @@ void FPSCounter(void*) { const size_t element_count = sizeof(NxFps -> FPSticks) / sizeof(NxFps -> FPSticks[0]); FPSavg_old = (float)systemtickfrequency / (std::accumulate(&NxFps->FPSticks[0], &NxFps->FPSticks[element_count], 0) / element_count); const float FPS_in = (float)FPS; - if (FPSavg_old >= (FPS_in-0.25) && FPSavg_old <= (FPS_in+0.25)) + if (FPSavg_old >= (FPS_in-0.25) && FPSavg_old <= (FPS_in+0.25)) FPSavg = FPS_in; else FPSavg = FPSavg_old; lastFrameNumber = NxFps -> frameNumber; @@ -937,10 +940,10 @@ void EndFPSCounterThread() { threadClose(&t4); } -void StartInfoThread() { +void StartInfoThread() { // Clear the thread exit event for new threads leventClear(&threadexit); - + threadCreate(&t0, CheckCore, &idletick0, NULL, 0x1000, 0x10, 0); threadCreate(&t1, CheckCore, &idletick1, NULL, 0x1000, 0x10, 1); threadCreate(&t2, CheckCore, &idletick2, NULL, 0x1000, 0x10, 2); @@ -962,14 +965,14 @@ void StartInfoThread() { void EndInfoThread() { // Signal the thread exit event leventSignal(&threadexit); - + // Wait for all threads to exit threadWaitForExit(&t0); threadWaitForExit(&t1); threadWaitForExit(&t2); threadWaitForExit(&t3); threadWaitForExit(&t7); - + // Close thread handles threadClose(&t0); threadClose(&t1); @@ -1053,7 +1056,7 @@ void formatButtonCombination(std::string& line) { button = line.substr(old_pos); if (replaces.find(button) != replaces.end()) { line.replace(old_pos, button.length(), replaces[button]); - } + } } //uint64_t comboBitmask = 0; @@ -1086,7 +1089,7 @@ void formatButtonCombination(std::string& line) { // {"RIGHT", HidNpadButton_AnyRight} // }; // -// +// // std::string comboCopy = buttonCombo; // Make a copy of buttonCombo // // static const std::string delimiter = "+"; @@ -1112,16 +1115,16 @@ void formatButtonCombination(std::string& line) { ALWAYS_INLINE bool isKeyComboPressed(uint64_t keysHeld, uint64_t keysDown) { // Check if any of the combo buttons are pressed down this frame // while the rest of the combo buttons are being held - + const uint64_t comboButtonsDown = keysDown & tsl::cfg::launchCombo; const uint64_t comboButtonsHeld = keysHeld & tsl::cfg::launchCombo; - + // If any combo buttons are pressed down this frame if (comboButtonsDown != 0) { // Check if the remaining combo buttons are being held // (the full combo should be active when combining held + down) const uint64_t totalComboActive = comboButtonsHeld | comboButtonsDown; - + if (totalComboActive == tsl::cfg::launchCombo) { fixHiding = true; // for fixing hiding when returning //triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -1129,7 +1132,7 @@ ALWAYS_INLINE bool isKeyComboPressed(uint64_t keysHeld, uint64_t keysDown) { return true; } } - + return false; } @@ -1177,7 +1180,7 @@ void ParseIniFile() { // Load main config INI once auto configData = ult::getParsedDataFromIniFile(configIniPath); auto statusIt = configData.find("status-monitor"); - + if (statusIt != configData.end()) { const auto& statusSection = statusIt->second; std::string key; @@ -1189,12 +1192,12 @@ void ParseIniFile() { convertToUpper(key); batteryFiltered = (key == "TRUE"); } - + auto refreshRateIt = statusSection.find("battery_time_left_refreshrate"); if (refreshRateIt != statusSection.end()) { batteryTimeLeftRefreshRate = std::clamp(atol(refreshRateIt->second.c_str()), 1L, 60L); } - + auto gpuLoadIt = statusSection.find("average_gpu_load"); if (gpuLoadIt != statusSection.end()) { key = gpuLoadIt->second; @@ -1215,11 +1218,11 @@ void ParseIniFile() { {ultrahandConfigIniPath, "ultrahand"}, {teslaConfigIniPath, "tesla"} }; - + for (const auto& config : externalConfigs) { auto extConfigData = ult::getParsedDataFromIniFile(config.path); auto sectionIt = extConfigData.find(config.section); - + if (sectionIt != extConfigData.end()) { auto keyComboIt = sectionIt->second.find("key_combo"); if (keyComboIt != sectionIt->second.end() && !keyComboIt->second.empty()) { @@ -1230,14 +1233,14 @@ void ParseIniFile() { } } } - + //comboBitmask = MapButtons(keyCombo); } ALWAYS_INLINE bool isValidRGBA4Color(const std::string& hexColor) { const char* data = hexColor.data(); const size_t size = hexColor.size(); - + static unsigned char c; for (size_t i = 0; i < size; ++i) { c = data[i]; @@ -1246,7 +1249,7 @@ ALWAYS_INLINE bool isValidRGBA4Color(const std::string& hexColor) { return false; } } - + return true; } @@ -1254,7 +1257,7 @@ bool convertStrToRGBA4444(std::string hexColor, uint16_t* returnValue) { // Check if # is present if (hexColor.size() != 5 || hexColor[0] != '#') return false; - + hexColor = hexColor.substr(1); if (isValidRGBA4Color(hexColor)) { @@ -1268,7 +1271,7 @@ struct FullSettings { uint8_t refreshRate; bool setPosRight; bool showRealFreqs; - bool realVolts; + bool realVolts; bool realTemps; bool showDeltas; bool showTargetFreqs; @@ -1322,7 +1325,7 @@ struct MiniSettings { struct MicroSettings { uint8_t refreshRate; bool realFrequencies; - bool realVolts; + bool realVolts; bool realTemps; bool showFullCPU; bool showFullResolution; @@ -1443,7 +1446,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -1452,13 +1455,13 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("mini"); if (sectionIt == parsedData.end()) return; - + std::string key; uint16_t temp; @@ -1469,7 +1472,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { if (it != section.end()) { settings->refreshRate = std::clamp(atol(it->second.c_str()), 1L, 60L); } - + // Process boolean flags it = section.find("real_freqs"); if (it != section.end()) { @@ -1477,14 +1480,14 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { convertToUpper(key); settings->realFrequencies = (key == "TRUE"); } - + it = section.find("real_volts"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->realVolts = (key == "TRUE"); } - + it = section.find("real_temps"); if (it != section.end()) { key = it->second; @@ -1494,12 +1497,12 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { // Process font sizes with shared bounds static constexpr long minFontSize = 8; static constexpr long maxFontSize = 22; - + it = section.find("handheld_font_size"); if (it != section.end()) { settings->handheldFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); } - + it = section.find("docked_font_size"); if (it != section.end()) { settings->dockedFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); @@ -1509,7 +1512,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { if (it != section.end()) { settings->spacing = atol(it->second.c_str()); } - + // Process colors it = section.find("background_color"); if (it != section.end()) { @@ -1523,7 +1526,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { if (convertStrToRGBA4444(it->second, &temp)) settings->focusBackgroundColor = temp; } - + it = section.find("separator_color"); if (it != section.end()) { temp = 0; @@ -1537,14 +1540,14 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { if (convertStrToRGBA4444(it->second, &temp)) settings->catColor = temp; } - + it = section.find("text_color"); if (it != section.end()) { temp = 0; if (convertStrToRGBA4444(it->second, &temp)) settings->textColor = temp; } - + // Process RAM load flag it = section.find("show_full_cpu"); if (it != section.end()) { @@ -1622,7 +1625,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { convertToUpper(key); settings->show = std::move(key); } - + // Process RAM load flag it = section.find("replace_MB_with_RAM_load"); if (it != section.end()) { @@ -1662,7 +1665,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { convertToUpper(key); settings->sleepExit = (key != "FALSE"); } - + // Process alignment settings //it = section.find("layer_width_align"); //if (it != section.end()) { @@ -1735,7 +1738,7 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -1744,13 +1747,13 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("micro"); if (sectionIt == parsedData.end()) return; - + std::string key; uint16_t temp; @@ -1761,7 +1764,7 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { if (it != section.end()) { settings->refreshRate = std::clamp(atol(it->second.c_str()), 1L, 60L); } - + // Process boolean flags it = section.find("real_freqs"); if (it != section.end()) { @@ -1769,28 +1772,28 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { convertToUpper(key); settings->realFrequencies = (key == "TRUE"); } - + it = section.find("real_volts"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->realVolts = (key == "TRUE"); } - + it = section.find("real_temps"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->realTemps = (key == "TRUE"); } - + it = section.find("show_full_cpu"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showFullCPU = (key == "TRUE"); } - + it = section.find("show_full_res"); if (it != section.end()) { key = it->second; @@ -1860,21 +1863,21 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { convertToUpper(key); settings->invertBatteryDisplay = (key != "FALSE"); } - + // Process font sizes with shared bounds static constexpr long minFontSize = 8; static constexpr long maxFontSize = 18; - + it = section.find("handheld_font_size"); if (it != section.end()) { settings->handheldFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); } - + it = section.find("docked_font_size"); if (it != section.end()) { settings->dockedFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); } - + // Process colors it = section.find("background_color"); if (it != section.end()) { @@ -1882,28 +1885,28 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { if (convertStrToRGBA4444(it->second, &temp)) settings->backgroundColor = temp; } - + it = section.find("separator_color"); if (it != section.end()) { temp = 0; if (convertStrToRGBA4444(it->second, &temp)) settings->separatorColor = temp; } - + it = section.find("cat_color"); if (it != section.end()) { temp = 0; if (convertStrToRGBA4444(it->second, &temp)) settings->catColor = temp; } - + it = section.find("text_color"); if (it != section.end()) { temp = 0; if (convertStrToRGBA4444(it->second, &temp)) settings->textColor = temp; } - + // Process text alignment it = section.find("text_align"); if (it != section.end()) { @@ -1917,7 +1920,7 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { settings->alignTo = 2; } } - + // Process RAM load flag it = section.find("replace_GB_with_RAM_load"); if (it != section.end()) { @@ -1925,7 +1928,7 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { convertToUpper(key); settings->showpartLoad = (key != "FALSE"); } - + // Process show string it = section.find("show"); if (it != section.end()) { @@ -1933,7 +1936,7 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { convertToUpper(key); settings->show = std::move(key); } - + // Process layer height alignment it = section.find("layer_height_align"); if (it != section.end()) { @@ -1979,7 +1982,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsCounterSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -1988,33 +1991,33 @@ ALWAYS_INLINE void GetConfigSettings(FpsCounterSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("fps-counter"); if (sectionIt == parsedData.end()) return; - + std::string key; uint16_t temp; const auto& section = sectionIt->second; - + // Process font sizes with shared bounds static constexpr long minFontSize = 8; static constexpr long maxFontSize = 150; - + auto it = section.find("handheld_font_size"); if (it != section.end()) { settings->handheldFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); } - + it = section.find("docked_font_size"); if (it != section.end()) { settings->dockedFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); } - + // Process colors it = section.find("background_color"); if (it != section.end()) { @@ -2030,14 +2033,14 @@ ALWAYS_INLINE void GetConfigSettings(FpsCounterSettings* settings) { settings->focusBackgroundColor = temp; } - + it = section.find("text_color"); if (it != section.end()) { temp = 0; if (convertStrToRGBA4444(it->second, &temp)) settings->textColor = temp; } - + // Process alignment settings //it = section.find("layer_width_align"); //if (it != section.end()) { @@ -2049,7 +2052,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsCounterSettings* settings) { // settings->setPos = 2; // } //} - + //it = section.find("layer_height_align"); //if (it != section.end()) { // key = it->second; @@ -2123,7 +2126,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -2132,18 +2135,18 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("fps-graph"); if (sectionIt == parsedData.end()) return; - + std::string key; uint16_t temp; const auto& section = sectionIt->second; - + // Process alignment settings //auto it = section.find("layer_width_align"); //if (it != section.end()) { @@ -2166,7 +2169,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { // settings->setPos += 6; // } //} - + // Process show_info boolean auto it = section.find("show_info"); if (it != section.end()) { @@ -2174,7 +2177,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { convertToUpper(key); settings->showInfo = (key == "TRUE"); } - + it = section.find("real_temps"); if (it != section.end()) { key = it->second; @@ -2214,13 +2217,13 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { settings->framePadding = atol(it->second.c_str()); } - + // Process colors - using a struct for cleaner code struct ColorMapping { const char* key; uint16_t* target; }; - + const ColorMapping colorMappings[] = { {"min_fps_text_color", &settings->minFPSTextColor}, {"max_fps_text_color", &settings->maxFPSTextColor}, @@ -2235,7 +2238,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { {"text_color", &settings->textColor}, {"cat_color", &settings->catColor} }; - + for (const auto& mapping : colorMappings) { it = section.find(mapping.key); if (it != section.end()) { @@ -2267,7 +2270,7 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -2276,16 +2279,16 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("full"); if (sectionIt == parsedData.end()) return; - + std::string key; uint16_t temp; - + const auto& section = sectionIt->second; // Process refresh_rate @@ -2293,7 +2296,7 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { if (it != section.end()) { settings->refreshRate = std::clamp(atol(it->second.c_str()), 1L, 60L); } - + // Process layer position it = section.find("layer_width_align"); if (it != section.end()) { @@ -2301,7 +2304,7 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { convertToUpper(key); settings->setPosRight = (key == "RIGHT"); } - + // Process boolean flags it = section.find("show_real_freqs"); if (it != section.end()) { @@ -2309,49 +2312,49 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { convertToUpper(key); settings->showRealFreqs = !(key == "FALSE"); } - + it = section.find("real_temps"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->realTemps = (key == "TRUE"); } - + it = section.find("show_deltas"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showDeltas = !(key == "FALSE"); } - + it = section.find("show_target_freqs"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showTargetFreqs = !(key == "FALSE"); } - + it = section.find("show_fps"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showFPS = !(key == "FALSE"); } - + it = section.find("show_res"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showRES = !(key == "FALSE"); } - + it = section.find("show_read_speed"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showRDSD = !(key == "FALSE"); } - + it = section.find("use_dynamic_colors"); if (it != section.end()) { key = it->second; @@ -2380,7 +2383,7 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { if (convertStrToRGBA4444(it->second, &temp)) settings->catColor1 = temp; } - + it = section.find("cat_color_2"); if (it != section.end()) { temp = 0; @@ -2415,7 +2418,7 @@ ALWAYS_INLINE void GetConfigSettings(ResolutionSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -2424,15 +2427,15 @@ ALWAYS_INLINE void GetConfigSettings(ResolutionSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("game_resolutions"); if (sectionIt == parsedData.end()) return; - + std::string key; - + const auto& section = sectionIt->second; // Process refresh_rate @@ -2457,7 +2460,7 @@ ALWAYS_INLINE void GetConfigSettings(ResolutionSettings* settings) { if (convertStrToRGBA4444(it->second, &temp)) settings->focusBackgroundColor = temp; } - + it = section.find("cat_color"); if (it != section.end()) { temp = 0; @@ -2472,7 +2475,7 @@ ALWAYS_INLINE void GetConfigSettings(ResolutionSettings* settings) { // settings->catColor2 = temp; //} - + it = section.find("text_color"); if (it != section.end()) { temp = 0; @@ -2494,7 +2497,7 @@ ALWAYS_INLINE void GetConfigSettings(ResolutionSettings* settings) { if (it != section.end()) { settings->framePadding = atol(it->second.c_str()); } - + // Process alignment settings //it = section.find("layer_width_align"); //if (it != section.end()) { diff --git a/Source/Horizon-OC-Monitor/source/modes/Micro.hpp b/Source/Horizon-OC-Monitor/source/modes/Micro.hpp index 0a805643..ce9a80de 100644 --- a/Source/Horizon-OC-Monitor/source/modes/Micro.hpp +++ b/Source/Horizon-OC-Monitor/source/modes/Micro.hpp @@ -36,7 +36,7 @@ private: size_t fontsize = 0; bool showFPS = false; uint64_t systemtickfrequency_impl = systemtickfrequency; - + // Pre-compiled render data structures struct RenderItem { uint8_t type; @@ -45,7 +45,7 @@ private: const char* volt_ptr; bool has_voltage; }; - + // Resolution tracking resolutionCalls m_resolutionRenderCalls[8] = {0}; resolutionCalls m_resolutionViewportCalls[8] = {0}; @@ -62,7 +62,7 @@ private: bool skipOnce = true; bool runOnce = true; - + // Fixed spacing system - calculate actual widths at render time struct LayoutMetrics { uint32_t label_data_gap = 8; // Fixed gap between label and data @@ -72,20 +72,20 @@ private: uint32_t side_margin = 3; // Margins on left and right bool calculated = false; } layout; - + // Lookup table for difference symbols static constexpr const char* diffSymbols[4] = {"△", "@", "▽", "≠"}; - + inline const char* getDifferenceSymbol(int32_t delta) { if (delta > 20000) return diffSymbols[0]; // △ if (delta > -20000) return diffSymbols[1]; // @ if (delta < -50000) return diffSymbols[3]; // ≠ return diffSymbols[2]; // ▽ } - + void calculateLayoutMetrics(tsl::gfx::Renderer *renderer) { if (layout.calculated) return; - + // Use font size to determine appropriate spacing if (fontsize <= 16) { layout.label_data_gap = 6; @@ -103,12 +103,12 @@ private: layout.volt_data_gap = 0; layout.item_spacing = 20; } - + layout.calculated = true; } public: - MicroOverlay() { + MicroOverlay() { tsl::hlp::requestForeground(false); disableJumpTo = true; //tsl::initializeUltrahandSettings(); @@ -135,7 +135,7 @@ public: //alphabackground = 0x0; deactivateOriginalFooter = true; StartThreads(); - + // Pre-allocate render items vector //renderItems.reserve(8); realVoltsPolling = settings.realVolts; @@ -144,15 +144,15 @@ public: if (R_SUCCEEDED(psmCheck) && R_SUCCEEDED(i2cCheck)) { uint16_t data = 0; float tempA = 0.0; - + // Get initial power consumption Max17050ReadReg(MAX17050_AvgCurrent, &data); tempA = (1.5625 / (max17050SenseResistor * max17050CGain)) * (s16)data; PowerConsumption = tempA * batVoltageAvg / 1000000.0; // Rough initial estimate - + // Get initial battery info psmGetBatteryChargeInfoFields(psmService, &_batteryChargeInfoFields); - + // Get initial time estimate if (tempA >= 0) { batTimeEstimate = -1; @@ -171,18 +171,18 @@ public: batTimeEstimate = -1; _batteryChargeInfoFields = {0}; } - + // Now format the initial Battery_c string char remainingBatteryLife[8]; const float drawW = (fabsf(PowerConsumption) < 0.01f) ? 0.0f : PowerConsumption; - + if (batTimeEstimate >= 0 && !(drawW <= 0.01f && drawW >= -0.01f)) { snprintf(remainingBatteryLife, sizeof(remainingBatteryLife), "%d:%02d", batTimeEstimate / 60, batTimeEstimate % 60); } else { strcpy(remainingBatteryLife, "--:--"); } - + if (!settings.invertBatteryDisplay) { snprintf(Battery_c, sizeof(Battery_c), "%.2f W%.1f%% [%s]", @@ -197,39 +197,39 @@ public: drawW); } - + } - + ~MicroOverlay() { CloseThreads(); fixForeground = true; FullMode = true; } - + // Fast parsing and render item preparation void prepareRenderItems() { if (!renderDataDirty) return; - + renderItems.clear(); - + // Fast manual parsing of settings.show const std::string& show = settings.show; size_t start = 0, end = 0; uint8_t seen_flags = 0; - + static size_t len; static uint32_t key3; while (start < show.length()) { end = show.find('+', start); if (end == std::string::npos) end = show.length(); - + len = end - start; if (len >= 3) { const char* key = &show[start]; - + // Use first 3 chars for fast comparison key3 = (key[0] << 16) | (key[1] << 8) | key[2]; - + switch (key3) { case 0x435055: // "CPU" if (!(seen_flags & 1)) { @@ -286,7 +286,7 @@ public: seen_flags |= 512; } break; - case 0x445443: // "DTC" + case 0x445443: // "DTC" if (!(seen_flags & 256) && settings.showDTC) { renderItems.push_back({8, settings.useDTCSymbol ? "\uE007" : "DTC", DTC_c, nullptr, false}); seen_flags |= 256; @@ -296,38 +296,38 @@ public: } start = end + 1; } - + renderDataDirty = false; } - + virtual tsl::elm::Element* createUI() override { auto* Status = new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer *renderer, u16 x, u16 y, u16 w, u16 h) { cachedMargin = renderer->getTextDimensions("CPUGPURAMSOCBAT[]", false, fontsize).second; if (!Initialized) { //cachedMargin = renderer->drawString(" ", false, 0, 0, fontsize, renderer->a(0x0000)).first; - + catColorA = settings.catColor; textColorA = settings.textColor; - base_y = settings.setPosBottom ? + base_y = settings.setPosBottom ? tsl::cfg::FramebufferHeight - (fontsize + (fontsize / 4)) +1: 0; Initialized = true; renderDataDirty = true; layout.calculated = false; // Force recalculation tsl::hlp::requestForeground(false); } - + //renderer->drawRect(0, 0, tsl::cfg::FramebufferWidth, cachedMargin + 4, a(settings.backgroundColor)); renderer->drawRect(0, settings.setPosBottom ? base_y-1 : 0, tsl::cfg::FramebufferWidth, cachedMargin + 4, a(settings.backgroundColor)); // Prepare render items if settings changed prepareRenderItems(); calculateLayoutMetrics(renderer); - + // Separate battery from other items std::vector main_items; //RenderItem* battery_item = nullptr; - + for (auto& item : renderItems) { //if (item.type == 6) { // BAT // battery_item = &item; @@ -347,7 +347,7 @@ public: main_items.push_back(item); } } - + // Calculate actual widths for all main items struct ItemLayout { uint32_t label_width; @@ -355,7 +355,7 @@ public: uint32_t volt_width; uint32_t total_width; }; - + std::vector item_layouts; uint32_t total_main_width = 0; @@ -364,81 +364,81 @@ public: static ItemLayout item_layout; for (const auto& item : main_items) { item_layout = {}; - + // Calculate actual label width //auto label_dim = renderer->drawString(item.label, false, 0, 0, fontsize, renderer->a(0x0000)); //auto label_dim = renderer->getTextDimensions(item.label, fontsize); item_layout.label_width = renderer->getTextDimensions(item.label, false, fontsize).first; - + // Calculate actual data width //auto data_dim = renderer->drawString(item.data_ptr, false, 0, 0, fontsize, renderer->a(0x0000)); //auto data_dim = renderer->getTextDimensions(item.data_ptr, fontsize); item_layout.data_width = renderer->getTextDimensions(item.data_ptr, false, fontsize).first; - + // Calculate voltage width if present if (item.has_voltage && item.volt_ptr) { //uto volt_dim = renderer->drawString(item.volt_ptr, false, 0, 0, fontsize, renderer->a(0x0000)); //auto volt_dim = renderer->getTextDimensions(item.volt_ptr, fontsize); item_layout.volt_width = renderer->getTextDimensions(item.volt_ptr, false, fontsize).first; - + // Total: label + gap + data + gap + "|" + gap + voltage //auto sep_width = renderer->drawString("", false, 0, 0, fontsize, renderer->a(0x0000)); - item_layout.total_width = item_layout.label_width + layout.label_data_gap + - item_layout.data_width + layout.volt_separator_gap + + item_layout.total_width = item_layout.label_width + layout.label_data_gap + + item_layout.data_width + layout.volt_separator_gap + sep_width + layout.volt_data_gap + item_layout.volt_width; } else { // Total: label + gap + data item_layout.total_width = item_layout.label_width + layout.label_data_gap + item_layout.data_width; } - + item_layouts.push_back(item_layout); total_main_width += item_layout.total_width; } - - + + // Determine if we have battery and handle it as the rightmost item std::vector all_items_ordered; std::vector all_layouts_ordered; - + // Add main items first for (size_t i = 0; i < main_items.size(); i++) { all_items_ordered.push_back(main_items[i]); all_layouts_ordered.push_back(item_layouts[i]); } - + // Add battery as the last item if present //if (battery_item) { // //auto bat_label_dim = renderer->drawString("BAT", false, 0, 0, fontsize, renderer->a(0x0000)); // //auto bat_label_dim = renderer->getTextDimensions("BAT", fontsize); // //auto bat_data_dim = renderer->drawString(battery_item->data_ptr, false, 0, 0, fontsize, renderer->a(0x0000)); // //auto bat_data_dim = renderer->getTextDimensions(battery_item->data_ptr, fontsize); - // + // // ItemLayout battery_layout = {}; // battery_layout.label_width = renderer->getTextDimensions("BAT", false, fontsize).first; // battery_layout.data_width = renderer->getTextDimensions(battery_item->data_ptr, false, fontsize).first; // battery_layout.volt_width = 0; // battery_layout.total_width = battery_layout.label_width + layout.label_data_gap + battery_layout.data_width; - // + // // all_items_ordered.push_back(*battery_item); // all_layouts_ordered.push_back(battery_layout); //} - + // Calculate total width of all items uint32_t total_all_width = 0; for (const auto& item_layout : all_layouts_ordered) { total_all_width += item_layout.total_width; } - + // Calculate available space for distribution //uint32_t available_width = tsl::cfg::FramebufferWidth - (2 * layout.side_margin); //uint32_t remaining_space = available_width - total_all_width; - + // Calculate positions based on alignment mode std::vector item_positions; const size_t N = all_items_ordered.size(); if (N == 0) return; - + if (N == 1) { // Single item positioning based on alignment if (settings.alignTo == 2) { // RIGHT @@ -452,43 +452,43 @@ public: for (const auto& layout : all_layouts_ordered) { total_widths += layout.total_width; } - + if (settings.alignTo == 0) { // LEFT alignment // All items except last positioned from left with small gaps // Last item (battery if present) positioned at far right const uint32_t small_gap = layout.item_spacing; - + // Position items from left uint32_t current_x = layout.side_margin; for (size_t i = 0; i < N - 1; ++i) { item_positions.push_back(current_x); current_x += all_layouts_ordered[i].total_width + small_gap; } - + // Position last item at far right const uint32_t last_width = all_layouts_ordered[N-1].total_width; item_positions.push_back(tsl::cfg::FramebufferWidth - layout.side_margin - last_width); - + } else if (settings.alignTo == 2) { // RIGHT alignment // First item at far left, remaining items packed at right const uint32_t small_gap = layout.item_spacing; - + // Resize vector to hold all positions item_positions.resize(N); - + // Position first item at far left item_positions[0] = layout.side_margin; - + // Calculate total width of items 1 to N-1 plus gaps between them uint32_t right_group_width = 0; for (size_t i = 1; i < N; ++i) { right_group_width += all_layouts_ordered[i].total_width; if (i < N - 1) right_group_width += small_gap; // Gap after each item except the last } - + // Start positioning from right margin minus total width of right group uint32_t current_x = tsl::cfg::FramebufferWidth - layout.side_margin - right_group_width; - + // Position items 1 to N-1 sequentially from left to right within the right group for (size_t i = 1; i < N; ++i) { item_positions[i] = current_x; @@ -497,10 +497,10 @@ public: } else { // CENTER alignment (default behavior) // Total available width for spacing = framebuffer width minus total item widths minus margins const int32_t total_spacing = (int32_t)tsl::cfg::FramebufferWidth - (2 * (int32_t)layout.side_margin) - (int32_t)total_widths; - + // Number of gaps between items is N-1 const uint32_t gap = total_spacing > 0 ? (uint32_t)(total_spacing / (N - 1)) : 0; - + // Position first item flush left item_positions.push_back(layout.side_margin); static uint32_t prev_pos, prev_width; @@ -510,11 +510,11 @@ public: prev_width = all_layouts_ordered[i - 1].total_width; item_positions.push_back(prev_pos + prev_width + gap); } - + // Fix any rounding error for center alignment const int32_t last_item_end = item_positions.back() + all_layouts_ordered.back().total_width; const int32_t overflow = (int32_t)tsl::cfg::FramebufferWidth - layout.side_margin - last_item_end; - + if (overflow != 0) { for (size_t i = 1; i < item_positions.size(); ++i) { item_positions[i] += overflow; @@ -532,11 +532,11 @@ public: const auto& item = all_items_ordered[i]; const auto& item_layout = all_layouts_ordered[i]; current_x = item_positions[i]; - + // Draw label renderer->drawString(item.label, false, current_x, base_y + cachedMargin, fontsize, catColorA); current_x += item_layout.label_width + layout.label_data_gap; - + // Draw data //renderer->drawString(item.data_ptr, false, current_x, base_y + fontsize, fontsize, textColorA); @@ -552,22 +552,22 @@ public: const size_t cPos = dataStr.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; - + const std::string preTempPart = dataStr.substr(0, tempStart); const std::string tempPart = dataStr.substr(tempStart, tempEnd - tempStart); const std::string postTempPart = dataStr.substr(tempEnd); - + const float temp = realCPU_Temp / 1000.0f; const tsl::Color tempColor = tsl::GradientColor(temp); - + uint32_t renderX = current_x; if (!preTempPart.empty()) { renderer->drawStringWithColoredSections(preTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); renderX += renderer->getTextDimensions(preTempPart, false, fontsize).first; } - + renderer->drawStringWithColoredSections(tempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, tempColor, a(settings.separatorColor)); - + if (!postTempPart.empty()) { renderX += renderer->getTextDimensions(tempPart, false, fontsize).first; renderer->drawStringWithColoredSections(postTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); @@ -581,7 +581,7 @@ public: } else { renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + } else if (item.type == 1) { // GPU std::string dataStr(item.data_ptr); const size_t degreesPos = dataStr.find("°"); @@ -592,22 +592,22 @@ public: const size_t cPos = dataStr.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; - + const std::string preTempPart = dataStr.substr(0, tempStart); const std::string tempPart = dataStr.substr(tempStart, tempEnd - tempStart); const std::string postTempPart = dataStr.substr(tempEnd); - + const float temp = realGPU_Temp / 1000.0f; const tsl::Color tempColor = tsl::GradientColor(temp); - + uint32_t renderX = current_x; if (!preTempPart.empty()) { renderer->drawStringWithColoredSections(preTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); renderX += renderer->getTextDimensions(preTempPart, false, fontsize).first; } - + renderer->drawStringWithColoredSections(tempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, tempColor, a(settings.separatorColor)); - + if (!postTempPart.empty()) { renderX += renderer->getTextDimensions(tempPart, false, fontsize).first; renderer->drawStringWithColoredSections(postTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); @@ -621,7 +621,7 @@ public: } else { renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + } else if (item.type == 2) { // RAM std::string dataStr(item.data_ptr); const size_t degreesPos = dataStr.find("°"); @@ -632,22 +632,22 @@ public: const size_t cPos = dataStr.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; - + const std::string preTempPart = dataStr.substr(0, tempStart); const std::string tempPart = dataStr.substr(tempStart, tempEnd - tempStart); const std::string postTempPart = dataStr.substr(tempEnd); - + const float temp = realRAM_Temp / 1000.0f; const tsl::Color tempColor = tsl::GradientColor(temp); - + uint32_t renderX = current_x; if (!preTempPart.empty()) { renderer->drawStringWithColoredSections(preTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); renderX += renderer->getTextDimensions(preTempPart, false, fontsize).first; } - + renderer->drawStringWithColoredSections(tempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, tempColor, a(settings.separatorColor)); - + if (!postTempPart.empty()) { renderX += renderer->getTextDimensions(tempPart, false, fontsize).first; renderer->drawStringWithColoredSections(postTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); @@ -661,7 +661,7 @@ public: } else { renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + } else if (item.type == 3) { // SOC temperature // Parse SOC temperature: "XX°C (XX%)" std::string dataStr(item.data_ptr); @@ -670,18 +670,18 @@ public: const size_t cPos = dataStr.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; // Include the 'C' - + // Extract temperature value and apply gradient const int temp = atoi(item.data_ptr); const tsl::Color tempColor = tsl::GradientColor((float)temp); - + // Split into temperature part and remaining part const std::string tempPart = dataStr.substr(0, tempEnd); const std::string restPart = dataStr.substr(tempEnd); - + // Render temperature with gradient color renderer->drawString(tempPart, false, current_x, base_y + cachedMargin, fontsize, tempColor); - + // Render remaining text with normal color if (!restPart.empty()) { const uint32_t tempPartWidth = renderer->getTextDimensions(tempPart, false, fontsize).first; @@ -695,14 +695,14 @@ public: // Fallback: render normally renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + } else if (item.type == 4) { // TMP multiple temperatures // Parse TMP temperatures: "XX°C XX°C XX°C (XX%)" std::string dataStr(item.data_ptr); uint32_t renderX = current_x; size_t pos = 0; bool parseSuccess = true; - + // Parse up to 3 temperatures for (int tempCount = 0; tempCount < 3 && parseSuccess && pos < dataStr.length(); tempCount++) { // Skip any leading spaces @@ -711,47 +711,47 @@ public: renderX += renderer->getTextDimensions(" ", false, fontsize).first; pos++; } - + if (pos >= dataStr.length()) break; - + // Find degrees symbol const size_t degreesPos = dataStr.find("°", pos); if (degreesPos == std::string::npos) { parseSuccess = false; break; } - + // Find 'C' after degrees symbol const size_t cPos = dataStr.find("C", degreesPos); if (cPos == std::string::npos) { parseSuccess = false; break; } - + const size_t tempEnd = cPos + 1; // Include the 'C' - + // Extract and render temperature with gradient const std::string tempPart = dataStr.substr(pos, tempEnd - pos); const int temp = atoi(tempPart.c_str()); const tsl::Color tempColor = tsl::GradientColor((float)temp); - + renderer->drawStringWithColoredSections(tempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, tempColor, a(settings.separatorColor)); renderX += renderer->getTextDimensions(tempPart, false, fontsize).first; - + pos = tempEnd; } - + // Render any remaining text (like " (50%)") if (pos < dataStr.length()) { const std::string restPart = dataStr.substr(pos); renderer->drawStringWithColoredSections(restPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + // If parsing failed, fall back to normal rendering if (!parseSuccess) { renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + } else { // Normal rendering for all other item types renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); @@ -761,7 +761,7 @@ public: renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } current_x += item_layout.data_width; - + // Draw voltage if present if (item.has_voltage && item.volt_ptr) { current_x += layout.volt_separator_gap; @@ -773,7 +773,7 @@ public: } } }); - + tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("", ""); rootFrame->setContent(Status); return rootFrame; @@ -827,7 +827,7 @@ public: // CPU usage calculations - optimized with fewer conditionals const double inv_freq = 1.0 / systemtickfrequency_impl; - + // Capture systemtickfrequency_impl and inv_freq safely const auto formatUsage = [this](char* buf, size_t size, uint64_t idletick, double inv_freq) { if (idletick > systemtickfrequency_impl) { @@ -836,60 +836,60 @@ public: snprintf(buf, size, "%.0f%%", (1.0 - (idletick * inv_freq)) * 100.0); } }; - + // Atomically load idle ticks before using them const uint64_t idle0 = idletick0.load(std::memory_order_acquire); const uint64_t idle1 = idletick1.load(std::memory_order_acquire); const uint64_t idle2 = idletick2.load(std::memory_order_acquire); const uint64_t idle3 = idletick3.load(std::memory_order_acquire); - + formatUsage(CPU_Usage0, sizeof(CPU_Usage0), idle0, inv_freq); formatUsage(CPU_Usage1, sizeof(CPU_Usage1), idle1, inv_freq); formatUsage(CPU_Usage2, sizeof(CPU_Usage2), idle2, inv_freq); formatUsage(CPU_Usage3, sizeof(CPU_Usage3), idle3, inv_freq); mutexLock(&mutex_Misc); - + // CPU frequency and voltage const char* cpuDiff = "@"; if (realCPU_Hz) { const int32_t deltaCPU = (int32_t)(realCPU_Hz / 1000) - (CPU_Hz / 1000); cpuDiff = getDifferenceSymbol(deltaCPU); } - + const uint32_t cpuFreq = settings.realFrequencies && realCPU_Hz ? realCPU_Hz : CPU_Hz; - + if (settings.showFullCPU) { - snprintf(CPU_compressed_c, sizeof(CPU_compressed_c), - "[%s,%s,%s,%s]%s%u.%u", - CPU_Usage0, CPU_Usage1, CPU_Usage2, CPU_Usage3, + snprintf(CPU_compressed_c, sizeof(CPU_compressed_c), + "[%s,%s,%s,%s]%s%u.%u", + CPU_Usage0, CPU_Usage1, CPU_Usage2, CPU_Usage3, cpuDiff, cpuFreq / 1000000, (cpuFreq / 100000) % 10); } else { // Find max CPU usage across all cores const auto extractUsage = [](const char* usage_str) -> double { return strtod(usage_str, nullptr); }; - + const double usage0 = extractUsage(CPU_Usage0); const double usage1 = extractUsage(CPU_Usage1); const double usage2 = extractUsage(CPU_Usage2); const double usage3 = extractUsage(CPU_Usage3); - + const double maxUsage = std::max({usage0, usage1, usage2, usage3}); - - snprintf(CPU_compressed_c, sizeof(CPU_compressed_c), - "%.0f%%%s%u.%u", + + snprintf(CPU_compressed_c, sizeof(CPU_compressed_c), + "%.0f%%%s%u.%u", maxUsage, cpuDiff, cpuFreq / 1000000, (cpuFreq / 100000) % 10); } - + if (settings.realTemps && realCPU_Temp != 0) { char temp_buffer[48]; - snprintf(temp_buffer, sizeof(temp_buffer), " %s", CPU_temp_c); + snprintf(temp_buffer, sizeof(temp_buffer), "%s", CPU_temp_c); strncat(CPU_compressed_c, temp_buffer, sizeof(CPU_compressed_c) - strlen(CPU_compressed_c) - 1); } - + //if (settings.realVolts) { - // snprintf(CPU_volt_c, sizeof(CPU_volt_c), "%u.%u mV", + // snprintf(CPU_volt_c, sizeof(CPU_volt_c), "%u.%u mV", // realCPU_mV/1000, (isMariko ? (realCPU_mV/100)%10 : (realCPU_mV/10)%100)); //} @@ -898,28 +898,28 @@ public: const uint32_t mv = realCPU_mV / 1000; // µV → mV snprintf(CPU_volt_c, sizeof(CPU_volt_c), "%u mV", mv); } - + // GPU frequency and voltage const char* gpuDiff = "@"; if (realGPU_Hz) { const int32_t deltaGPU = (int32_t)(realGPU_Hz / 1000) - (GPU_Hz / 1000); gpuDiff = getDifferenceSymbol(deltaGPU); } - + const uint32_t gpuFreq = settings.realFrequencies && realGPU_Hz ? realGPU_Hz : GPU_Hz; snprintf(GPU_Load_c, sizeof(GPU_Load_c), "%u%%%s%u.%u", GPU_Load_u / 10, gpuDiff, gpuFreq / 1000000, (gpuFreq / 100000) % 10); - + if (settings.realTemps && realGPU_Temp != 0) { char temp_buffer[48]; - snprintf(temp_buffer, sizeof(temp_buffer), " %s", GPU_temp_c); + snprintf(temp_buffer, sizeof(temp_buffer), "%s", GPU_temp_c); strncat(GPU_Load_c, temp_buffer, sizeof(GPU_Load_c) - strlen(GPU_Load_c) - 1); } - + //if (settings.realVolts) { - // snprintf(GPU_volt_c, sizeof(GPU_volt_c), "%u.%u mV", + // snprintf(GPU_volt_c, sizeof(GPU_volt_c), "%u.%u mV", // realGPU_mV/1000, (isMariko ? (realGPU_mV/100)%10 : (realGPU_mV/10)%100)); //} @@ -930,20 +930,20 @@ public: if (GPU_Hz_int == 0 && lastGPU_Hz_int != 0) { isRendering = false; leventSignal(&renderingStopEvent); - + triggerExitNow = true; return; } lastGPU_Hz_int = GPU_Hz_int; } - + /* ── GPU voltage ───────────────────────────── */ if (settings.realVolts) { const uint32_t mv = realGPU_mV / 1000; snprintf(GPU_volt_c, sizeof(GPU_volt_c), "%u mV", mv); } - + // RAM usage and frequency char MICRO_RAM_all_c[16]; if (!settings.showpartLoad) { @@ -971,23 +971,23 @@ public: const int32_t deltaRAM = (int32_t)(realRAM_Hz / 1000) - (RAM_Hz / 1000); ramDiff = getDifferenceSymbol(deltaRAM); } - + const uint32_t ramFreq = settings.realFrequencies && realRAM_Hz ? realRAM_Hz : RAM_Hz; - snprintf(RAM_var_compressed_c, sizeof(RAM_var_compressed_c), - "%s%s%u.%u", MICRO_RAM_all_c, ramDiff, + snprintf(RAM_var_compressed_c, sizeof(RAM_var_compressed_c), + "%s%s%u.%u", MICRO_RAM_all_c, ramDiff, ramFreq / 1000000, (ramFreq / 100000) % 10); - + if (settings.realTemps && realRAM_Temp != 0) { char temp_buffer[48]; - snprintf(temp_buffer, sizeof(temp_buffer), " %s", RAM_temp_c); + snprintf(temp_buffer, sizeof(temp_buffer), "%s", RAM_temp_c); strncat(RAM_var_compressed_c, temp_buffer, sizeof(RAM_var_compressed_c) - strlen(RAM_var_compressed_c) - 1); } - + //if (settings.realVolts) { // uint32_t vdd2 = realRAM_mV / 10000; // uint32_t vddq = realRAM_mV % 10000; // if (isMariko) { - // snprintf(RAM_volt_c, sizeof(RAM_volt_c), "%u.%u%u.%u mV", + // snprintf(RAM_volt_c, sizeof(RAM_volt_c), "%u.%u%u.%u mV", // vdd2/10, vdd2%10, vddq/10, vddq%10); // } else { // snprintf(RAM_volt_c, sizeof(RAM_volt_c), "%u.%u mV", vdd2/10, vdd2%10); @@ -997,15 +997,15 @@ public: /* ── RAM voltage ───────────────────────────── */ if (settings.realVolts && (settings.showVDD2 || settings.showVDDQ)) { /* realRAM_mV packs VDD2 | VDDQ in 10-µV units * - * → split, convert to mV + * → split, convert to mV */ const float mv_vdd2 = (realRAM_mV % 100000) / 10.0f; // VDD2 const uint32_t mv_vddq = (realRAM_mV / 10000) / 10; // VDDQ - + // Build voltage string based on settings RAM_volt_c[0] = '\0'; // Start with empty string char temp_buffer[16]; - + if (settings.showVDD2) { if (settings.decimalVDD2) { snprintf(temp_buffer, sizeof(temp_buffer), "%.1f mV", mv_vdd2); @@ -1014,7 +1014,7 @@ public: } strcat(RAM_volt_c, temp_buffer); } - + if (settings.showVDDQ && isMariko) { if (RAM_volt_c[0] != '\0') { strcat(RAM_volt_c, ""); @@ -1025,7 +1025,7 @@ public: } else { RAM_volt_c[0] = '\0'; // Empty if voltages disabled } - + /* ── Battery / power draw ───────────────────────────── */ char remainingBatteryLife[8]; @@ -1075,7 +1075,7 @@ public: "%d°C %d%%", (int)SOC_temperatureF, // SoC °C, no decimals duty); // fan % - + /* Integer SOC, PCB and skin temperatures + duty * * skin_temperaturemiliC is in milli-degrees C → divide by 1000 */ snprintf(skin_temperature_c, sizeof skin_temperature_c, @@ -1084,9 +1084,9 @@ public: (int)PCB_temperatureF, // PCB (uint16_t)(skin_temperaturemiliC / 1000), // skin duty); - + //if (settings.realVolts) { - // snprintf(SOC_volt_c, sizeof(SOC_volt_c), "%u.%u mV", + // snprintf(SOC_volt_c, sizeof(SOC_volt_c), "%u.%u mV", // realSOC_mV/1000, (realSOC_mV/100)%10); //} @@ -1097,16 +1097,16 @@ public: } else { SOC_volt_c[0] = '\0'; // Clear the buffer when disabled } - + if (settings.realTemps) { if (realCPU_Temp != 0) { - snprintf(CPU_temp_c, sizeof(CPU_temp_c), " %.1f°C", realCPU_Temp / 1000.0f); + snprintf(CPU_temp_c, sizeof(CPU_temp_c), "%.1f°C", realCPU_Temp / 1000.0f); } if (realGPU_Temp != 0) { - snprintf(GPU_temp_c, sizeof(GPU_temp_c), " %.1f°C", realGPU_Temp / 1000.0f); + snprintf(GPU_temp_c, sizeof(GPU_temp_c), "%.1f°C", realGPU_Temp / 1000.0f); } if (realRAM_Temp != 0) { - snprintf(RAM_temp_c, sizeof(RAM_temp_c), " %.1f°C", realRAM_Temp / 1000.0f); + snprintf(RAM_temp_c, sizeof(RAM_temp_c), "%.1f°C", realRAM_Temp / 1000.0f); } } @@ -1176,20 +1176,20 @@ public: m_resolutionOutput[out_iter].width = m_resolutionViewportCalls[x].width; m_resolutionOutput[out_iter].height = m_resolutionViewportCalls[x].height; m_resolutionOutput[out_iter].calls = m_resolutionViewportCalls[x].calls; - out_iter++; + out_iter++; } found = false; if (out_iter == 8) break; } } qsort(m_resolutionOutput, 8, sizeof(resolutionCalls), compare); - + // Anti-flicker swap logic static std::pair old_res[2]; - + // Only swap if BOTH resolutions exist (prevent swapping with empty slot) if (m_resolutionOutput[0].width && m_resolutionOutput[1].width) { - if ((m_resolutionOutput[0].width == old_res[1].first && m_resolutionOutput[0].height == old_res[1].second) || + if ((m_resolutionOutput[0].width == old_res[1].first && m_resolutionOutput[0].height == old_res[1].second) || (m_resolutionOutput[1].width == old_res[0].first && m_resolutionOutput[1].height == old_res[0].second)) { const uint16_t swap_width = m_resolutionOutput[0].width; const uint16_t swap_height = m_resolutionOutput[0].height; @@ -1199,31 +1199,31 @@ public: m_resolutionOutput[1].height = swap_height; } } - + // Format resolution string if (m_resolutionOutput[0].width) { if (settings.showFullResolution) { if (!m_resolutionOutput[1].width) { - snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dx%d", + snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dx%d", m_resolutionOutput[0].width, m_resolutionOutput[0].height); } else { - snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dx%d%dx%d", - m_resolutionOutput[0].width, m_resolutionOutput[0].height, + snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dx%d%dx%d", + m_resolutionOutput[0].width, m_resolutionOutput[0].height, m_resolutionOutput[1].width, m_resolutionOutput[1].height); } } else { if (!m_resolutionOutput[1].width) { - snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dp", + snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dp", m_resolutionOutput[0].height); } else { - snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dp%dp", + snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dp%dp", m_resolutionOutput[0].height, m_resolutionOutput[1].height); } } } - + // Always store current resolutions for next frame comparison old_res[0] = std::make_pair(m_resolutionOutput[0].width, m_resolutionOutput[0].height); old_res[1] = std::make_pair(m_resolutionOutput[1].width, m_resolutionOutput[1].height); @@ -1253,7 +1253,7 @@ public: mutexUnlock(&mutex_Misc); //static bool skipOnce = true; - + if (!skipOnce) { //static bool runOnce = true; if (runOnce) { @@ -1265,7 +1265,7 @@ public: skipOnce = false; } } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (isKeyComboPressed(keysHeld, keysDown)) { isRendering = false;