diff --git a/Source/hoc-clk/build.sh b/Source/hoc-clk/build.sh index 74634101..70b045d0 100755 --- a/Source/hoc-clk/build.sh +++ b/Source/hoc-clk/build.sh @@ -53,7 +53,11 @@ echo echo "*** Copying assets ***" mkdir -p "$DIST_DIR/config/horizon-oc" -cp -vf "$ROOT_DIR/config.ini.template" "$DIST_DIR/config/horizon-oc/config.ini.template" +cp -vf "$ROOT_DIR/templates/settings.ini.template" "$DIST_DIR/config/horizon-oc/settings.ini.template" +mkdir -p "$DIST_DIR/config/horizon-oc/kip" +cp -vf "$ROOT_DIR/templates/kip/current.ini.template" "$DIST_DIR/config/horizon-oc/kip/current.ini.template" +mkdir -p "$DIST_DIR/config/horizon-oc/profiles" +cp -vf "$ROOT_DIR/templates/profiles/README.ini" "$DIST_DIR/config/horizon-oc/profiles/README.ini" mkdir -p "$DIST_DIR/config/ultrahand/assets/notifications" cp -vf "$ROOT_DIR/assets/hoc.rgba" "$DIST_DIR/config/ultrahand/assets/notifications/hoc.rgba" diff --git a/Source/hoc-clk/common/include/hocclk/config.h b/Source/hoc-clk/common/include/hocclk/config.h index c9262f81..e1d411dc 100644 --- a/Source/hoc-clk/common/include/hocclk/config.h +++ b/Source/hoc-clk/common/include/hocclk/config.h @@ -555,6 +555,11 @@ static inline const char* hocclkFormatConfigValue(HocClkConfigValue val, bool pr } } +static inline bool hocclkIsKipConfigValue(HocClkConfigValue val) +{ + return val >= KipConfigValue_custRev && val <= KipCrc32; +} + static inline uint64_t hocclkDefaultConfigValue(HocClkConfigValue val) { switch(val) diff --git a/Source/hoc-clk/config.ini.template b/Source/hoc-clk/config.ini.template deleted file mode 100644 index 4305c725..00000000 --- a/Source/hoc-clk/config.ini.template +++ /dev/null @@ -1,19 +0,0 @@ -[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 -; Overclock MEM to docked clocks when handheld -;[01007EF00011E000] -;docked_cpu=1224 -;handheld_mem=1600 - -; Example #2: Picross -; Underclock to save battery -;[0100BA0003EEA000] -;handheld_cpu=816 -;handheld_gpu=153 -;handheld_mem=800 \ No newline at end of file diff --git a/Source/hoc-clk/sysmodule/src/file/config.cpp b/Source/hoc-clk/sysmodule/src/file/config.cpp index 018571a5..e36d8013 100644 --- a/Source/hoc-clk/sysmodule/src/file/config.cpp +++ b/Source/hoc-clk/sysmodule/src/file/config.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -55,30 +56,37 @@ namespace config { namespace { bool gLoaded = false; - std::string gPath; - time_t gMtime = 0; + std::string gSettingsPath; + std::string gKipPath; + std::string gProfilesDir; + std::string gLegacyPath; + time_t gSettingsMtime = 0; + time_t gKipMtime = 0; std::atomic_bool gEnabled{ false }; std::uint32_t gOverrideFreqs[HocClkModule_EnumMax]; std::map, std::uint32_t> gProfileMHzMap; std::map gProfileCountMap; + std::set gLoadedTids; + std::map gProfileMtimes; + std::uint64_t gCurrentGameTid = 0; + bool gProfilesDirty = false; + bool gMigrationHappened = false; LockableMutex gConfigMutex; LockableMutex gOverrideMutex; - time_t CheckModificationTime() { + time_t CheckFileMtime(const std::string &path) { time_t mtime = 0; struct stat st; - if (stat(gPath.c_str(), &st) == 0) { + if (stat(path.c_str(), &st) == 0) { mtime = st.st_mtime; } return mtime; } std::uint32_t FindClockMHz(std::uint64_t tid, HocClkModule module, HocClkProfile profile) { - if (gLoaded) { - auto it = gProfileMHzMap.find(std::make_tuple(tid, profile, module)); - if (it != gProfileMHzMap.end()) { - return it->second; - } + auto it = gProfileMHzMap.find(std::make_tuple(tid, profile, module)); + if (it != gProfileMHzMap.end()) { + return it->second; } return 0; } @@ -99,33 +107,59 @@ namespace config { return std::max((std::uint32_t)0, mhz * mhzMultiplier); } - int BrowseIniFunc(const char *section, const char *key, const char *value, void *userdata) { + int BrowseSettingsIni(const char *section, const char *key, const char *value, void *userdata) { (void)userdata; - std::uint64_t input; - if (!strcmp(section, CONFIG_VAL_SECTION)) { - for (unsigned int kval = 0; kval < HocClkConfigValue_EnumMax; kval++) { - if (!strcmp(key, hocclkFormatConfigValue((HocClkConfigValue)kval, false))) { - input = strtoul(value, NULL, 0); - if (!hocclkValidConfigValue((HocClkConfigValue)kval, input)) { - input = hocclkDefaultConfigValue((HocClkConfigValue)kval); - fileUtils::LogLine("[cfg] Invalid value for key '%s' in section '%s': using default %d", key, section, input); - } - configValues[kval] = input; - return 1; - } + if (strcmp(section, CONFIG_VAL_SECTION) != 0) { + return 1; + } + + for (unsigned int kval = 0; kval < HocClkConfigValue_EnumMax; kval++) { + if (hocclkIsKipConfigValue((HocClkConfigValue)kval)) { + continue; } + if (!strcmp(key, hocclkFormatConfigValue((HocClkConfigValue)kval, false))) { + std::uint64_t input = strtoul(value, NULL, 0); + if (!hocclkValidConfigValue((HocClkConfigValue)kval, input)) { + input = hocclkDefaultConfigValue((HocClkConfigValue)kval); + fileUtils::LogLine("[cfg] Invalid value for key '%s': using default %llu", key, input); + } + configValues[kval] = input; + return 1; + } + } - fileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Unrecognized config value", key, section); + fileUtils::LogLine("[cfg] Skipping key '%s' in settings: Unrecognized config value", key); + return 1; + } + + int BrowseKipIni(const char *section, const char *key, const char *value, void *userdata) { + (void)userdata; + if (strcmp(section, CONFIG_VAL_SECTION) != 0) { 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; + for (unsigned int kval = 0; kval < HocClkConfigValue_EnumMax; kval++) { + if (!hocclkIsKipConfigValue((HocClkConfigValue)kval)) { + continue; + } + if (!strcmp(key, hocclkFormatConfigValue((HocClkConfigValue)kval, false))) { + std::uint64_t input = strtoul(value, NULL, 0); + if (!hocclkValidConfigValue((HocClkConfigValue)kval, input)) { + input = hocclkDefaultConfigValue((HocClkConfigValue)kval); + fileUtils::LogLine("[cfg] Invalid value for key '%s': using default %llu", key, input); + } + configValues[kval] = input; + return 1; + } } + fileUtils::LogLine("[cfg] Skipping key '%s' in kip: Unrecognized config value", key); + return 1; + } + + int BrowseProfileIni(const char *section, const char *key, const char *value, void *userdata) { + std::uint64_t tid = (std::uint64_t)(uintptr_t)userdata; + HocClkProfile parsedProfile = HocClkProfile_EnumMax; HocClkModule parsedModule = HocClkModule_EnumMax; @@ -148,13 +182,11 @@ namespace config { } if (parsedModule == HocClkModule_EnumMax || parsedProfile == HocClkProfile_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; } @@ -173,33 +205,305 @@ namespace config { gLoaded = false; gProfileMHzMap.clear(); gProfileCountMap.clear(); + gLoadedTids.clear(); + gProfileMtimes.clear(); + gCurrentGameTid = 0; + gProfilesDirty = false; for (unsigned int i = 0; i < HocClkConfigValue_EnumMax; i++) { configValues[i] = hocclkDefaultConfigValue((HocClkConfigValue)i); } } - void Load() { - fileUtils::LogLine("[cfg] Reading %s", gPath.c_str()); + void LoadSettings() { + fileUtils::LogLine("[cfg] Reading %s", gSettingsPath.c_str()); + gSettingsMtime = CheckFileMtime(gSettingsPath); + if (!gSettingsMtime) { + fileUtils::LogLine("[cfg] Settings file not found, using defaults"); + return; + } + if (!ini_browse(&BrowseSettingsIni, nullptr, gSettingsPath.c_str())) { + fileUtils::LogLine("[cfg] Error loading settings file"); + } + } - Close(); - gMtime = CheckModificationTime(); - if (!gMtime) { - fileUtils::LogLine("[cfg] Error finding file"); - } else if (!ini_browse(&BrowseIniFunc, nullptr, gPath.c_str())) { - fileUtils::LogLine("[cfg] Error loading file"); + void LoadKip() { + fileUtils::LogLine("[cfg] Reading %s", gKipPath.c_str()); + gKipMtime = CheckFileMtime(gKipPath); + if (!gKipMtime) { + fileUtils::LogLine("[cfg] KIP config file not found, using defaults"); + return; + } + if (!ini_browse(&BrowseKipIni, nullptr, gKipPath.c_str())) { + fileUtils::LogLine("[cfg] Error loading KIP config file"); + } + } + + std::string ProfilePathForTid(std::uint64_t tid) { + char filename[32]; + snprintf(filename, sizeof(filename), "%016lX.ini", tid); + return gProfilesDir + "/" + filename; + } + + void EvictProfile(std::uint64_t tid) { + if (tid == HOCCLK_GLOBAL_PROFILE_TID) { + return; } + for (unsigned int profile = 0; profile < HocClkProfile_EnumMax; profile++) { + for (unsigned int module = 0; module < HocClkModule_EnumMax; module++) { + gProfileMHzMap.erase(std::make_tuple(tid, (HocClkProfile)profile, (HocClkModule)module)); + } + } + gProfileCountMap.erase(tid); + gProfileMtimes.erase(tid); + gLoadedTids.erase(tid); + } + + void LoadProfileTid(std::uint64_t tid) { + if (tid == 0) { + return; + } + + if (gLoadedTids.count(tid)) { + return; + } + + if (tid != HOCCLK_GLOBAL_PROFILE_TID && gCurrentGameTid != 0) { + EvictProfile(gCurrentGameTid); + } + + std::string path = ProfilePathForTid(tid); + + struct stat st; + if (stat(path.c_str(), &st) == 0) { + fileUtils::LogLine("[cfg] Loading profile %s", path.c_str()); + ini_browse(&BrowseProfileIni, (void *)(uintptr_t)tid, path.c_str()); + gProfileMtimes[tid] = st.st_mtime; + } else { + gProfileMtimes.erase(tid); + } + gLoadedTids.insert(tid); + if (tid != HOCCLK_GLOBAL_PROFILE_TID) { + gCurrentGameTid = tid; + } + } + + void Load() { + gLoaded = false; + for (unsigned int i = 0; i < HocClkConfigValue_EnumMax; i++) { + configValues[i] = hocclkDefaultConfigValue((HocClkConfigValue)i); + } + LoadSettings(); + LoadKip(); gLoaded = true; } + struct MigrationData { + std::vector> settingsKeys; + std::vector> kipKeys; + std::vector>> profileEntries; + }; + + int MigrationBrowseFunc(const char *section, const char *key, const char *value, void *userdata) { + MigrationData *data = static_cast(userdata); + + if (!strcmp(section, CONFIG_VAL_SECTION)) { + for (unsigned int kval = 0; kval < HocClkConfigValue_EnumMax; kval++) { + if (!strcmp(key, hocclkFormatConfigValue((HocClkConfigValue)kval, false))) { + if (hocclkIsKipConfigValue((HocClkConfigValue)kval)) { + data->kipKeys.push_back({key, value}); + return 1; + } + data->settingsKeys.push_back({key, value}); + return 1; + } + } + fileUtils::LogLine("[cfg] Skipping unrecognized key '%s'", key); + } else { + std::uint64_t tid = strtoul(section, NULL, 16); + if (tid && strlen(section) == 16) { + data->profileEntries.push_back({tid, {key, value}}); + } + } + return 1; + } + + void MigrateLegacyConfig() { + struct stat st; + if (stat(gLegacyPath.c_str(), &st) != 0) { + return; + } + + fileUtils::LogLine("[cfg] Migrating legacy config.ini"); + fileUtils::LogLine("[cfg] Legacy path: %s", gLegacyPath.c_str()); + fileUtils::LogLine("[cfg] Settings path: %s", gSettingsPath.c_str()); + fileUtils::LogLine("[cfg] Profiles dir: %s", gProfilesDir.c_str()); + + MigrationData data; + fileUtils::LogLine("[cfg] Parsing legacy config.ini..."); + ini_browse(&MigrationBrowseFunc, &data, gLegacyPath.c_str()); + fileUtils::LogLine("[cfg] Found %zu settings keys, %zu KIP keys, %zu profile entries", + data.settingsKeys.size(), data.kipKeys.size(), data.profileEntries.size()); + + if (!data.settingsKeys.empty()) { + fileUtils::LogLine("[cfg] Creating kip directory: %s", FILE_KIP_DIR); + mkdir(FILE_KIP_DIR, 0777); + fileUtils::LogLine("[cfg] Writing %zu settings to %s", data.settingsKeys.size(), gSettingsPath.c_str()); + + std::vector sKeys; + std::vector sVals; + sKeys.reserve(data.settingsKeys.size()); + sVals.reserve(data.settingsKeys.size()); + for (const auto &entry : data.settingsKeys) { + fileUtils::LogLine("[cfg] %s = %s", entry.first.c_str(), entry.second.c_str()); + sKeys.push_back(entry.first); + sVals.push_back(entry.second); + } + + std::vector keyPtrs; + std::vector valPtrs; + keyPtrs.reserve(sKeys.size() + 1); + valPtrs.reserve(sVals.size() + 1); + for (size_t i = 0; i < sKeys.size(); i++) { + keyPtrs.push_back(sKeys[i].c_str()); + valPtrs.push_back(sVals[i].c_str()); + } + keyPtrs.push_back(NULL); + valPtrs.push_back(NULL); + + if (!ini_putsection(CONFIG_VAL_SECTION, keyPtrs.data(), valPtrs.data(), gSettingsPath.c_str())) { + fileUtils::LogLine("[cfg] FAILED to write settings section"); + } else { + fileUtils::LogLine("[cfg] Settings migration done"); + } + } else { + fileUtils::LogLine("[cfg] No settings keys to migrate"); + } + + if (!data.kipKeys.empty()) { + fileUtils::LogLine("[cfg] Writing %zu KIP keys to %s", data.kipKeys.size(), gKipPath.c_str()); + + std::vector kKeys; + std::vector kVals; + kKeys.reserve(data.kipKeys.size()); + kVals.reserve(data.kipKeys.size()); + for (const auto &entry : data.kipKeys) { + fileUtils::LogLine("[cfg] %s = %s", entry.first.c_str(), entry.second.c_str()); + kKeys.push_back(entry.first); + kVals.push_back(entry.second); + } + + std::vector kKeyPtrs; + std::vector kValPtrs; + kKeyPtrs.reserve(kKeys.size() + 1); + kValPtrs.reserve(kVals.size() + 1); + for (size_t i = 0; i < kKeys.size(); i++) { + kKeyPtrs.push_back(kKeys[i].c_str()); + kValPtrs.push_back(kVals[i].c_str()); + } + kKeyPtrs.push_back(NULL); + kValPtrs.push_back(NULL); + + if (!ini_putsection(CONFIG_VAL_SECTION, kKeyPtrs.data(), kValPtrs.data(), gKipPath.c_str())) { + fileUtils::LogLine("[cfg] FAILED to write KIP section"); + } else { + fileUtils::LogLine("[cfg] KIP migration done"); + } + + for (const auto &entry : data.kipKeys) { + for (unsigned int kval = 0; kval < HocClkConfigValue_EnumMax; kval++) { + if (!hocclkIsKipConfigValue((HocClkConfigValue)kval)) { + continue; + } + if (!strcmp(entry.first.c_str(), hocclkFormatConfigValue((HocClkConfigValue)kval, false))) { + std::uint64_t input = strtoul(entry.second.c_str(), NULL, 0); + if (hocclkValidConfigValue((HocClkConfigValue)kval, input)) { + configValues[kval] = input; + } + break; + } + } + } + } else { + fileUtils::LogLine("[cfg] No KIP keys to migrate"); + } + + fileUtils::LogLine("[cfg] Creating profiles directory: %s", FILE_PROFILES_DIR); + mkdir(FILE_PROFILES_DIR, 0777); + + std::map>> profileBuckets; + for (const auto &entry : data.profileEntries) { + profileBuckets[entry.first].push_back(entry.second); + } + + fileUtils::LogLine("[cfg] Writing %zu profiles (%zu TIDs)", data.profileEntries.size(), profileBuckets.size()); + for (const auto &bucket : profileBuckets) { + char filename[32]; + snprintf(filename, sizeof(filename), "%016lX.ini", bucket.first); + std::string profilePath = gProfilesDir + "/" + filename; + + fileUtils::LogLine("[cfg] TID=%016lX %zu keys -> %s", bucket.first, bucket.second.size(), profilePath.c_str()); + + std::vector pKeys; + std::vector pVals; + pKeys.reserve(bucket.second.size()); + pVals.reserve(bucket.second.size()); + for (const auto &kv : bucket.second) { + fileUtils::LogLine("[cfg] %s = %s", kv.first.c_str(), kv.second.c_str()); + pKeys.push_back(kv.first); + pVals.push_back(kv.second); + } + + std::vector keyPtrs; + std::vector valPtrs; + keyPtrs.reserve(pKeys.size() + 1); + valPtrs.reserve(pVals.size() + 1); + for (size_t i = 0; i < pKeys.size(); i++) { + keyPtrs.push_back(pKeys[i].c_str()); + valPtrs.push_back(pVals[i].c_str()); + } + keyPtrs.push_back(NULL); + valPtrs.push_back(NULL); + + if (!ini_putsection(CONFIG_VAL_SECTION, keyPtrs.data(), valPtrs.data(), profilePath.c_str())) { + fileUtils::LogLine("[cfg] FAILED to write profile for TID %016lX", bucket.first); + } + } + fileUtils::LogLine("[cfg] Profiles migration done"); + + fileUtils::LogLine("[cfg] Renaming legacy config to config.ini.migrated"); + if (rename(gLegacyPath.c_str(), (gLegacyPath + ".migrated").c_str()) != 0) { + fileUtils::LogLine("[cfg] WARNING: Failed to rename legacy config file"); + } + gMigrationHappened = true; + fileUtils::LogLine("[cfg] Migration complete"); + } + } // namespace + bool IsProfileDirty() { + return gProfilesDirty; + } + + void SetProfileDirty(bool dirty) { + gProfilesDirty = dirty; + } + void Initialize() { - gPath = FILE_CONFIG_DIR "/config.ini"; + gSettingsPath = FILE_SETTINGS_PATH; + gKipPath = FILE_KIP_CONFIG_PATH; + gProfilesDir = FILE_PROFILES_DIR; + gLegacyPath = FILE_LEGACY_CONFIG_PATH; gLoaded = false; + gMigrationHappened = false; gProfileMHzMap.clear(); gProfileCountMap.clear(); - gMtime = 0; + gLoadedTids.clear(); + gProfileMtimes.clear(); + gCurrentGameTid = 0; + gProfilesDirty = false; + gSettingsMtime = 0; + gKipMtime = 0; gEnabled = false; for (unsigned int i = 0; i < HocClkModule_EnumMax; i++) { gOverrideFreqs[i] = 0; @@ -207,6 +511,15 @@ namespace config { for (unsigned int i = 0; i < HocClkConfigValue_EnumMax; i++) { configValues[i] = hocclkDefaultConfigValue((HocClkConfigValue)i); } + + mkdir(FILE_CONFIG_DIR, 0777); + mkdir(FILE_KIP_DIR, 0777); + mkdir(FILE_PROFILES_DIR, 0777); + + MigrateLegacyConfig(); + + gLoaded = true; + LoadProfileTid(HOCCLK_GLOBAL_PROFILE_TID); } void Exit() { @@ -216,11 +529,38 @@ namespace config { bool Refresh() { std::scoped_lock lock{ gConfigMutex }; - if (!gLoaded || gMtime != CheckModificationTime()) { + time_t settingsMtime = CheckFileMtime(gSettingsPath); + time_t kipMtime = CheckFileMtime(gKipPath); + if (!gLoaded || gSettingsMtime != settingsMtime || gKipMtime != kipMtime) { Load(); return true; } - return false; + if (IsProfileDirty()) { + SetProfileDirty(false); + return true; + } + + std::vector staleTids; + for (std::uint64_t tid : gLoadedTids) { + time_t mtime = CheckFileMtime(ProfilePathForTid(tid)); + auto it = gProfileMtimes.find(tid); + time_t known = (it != gProfileMtimes.end()) ? it->second : 0; + if (mtime != known) { + staleTids.push_back(tid); + } + } + for (std::uint64_t tid : staleTids) { + fileUtils::LogLine("[cfg] Profile %016lX changed on disk, dropping cache", tid); + for (unsigned int profile = 0; profile < HocClkProfile_EnumMax; profile++) { + for (unsigned int module = 0; module < HocClkModule_EnumMax; module++) { + gProfileMHzMap.erase(std::make_tuple(tid, (HocClkProfile)profile, (HocClkModule)module)); + } + } + gProfileCountMap.erase(tid); + gProfileMtimes.erase(tid); + gLoadedTids.erase(tid); + } + return !staleTids.empty(); } bool HasProfilesLoaded() { @@ -230,6 +570,9 @@ namespace config { std::uint32_t GetAutoClockHz(std::uint64_t tid, HocClkModule module, HocClkProfile profile, bool returnRaw) { std::scoped_lock lock{ gConfigMutex }; + if (gLoaded && !gLoadedTids.count(tid)) { + LoadProfileTid(tid); + } switch (profile) { case HocClkProfile_Handheld: return FindClockHzFromProfiles(tid, module, { HocClkProfile_Handheld }, returnRaw ? 1 : 1000000); @@ -252,6 +595,7 @@ namespace config { void GetProfiles(std::uint64_t tid, HocClkTitleProfileList *out_profiles) { std::scoped_lock lock{ gConfigMutex }; + LoadProfileTid(tid); for (unsigned int profile = 0; profile < HocClkProfile_EnumMax; profile++) { for (unsigned int module = 0; module < HocClkModule_EnumMax; module++) { out_profiles->mhzMap[profile][module] = FindClockMHz(tid, (HocClkModule)module, (HocClkProfile)profile); @@ -261,10 +605,12 @@ namespace config { bool SetProfiles(std::uint64_t tid, HocClkTitleProfileList *profiles, bool immediate) { std::scoped_lock lock{ gConfigMutex }; - uint8_t numProfiles = 0; - char section[17] = { 0 }; - snprintf(section, sizeof(section), "%016lX", tid); + mkdir(FILE_PROFILES_DIR, 0777); + + std::string profilePath = ProfilePathForTid(tid); + + uint8_t numProfiles = 0; std::vector keys; std::vector values; @@ -301,10 +647,18 @@ namespace config { keyPointers.push_back(NULL); valuePointers.push_back(NULL); - if (!ini_putsection(section, keyPointers.data(), valuePointers.data(), gPath.c_str())) { + if (keys.empty()) { + struct stat st; + if (stat(profilePath.c_str(), &st) == 0 && remove(profilePath.c_str()) != 0) { + fileUtils::LogLine("[cfg] Failed to remove empty profile %s", profilePath.c_str()); + return false; + } + } else if (!ini_putsection(CONFIG_VAL_SECTION, keyPointers.data(), valuePointers.data(), profilePath.c_str())) { return false; } + gProfileMtimes[tid] = CheckFileMtime(profilePath); + if (immediate) { mhz = &profiles->mhz[0]; gProfileCountMap[tid] = numProfiles; @@ -318,12 +672,22 @@ namespace config { mhz++; } } + gLoadedTids.insert(tid); + if (tid != HOCCLK_GLOBAL_PROFILE_TID) { + gCurrentGameTid = tid; + } + + SetProfileDirty(true); } return true; } std::uint8_t GetProfileCount(std::uint64_t tid) { + std::scoped_lock lock{ gConfigMutex }; + if (tid != 0 && gLoaded && !gLoadedTids.count(tid)) { + LoadProfileTid(tid); + } auto it = gProfileCountMap.find(tid); if (it == gProfileCountMap.end()) { return 0; @@ -339,6 +703,10 @@ namespace config { return gEnabled; } + bool MigrationHappened() { + return gMigrationHappened; + } + void SetOverrideHz(HocClkModule module, std::uint32_t hz) { ASSERT_ENUM_VALID(HocClkModule, module); std::scoped_lock lock{ gOverrideMutex }; @@ -372,31 +740,60 @@ namespace config { bool SetConfigValues(HocClkConfigValueList *configValues, bool immediate) { std::scoped_lock lock{ gConfigMutex }; - std::vector iniKeys; - std::vector iniValues; - iniKeys.reserve(HocClkConfigValue_EnumMax + 1); - iniValues.reserve(HocClkConfigValue_EnumMax); + std::vector settingsKeys; + std::vector settingsValues; + std::vector kipKeys; + std::vector kipValues; for (unsigned int kval = 0; kval < HocClkConfigValue_EnumMax; kval++) { if (!hocclkValidConfigValue((HocClkConfigValue)kval, configValues->values[kval]) || configValues->values[kval] == hocclkDefaultConfigValue((HocClkConfigValue)kval)) { continue; } - iniValues.push_back(std::to_string(configValues->values[kval])); - iniKeys.push_back(hocclkFormatConfigValue((HocClkConfigValue)kval, false)); + std::string key = hocclkFormatConfigValue((HocClkConfigValue)kval, false); + std::string value = std::to_string(configValues->values[kval]); + + if (hocclkIsKipConfigValue((HocClkConfigValue)kval)) { + kipKeys.push_back(key); + kipValues.push_back(value); + } else { + settingsKeys.push_back(key); + settingsValues.push_back(value); + } } - iniKeys.push_back(NULL); + bool success = true; - std::vector valuePointers; - valuePointers.reserve(iniValues.size() + 1); - for (const auto &val : iniValues) { - valuePointers.push_back(val.c_str()); + if (!settingsKeys.empty()) { + std::vector sKeyPtrs; + std::vector sValPtrs; + sKeyPtrs.reserve(settingsKeys.size() + 1); + sValPtrs.reserve(settingsValues.size() + 1); + for (size_t i = 0; i < settingsKeys.size(); i++) { + sKeyPtrs.push_back(settingsKeys[i].c_str()); + sValPtrs.push_back(settingsValues[i].c_str()); + } + sKeyPtrs.push_back(NULL); + sValPtrs.push_back(NULL); + if (!ini_putsection(CONFIG_VAL_SECTION, sKeyPtrs.data(), sValPtrs.data(), gSettingsPath.c_str())) { + success = false; + } } - valuePointers.push_back(NULL); - if (!ini_putsection(CONFIG_VAL_SECTION, iniKeys.data(), valuePointers.data(), gPath.c_str())) { - return false; + if (!kipKeys.empty()) { + std::vector kKeyPtrs; + std::vector kValPtrs; + kKeyPtrs.reserve(kipKeys.size() + 1); + kValPtrs.reserve(kipValues.size() + 1); + for (size_t i = 0; i < kipKeys.size(); i++) { + kKeyPtrs.push_back(kipKeys[i].c_str()); + kValPtrs.push_back(kipValues[i].c_str()); + } + kKeyPtrs.push_back(NULL); + kValPtrs.push_back(NULL); + if (!ini_putsection(CONFIG_VAL_SECTION, kKeyPtrs.data(), kValPtrs.data(), gKipPath.c_str())) { + success = false; + } } if (immediate) { @@ -407,9 +804,10 @@ namespace config { config::configValues[kval] = hocclkDefaultConfigValue((HocClkConfigValue)kval); } } + SetProfileDirty(true); } - return true; + return success; } bool ResetConfigValue(HocClkConfigValue kval) { @@ -438,13 +836,16 @@ namespace config { } valuePointers.push_back(NULL); - if (!ini_putsection(CONFIG_VAL_SECTION, iniKeys.data(), valuePointers.data(), gPath.c_str())) { + const char *targetPath = hocclkIsKipConfigValue(kval) ? gKipPath.c_str() : gSettingsPath.c_str(); + + if (!ini_putsection(CONFIG_VAL_SECTION, iniKeys.data(), valuePointers.data(), targetPath)) { fileUtils::LogLine("[cfg] Failed to reset config value %u in INI", kval); return false; } configValues[kval] = defaultValue; fileUtils::LogLine("[cfg] Reset config value %u to default: %llu", kval, defaultValue); + SetProfileDirty(true); return true; } @@ -473,12 +874,15 @@ namespace config { valuePointers.push_back(iniValues[0].c_str()); valuePointers.push_back(NULL); - if (!ini_putsection(CONFIG_VAL_SECTION, iniKeys.data(), valuePointers.data(), gPath.c_str())) { + const char *targetPath = hocclkIsKipConfigValue(kval) ? gKipPath.c_str() : gSettingsPath.c_str(); + + if (!ini_putsection(CONFIG_VAL_SECTION, iniKeys.data(), valuePointers.data(), targetPath)) { return false; } if (immediate) { configValues[kval] = value; + SetProfileDirty(true); } return true; @@ -486,6 +890,7 @@ namespace config { void DeleteKey(const char *section, const char *key) { std::scoped_lock lock{ gConfigMutex }; - ini_puts(section, key, NULL, gPath.c_str()); + ini_puts(section, key, NULL, gSettingsPath.c_str()); + ini_puts(section, key, NULL, gKipPath.c_str()); } } // namespace config diff --git a/Source/hoc-clk/sysmodule/src/file/config.hpp b/Source/hoc-clk/sysmodule/src/file/config.hpp index 969fa24b..3ea6fc5d 100644 --- a/Source/hoc-clk/sysmodule/src/file/config.hpp +++ b/Source/hoc-clk/sysmodule/src/file/config.hpp @@ -26,6 +26,8 @@ #pragma once +#include +#include #include #include @@ -39,6 +41,9 @@ namespace config { bool Refresh(); bool HasProfilesLoaded(); + bool IsProfileDirty(); + void SetProfileDirty(bool dirty); + std::uint8_t GetProfileCount(std::uint64_t tid); void GetProfiles(std::uint64_t tid, HocClkTitleProfileList *out_profiles); bool SetProfiles(std::uint64_t tid, HocClkTitleProfileList *profiles, bool immediate); @@ -46,6 +51,7 @@ namespace config { void SetEnabled(bool enabled); bool Enabled(); + bool MigrationHappened(); void SetOverrideHz(HocClkModule module, std::uint32_t hz); std::uint32_t GetOverrideHz(HocClkModule module); diff --git a/Source/hoc-clk/sysmodule/src/file/file_utils.hpp b/Source/hoc-clk/sysmodule/src/file/file_utils.hpp index f2ab4b2a..954f8889 100644 --- a/Source/hoc-clk/sysmodule/src/file/file_utils.hpp +++ b/Source/hoc-clk/sysmodule/src/file/file_utils.hpp @@ -39,6 +39,11 @@ #define FILE_LOG_FLAG_PATH FILE_CONFIG_DIR "/log.flag" #define FILE_UART_FLAG_PATH FILE_CONFIG_DIR "/uart.flag" #define FILE_LOG_FILE_PATH FILE_CONFIG_DIR "/log.txt" +#define FILE_SETTINGS_PATH FILE_CONFIG_DIR "/settings.ini" +#define FILE_KIP_DIR FILE_CONFIG_DIR "/kip" +#define FILE_KIP_CONFIG_PATH FILE_KIP_DIR "/current.ini" +#define FILE_PROFILES_DIR FILE_CONFIG_DIR "/profiles" +#define FILE_LEGACY_CONFIG_PATH FILE_CONFIG_DIR "/config.ini" namespace fileUtils { diff --git a/Source/hoc-clk/sysmodule/src/main.cpp b/Source/hoc-clk/sysmodule/src/main.cpp index 782f2e3c..f7c5b0c1 100644 --- a/Source/hoc-clk/sysmodule/src/main.cpp +++ b/Source/hoc-clk/sysmodule/src/main.cpp @@ -38,7 +38,7 @@ #include "mgr/clock_manager.hpp" -#define INNER_HEAP_SIZE 0x41000 +#define INNER_HEAP_SIZE 0x40000 extern "C" { void virtmemSetup(void); diff --git a/Source/hoc-clk/templates/kip/current.ini.template b/Source/hoc-clk/templates/kip/current.ini.template new file mode 100644 index 00000000..762564df --- /dev/null +++ b/Source/hoc-clk/templates/kip/current.ini.template @@ -0,0 +1 @@ +; Customize values applied to hoc.kip diff --git a/Source/hoc-clk/templates/profiles/README.ini b/Source/hoc-clk/templates/profiles/README.ini new file mode 100644 index 00000000..69c29ac1 --- /dev/null +++ b/Source/hoc-clk/templates/profiles/README.ini @@ -0,0 +1,11 @@ +; Per-title overclock profiles in this folder (one .ini file per game). +; .ini + +; Example: +; BOTW: DRAM OC in handheld for free performance + +; The Legend of Zelda Breath of the Wild (01007EF00011E000) +; File: 01007EF00011E000.ini +; [values] +; handheld_mem=1996 + diff --git a/Source/hoc-clk/templates/settings.ini.template b/Source/hoc-clk/templates/settings.ini.template new file mode 100644 index 00000000..d517e2bf --- /dev/null +++ b/Source/hoc-clk/templates/settings.ini.template @@ -0,0 +1 @@ +; General sysmodule settings