From eb429be1cc7ab1a6f9437338653004b7a0076ab4 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Sun, 5 Apr 2026 20:56:18 +0800 Subject: [PATCH] FS: easy mods folder for games (#4216) * Implement mods folder * Add logging when modified files exist * get mod folder entries in MntPoints::IterateDirectory * support eboot from mods folder --- src/core/file_sys/fs.cpp | 66 +++++++++++++++++++++++++++++++--------- src/core/file_sys/fs.h | 10 +++++- src/emulator.cpp | 10 +++++- 3 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index aa474d20a..8327b51de 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -42,7 +42,7 @@ void MntPoints::UnmountAll() { } std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_read_only, - bool force_base_path) { + HostPathType path_type) { // Evil games like Turok2 pass double slashes e.g /app0//game.kpf std::string corrected_path(path); size_t pos = corrected_path.find("//"); @@ -80,8 +80,24 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea } patch_path /= rel_path; + std::filesystem::path mods_path = mount->host_path; + mods_path += "-mods"; + mods_path /= rel_path; + + if (path_type == HostPathType::Mod) { + return mods_path; + } else if (path_type == HostPathType::Patch) { + return patch_path; + } + if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) && - !force_base_path && !ignore_game_patches && std::filesystem::exists(patch_path)) { + path_type != HostPathType::Base && std::filesystem::exists(mods_path)) { + return mods_path; + } + + if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) && + path_type != HostPathType::Base && !ignore_game_patches && + std::filesystem::exists(patch_path)) { return patch_path; } @@ -143,7 +159,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea return std::optional(current_path); }; - if (!force_base_path && !ignore_game_patches) { + if (path_type != HostPathType::Base && !ignore_game_patches) { if (const auto path = search(patch_path)) { return *path; } @@ -160,34 +176,54 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea // TODO: Does not handle mount points inside mount points. void MntPoints::IterateDirectory(std::string_view guest_directory, const IterateDirectoryCallback& callback) { - const auto base_path = GetHostPath(guest_directory, nullptr, true); - const auto patch_path = GetHostPath(guest_directory, nullptr, false); - // Only need to consider patch path if it exists and does not resolve to the same as base. - const auto apply_patch = base_path != patch_path && std::filesystem::exists(patch_path); + const auto base_path = GetHostPath(guest_directory, nullptr, HostPathType::Base); + + // Forces path types so as not to resolve to base path + const auto patch_path = GetHostPath(guest_directory, nullptr, HostPathType::Patch); + const auto mod_path = GetHostPath(guest_directory, nullptr, HostPathType::Mod); // Prepend entries for . and .., as both are treated as files on PS4. callback(base_path / ".", false); callback(base_path / "..", false); - // Pass 1: Any files that existed in the base directory, using patch directory if needed. + // Pass 1: Any files that existed in the base directory, using mod/patch directory if needed. if (std::filesystem::exists(base_path)) { for (const auto& entry : std::filesystem::directory_iterator(base_path)) { - if (apply_patch) { - const auto patch_entry_path = patch_path / entry.path().filename(); - if (std::filesystem::exists(patch_entry_path)) { - callback(patch_entry_path, !std::filesystem::is_directory(patch_entry_path)); - continue; - } + const auto mod_entry_path = mod_path / entry.path().filename(); + const auto patch_entry_path = patch_path / entry.path().filename(); + if (std::filesystem::exists(mod_entry_path)) { + callback(mod_entry_path, !std::filesystem::is_directory(mod_entry_path)); + continue; + } else if (std::filesystem::exists(patch_entry_path)) { + callback(patch_entry_path, !std::filesystem::is_directory(patch_entry_path)); + continue; } callback(entry.path(), !entry.is_directory()); } } // Pass 2: Any files that exist only in the patch directory. - if (apply_patch) { + if (std::filesystem::exists(patch_path)) { for (const auto& entry : std::filesystem::directory_iterator(patch_path)) { const auto base_entry_path = base_path / entry.path().filename(); if (!std::filesystem::exists(base_entry_path)) { + const auto mod_entry_path = mod_path / entry.path().filename(); + if (std::filesystem::exists(mod_entry_path)) { + callback(mod_entry_path, !std::filesystem::is_directory(mod_entry_path)); + continue; + } + callback(entry.path(), !entry.is_directory()); + } + } + } + + // Pass 3: Any files that exist only in the mod directory (confirmed this can be valid) + if (std::filesystem::exists(mod_path)) { + for (const auto& entry : std::filesystem::directory_iterator(mod_path)) { + const auto base_entry_path = base_path / entry.path().filename(); + const auto patch_entry_path = patch_path / entry.path().filename(); + if (!std::filesystem::exists(base_entry_path) && + !std::filesystem::exists(patch_entry_path)) { callback(entry.path(), !entry.is_directory()); } } diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index 0522c3d8a..9a782b19e 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -36,6 +36,13 @@ public: bool read_only; }; + enum class HostPathType { + Default, // Prioritizes Mod, then patch, then base + Base, + Patch, + Mod + }; + explicit MntPoints() = default; ~MntPoints() = default; @@ -45,7 +52,8 @@ public: void UnmountAll(); std::filesystem::path GetHostPath(std::string_view guest_directory, - bool* is_read_only = nullptr, bool force_base_path = false); + bool* is_read_only = nullptr, + HostPathType host_path = HostPathType::Default); using IterateDirectoryCallback = std::function; void IterateDirectory(std::string_view guest_directory, diff --git a/src/emulator.cpp b/src/emulator.cpp index 616a21ed1..3815c8f3d 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -109,7 +109,8 @@ void Emulator::Run(std::filesystem::path file, std::vector args, } else { game_folder = file.parent_path(); if (const auto game_folder_name = game_folder.filename().string(); - game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch")) { + game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch") || + game_folder_name.ends_with("-mods")) { // If an executable was launched from a separate update directory, // use the base game directory as the game folder. const std::string base_name = game_folder_name.substr(0, game_folder_name.rfind('-')); @@ -292,6 +293,13 @@ void Emulator::Run(std::filesystem::path file, std::vector args, } } + std::filesystem::path mods_folder = game_folder; + mods_folder += "-mods"; + + if (std::filesystem::exists(mods_folder) && !std::filesystem::is_empty(mods_folder)) { + LOG_INFO(Loader, "Files found in game mods folder"); + } + // Create stdin/stdout/stderr Common::Singleton::Instance()->CreateStdHandles();