From c110d0a6387486fff216df60cb98b4ed2d6a9c3a Mon Sep 17 00:00:00 2001 From: Louis Poirier Date: Tue, 4 Jun 2024 14:08:41 +0200 Subject: [PATCH] feat: add feature to blacklist Vortex file. Prevent access from FileSystemStorage. Refactor get_files/get_async_files with an inline template function. Update unit tests. Update README and CHANGELOG. --- CHANGELOG.md | 3 + README.md | 15 ++- .../Test/FileSystemStorageTest.reds | 15 +++ src/FileSystem.cpp | 12 +++ src/FileSystem.h | 2 + src/FileSystemStorage.cpp | 94 ++++++++++--------- src/FileSystemStorage.h | 2 + 7 files changed, 95 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e7ef1..c906762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- blacklist with file `__folder_managed_by_vortex` to hide it and prevent + access from `FileSystemStorage`. ------------------------ diff --git a/README.md b/README.md index 550214b..6e52fb0 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,11 @@ find a C++ API wrapper in [branch api]. ## Usage +All features are defined in module `RedFileSystem`. You need to import it with: +```swift +import RedFileSystem.* +``` + ### Disclaimer All read/write operations are restricted within: > Cyberpunk 2077\r6\storages\ @@ -44,10 +49,12 @@ For example, if your mod is named `Awesome`, your storage will be located in: This is a security measure to prevent malicious access. This way, you and other authors cannot break game files nor the operating system of the player. -All features are defined in module `RedFileSystem`. You need to import it with: -```swift -import RedFileSystem.* -``` +### Blacklist +Prevent access to files managed by other processes. For example, Vortex +adds the file `__folder_managed_by_vortex` when installing a mod. This file +can be present in your storage. However, when using methods from +`FileSystemStorage`, this file will be invisible for you. It won't be listed +either when using `GetFiles()` or `GetAsyncFiles()`. ### FileSystem diff --git a/scripts/RedFileSystem/Test/FileSystemStorageTest.reds b/scripts/RedFileSystem/Test/FileSystemStorageTest.reds index 00c2bf8..9ada37b 100644 --- a/scripts/RedFileSystem/Test/FileSystemStorageTest.reds +++ b/scripts/RedFileSystem/Test/FileSystemStorageTest.reds @@ -20,6 +20,9 @@ public class FileSystemStorageTest extends BaseTest { this.ExpectString("Exists denied", s"\(status)", "Denied"); let status = this.m_storage.Exists("..\\"); + this.ExpectString("Exists denied", s"\(status)", "Denied"); + let status = this.m_storage.Exists("__folder_managed_by_vortex"); + this.ExpectString("Exists denied", s"\(status)", "Denied"); let status = this.m_storage.Exists("unknown.txt"); @@ -32,6 +35,9 @@ public class FileSystemStorageTest extends BaseTest { private cb func Test_IsFile() { let status = this.m_storage.Exists("..\\..\\..\\..\\..\\..\\..\\steam.exe"); + this.ExpectString("IsFile denied", s"\(status)", "Denied"); + let status = this.m_storage.Exists("__folder_managed_by_vortex"); + this.ExpectString("IsFile denied", s"\(status)", "Denied"); let status = this.m_storage.IsFile("test"); @@ -44,6 +50,9 @@ public class FileSystemStorageTest extends BaseTest { private cb func Test_GetFile() { let file = this.m_storage.GetFile("..\\..\\..\\steam.exe"); + this.ExpectBool("GetFile denied", IsDefined(file), false); + let file = this.m_storage.GetFile("__folder_managed_by_vortex"); + this.ExpectBool("GetFile denied", IsDefined(file), false); let file = this.m_storage.GetFile("test.txt"); @@ -75,6 +84,9 @@ public class FileSystemStorageTest extends BaseTest { let file = this.m_storage.GetAsyncFile("..\\..\\..\\steam.exe"); this.ExpectBool("GetAsyncFile denied", IsDefined(file), false); + let file = this.m_storage.GetAsyncFile("__folder_managed_by_vortex"); + + this.ExpectBool("GetFile denied", IsDefined(file), false); let file = this.m_storage.GetAsyncFile("test.txt"); this.ExpectBool("GetAsyncFile valid", IsDefined(file), true); @@ -99,6 +111,9 @@ public class FileSystemStorageTest extends BaseTest { private cb func Test_DeleteFile() { let status = this.m_storage.DeleteFile("..\\..\\..\\..\\..\\..\\..\\steam.exe"); + this.ExpectString("DeleteFile denied", s"\(status)", "Denied"); + let status = this.m_storage.DeleteFile("__folder_managed_by_vortex"); + this.ExpectString("DeleteFile denied", s"\(status)", "Denied"); let status = this.m_storage.DeleteFile("test"); diff --git a/src/FileSystem.cpp b/src/FileSystem.cpp index 868cbb5..a4f7197 100644 --- a/src/FileSystem.cpp +++ b/src/FileSystem.cpp @@ -11,6 +11,7 @@ RED4ext::Logger* FileSystem::logger = nullptr; std::filesystem::path FileSystem::game_path; std::filesystem::path FileSystem::storages_path; bool FileSystem::has_mo2 = false; +std::vector FileSystem::blacklist = {"__folder_managed_by_vortex"}; std::regex FileSystem::storage_name_rule("[A-Za-z]{3,24}"); @@ -132,6 +133,17 @@ bool FileSystem::is_mo2_detected() { return has_mo2; } +bool FileSystem::is_blacklisted(const std::filesystem::path& p_path) { + const std::string filename = p_path.filename().string(); + + for (const auto& bl_filename : blacklist) { + if (equals_insensitive(filename, bl_filename)) { + return true; + } + } + return false; +} + bool FileSystem::request_directory(const std::filesystem::path& p_path) { std::error_code error; bool is_present = std::filesystem::exists(p_path, error); diff --git a/src/FileSystem.h b/src/FileSystem.h index 27e7f1c..835183a 100644 --- a/src/FileSystem.h +++ b/src/FileSystem.h @@ -25,6 +25,7 @@ class FileSystem : public Red::IScriptable { static std::filesystem::path game_path; static std::filesystem::path storages_path; static bool has_mo2; + static std::vector blacklist; static std::regex storage_name_rule; @@ -46,6 +47,7 @@ class FileSystem : public Red::IScriptable { static Red::Handle get_shared_storage(); static bool is_mo2_detected(); + static bool is_blacklisted(const std::filesystem::path& p_path); RTTI_IMPL_TYPEINFO(RedFS::FileSystem); RTTI_IMPL_ALLOCATOR(); diff --git a/src/FileSystemStorage.cpp b/src/FileSystemStorage.cpp index 27cf63a..4e81a5b 100644 --- a/src/FileSystemStorage.cpp +++ b/src/FileSystemStorage.cpp @@ -27,6 +27,9 @@ FileSystemStatus FileSystemStorage::exists(const Red::CString& p_path) const { if (error) { return FileSystemStatus::Denied; } + if (FileSystem::is_blacklisted(path)) { + return FileSystemStatus::Denied; + } bool status = std::filesystem::exists(path, error); if (error) { @@ -45,6 +48,9 @@ FileSystemStatus FileSystemStorage::is_file(const Red::CString& p_path) const { if (error) { return FileSystemStatus::Denied; } + if (FileSystem::is_blacklisted(path)) { + return FileSystemStatus::Denied; + } bool status = std::filesystem::is_regular_file(path, error); if (error) { @@ -63,34 +69,16 @@ Red::Handle FileSystemStorage::get_file(const Red::CString& p_path) { if (error) { return {}; } + if (FileSystem::is_blacklisted(path)) { + return {}; + } SharedMutex mutex = get_mutex(path); return Red::MakeHandle(mutex, p_path.c_str(), path); } Red::DynArray> FileSystemStorage::get_files() { - if (!rw_permission) { - return {}; - } - std::error_code error; - auto entries = std::filesystem::directory_iterator(storage_path, error); - - if (error) { - return {}; - } - Red::DynArray> files; - - for (const auto& entry : entries) { - if (entry.is_regular_file()) { - auto file_name = entry.path().filename(); - auto file_path = entry.path(); - auto file_mutex = get_mutex(file_path); - auto file = Red::MakeHandle(file_mutex, file_name, file_path); - - files.PushBack(file); - } - } - return files; + return _get_files(); } FileSystemStatus FileSystemStorage::delete_file( @@ -104,6 +92,9 @@ FileSystemStatus FileSystemStorage::delete_file( if (error) { return FileSystemStatus::Denied; } + if (FileSystem::is_blacklisted(path)) { + return FileSystemStatus::Denied; + } if (!std::filesystem::is_regular_file(path, error)) { return FileSystemStatus::Failure; } @@ -126,34 +117,16 @@ Red::Handle FileSystemStorage::get_async_file( if (error) { return {}; } + if (FileSystem::is_blacklisted(path)) { + return {}; + } SharedMutex mutex = get_mutex(path); return Red::MakeHandle(mutex, p_path.c_str(), path); } Red::DynArray> FileSystemStorage::get_async_files() { - if (!rw_permission) { - return {}; - } - std::error_code error; - auto entries = std::filesystem::directory_iterator(storage_path, error); - - if (error) { - return {}; - } - Red::DynArray> files; - - for (const auto& entry : entries) { - if (entry.is_regular_file()) { - auto file_name = entry.path().filename(); - auto file_path = entry.path(); - auto file_mutex = get_mutex(file_path); - auto file = Red::MakeHandle(file_mutex, file_name, file_path); - - files.PushBack(file); - } - } - return files; + return _get_files(); } std::filesystem::path FileSystemStorage::restrict_path( @@ -191,4 +164,37 @@ SharedMutex FileSystemStorage::get_mutex(const std::filesystem::path& p_path) { return mutex; } +template +Red::DynArray> FileSystemStorage::_get_files() { + if (!rw_permission) { + return {}; + } + std::error_code error; + auto entries = std::filesystem::directory_iterator(storage_path, error); + + if (error) { + return {}; + } + Red::DynArray> files; + + std::for_each( + begin(entries), end(entries), + [&files, this](const std::filesystem::directory_entry& entry) -> void { + if (!entry.is_regular_file()) { + return; + } + auto file_path = entry.path(); + + if (FileSystem::is_blacklisted(file_path)) { + return; + } + auto file_name = file_path.filename(); + auto file_mutex = get_mutex(file_path); + auto file = Red::MakeHandle(file_mutex, file_name, file_path); + + files.PushBack(file); + }); + return files; +} + } // namespace RedFS \ No newline at end of file diff --git a/src/FileSystemStorage.h b/src/FileSystemStorage.h index b31f1b9..63c2578 100644 --- a/src/FileSystemStorage.h +++ b/src/FileSystemStorage.h @@ -27,6 +27,8 @@ class FileSystemStorage : public Red::IScriptable { std::filesystem::path restrict_path(const std::string& p_path, std::error_code& p_error) const; SharedMutex get_mutex(const std::filesystem::path& p_path); + template + inline Red::DynArray> _get_files(); public: FileSystemStorage();