diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index c63120e4fe..e0960cc705 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -494,8 +494,85 @@ bool Emulator::BootRsxCapture(const std::string& path) return true; } +void Emulator::LimitCacheSize() +{ + const std::string cache_location = Emulator::GetHdd1Dir() + "/cache"; + if (!fs::is_dir(cache_location)) + { + LOG_WARNING(GENERAL, "Cache does not exist (%s)", cache_location); + return; + } + + const u64 size = fs::get_dir_size(cache_location); + const u64 max_size = static_cast(g_cfg.vfs.cache_max_size) * 1024 * 1024; + + if (max_size == 0) // Everything must go, so no need to do checks + { + fs::remove_all(cache_location, false); + LOG_SUCCESS(GENERAL, "Cleared disk cache"); + return; + } + + if (size <= max_size) + { + LOG_TRACE(GENERAL, "Cache size below limit: %llu/%llu", size, max_size); + return; + } + + LOG_SUCCESS(GENERAL, "Cleaning disk cache..."); + std::vector file_list{}; + fs::dir cache_dir{}; + if (!cache_dir.open(cache_location)) + { + LOG_ERROR(GENERAL, "Could not open cache directory"); + return; + } + + // retrieve items to delete + for (const auto &item : cache_dir) + { + if (item.name != "." && item.name != "..") + file_list.push_back(item); + } + cache_dir.close(); + + // sort oldest first + std::sort(file_list.begin(), file_list.end(), [](auto left, auto right) + { + return left.mtime < right.mtime; + }); + + // keep removing until cache is empty or enough bytes have been cleared + // cache is cleared down to 80% of limit to increase interval between clears + const u64 to_remove = static_cast(size - max_size * 0.8); + u64 removed = 0; + for (const auto &item : file_list) + { + const std::string &name = cache_location + "/" + item.name; + const u64 item_size = fs::is_dir(name) ? fs::get_dir_size(name) : item.size; + + if (fs::is_dir(name)) + { + fs::remove_all(name, true); + } + else + { + fs::remove_file(name); + } + + removed += item_size; + if (removed >= to_remove) + break; + } + + LOG_SUCCESS(GENERAL, "Cleaned disk cache, removed %.2f MB", size / 1024.0 / 1024.0); +} + bool Emulator::BootGame(const std::string& path, bool direct, bool add_only) { + if (g_cfg.vfs.limit_cache_size) + LimitCacheSize(); + static const char* boot_list[] = { "/PS3_GAME/USRDIR/EBOOT.BIN", @@ -571,6 +648,11 @@ std::string Emulator::GetHddDir() return fmt::replace_all(g_cfg.vfs.dev_hdd0, "$(EmulatorDir)", GetEmuDir()); } +std::string Emulator::GetHdd1Dir() +{ + return fmt::replace_all(g_cfg.vfs.dev_hdd1, "$(EmulatorDir)", GetEmuDir()); +} + std::string Emulator::GetSfoDirFromGamePath(const std::string& game_path, const std::string& user) { if (fs::is_file(game_path + "/PS3_DISC.SFB")) diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index c40b33c7ea..7c5b8f04fb 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -318,6 +318,9 @@ public: private: static std::string GetEmuDir(); + static std::string GetHdd1Dir(); + + void LimitCacheSize(); public: static std::string GetHddDir(); static std::string GetSfoDirFromGamePath(const std::string& game_path, const std::string& user); @@ -405,6 +408,9 @@ struct cfg_root : cfg::node cfg::_bool host_root{this, "Enable /host_root/"}; cfg::_bool init_dirs{this, "Initialize Directories", true}; + cfg::_bool limit_cache_size{this, "Limit disk cache size", false}; + cfg::_int<0, 10240> cache_max_size{this, "Disk cache maximum size (MB)", 5120}; + } vfs{this}; struct node_video : cfg::node diff --git a/rpcs3/Json/tooltips.json b/rpcs3/Json/tooltips.json index c502dffae8..346341a07c 100644 --- a/rpcs3/Json/tooltips.json +++ b/rpcs3/Json/tooltips.json @@ -140,6 +140,7 @@ "system": { "sysLangBox": "Some games may fail to boot if the system language is not available in the game itself.\nOther games will switch language automatically to what is selected here.\nIt is recommended leaving this on a language supported by the game.", "enterButtonAssignment": "The button used for enter/accept/confirm in system dialogs.\nChange this to use the circle button instead, which is the default configuration on japanese systems and in many japanese games.\nIn these cases having the cross button assigned can often lead to confusion.", - "enableHostRoot": "Required for some Homebrew.\nIf unsure, don't use this option." + "enableHostRoot": "Required for some Homebrew.\nIf unsure, don't use this option.", + "limitCacheSize": "Automatically removes older files from disk cache on boot if it grows larger than the specified value.\nGames can use the cache folder to temporarily store data outside of system memory. It is not used for long term storage." } } diff --git a/rpcs3/rpcs3qt/emu_settings.h b/rpcs3/rpcs3qt/emu_settings.h index 0b35dd24b1..1e09e3c62b 100644 --- a/rpcs3/rpcs3qt/emu_settings.h +++ b/rpcs3/rpcs3qt/emu_settings.h @@ -128,6 +128,8 @@ public: Language, EnterButtonAssignment, EnableHostRoot, + LimitCacheSize, + MaximumCacheSize, // Virtual File System emulatorLocation, @@ -330,6 +332,8 @@ private: { Language, { "System", "Language"}}, { EnterButtonAssignment, { "System", "Enter button assignment"}}, { EnableHostRoot, { "VFS", "Enable /host_root/"}}, + { LimitCacheSize, { "VFS", "Limit disk cache size"}}, + { MaximumCacheSize, { "VFS", "Disk cache maximum size (MB)"}}, // Virtual File System { emulatorLocation, { "VFS", "$(EmulatorDir)"}}, diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index adf9ed758e..02bc323261 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -784,6 +784,16 @@ settings_dialog::settings_dialog(std::shared_ptr guiSettings, std: xemu_settings->EnhanceCheckBox(ui->enableHostRoot, emu_settings::EnableHostRoot); SubscribeTooltip(ui->enableHostRoot, json_sys["enableHostRoot"].toString()); + xemu_settings->EnhanceCheckBox(ui->enableCacheClearing, emu_settings::LimitCacheSize); + SubscribeTooltip(ui->enableCacheClearing, json_sys["limitCacheSize"].toString()); + connect(ui->enableCacheClearing, &QCheckBox::stateChanged, ui->maximumCacheSize, &QSlider::setEnabled); + + // Sliders + + EnhanceSlider(emu_settings::MaximumCacheSize, ui->maximumCacheSize, ui->maximumCacheSizeLabel, tr("Maximum size: %0 MB")); + SubscribeTooltip(ui->maximumCacheSize, json_sys["limitCacheSize"].toString()); + ui->maximumCacheSize->setEnabled(ui->enableCacheClearing->isChecked()); + // Radio Buttons SubscribeTooltip(ui->gb_enterButtonAssignment, json_sys["enterButtonAssignment"].toString()); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 76ddbd522f..2063cd0dbb 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -1149,6 +1149,55 @@ + + + + + + Disk cache + + + + + + Clear cache automatically + + + + + + + Cache size: 3072 MB + + + + + + + 512 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 1024 + + + + + + + + + + + + + +