Skip to content

Commit

Permalink
feat: add feature to blacklist Vortex file.
Browse files Browse the repository at this point in the history
Prevent access from FileSystemStorage.
Refactor get_files/get_async_files with an inline template function.
Update unit tests.
Update README and CHANGELOG.
  • Loading branch information
poirierlouis committed Jun 4, 2024
1 parent a50de64 commit c110d0a
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 48 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

------------------------

Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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\
Expand All @@ -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

Expand Down
15 changes: 15 additions & 0 deletions scripts/RedFileSystem/Test/FileSystemStorageTest.reds
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Expand All @@ -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");
Expand Down Expand Up @@ -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);
Expand All @@ -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");
Expand Down
12 changes: 12 additions & 0 deletions src/FileSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> FileSystem::blacklist = {"__folder_managed_by_vortex"};

std::regex FileSystem::storage_name_rule("[A-Za-z]{3,24}");

Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/FileSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> blacklist;

static std::regex storage_name_rule;

Expand All @@ -46,6 +47,7 @@ class FileSystem : public Red::IScriptable {
static Red::Handle<FileSystemStorage> 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();
Expand Down
94 changes: 50 additions & 44 deletions src/FileSystemStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -63,34 +69,16 @@ Red::Handle<File> 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<File>(mutex, p_path.c_str(), path);
}

Red::DynArray<Red::Handle<File>> 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<Red::Handle<File>> 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>(file_mutex, file_name, file_path);

files.PushBack(file);
}
}
return files;
return _get_files<File>();
}

FileSystemStatus FileSystemStorage::delete_file(
Expand All @@ -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;
}
Expand All @@ -126,34 +117,16 @@ Red::Handle<AsyncFile> FileSystemStorage::get_async_file(
if (error) {
return {};
}
if (FileSystem::is_blacklisted(path)) {
return {};
}
SharedMutex mutex = get_mutex(path);

return Red::MakeHandle<AsyncFile>(mutex, p_path.c_str(), path);
}

Red::DynArray<Red::Handle<AsyncFile>> 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<Red::Handle<AsyncFile>> 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<AsyncFile>(file_mutex, file_name, file_path);

files.PushBack(file);
}
}
return files;
return _get_files<AsyncFile>();
}

std::filesystem::path FileSystemStorage::restrict_path(
Expand Down Expand Up @@ -191,4 +164,37 @@ SharedMutex FileSystemStorage::get_mutex(const std::filesystem::path& p_path) {
return mutex;
}

template <class T>
Red::DynArray<Red::Handle<T>> 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<Red::Handle<T>> 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<T>(file_mutex, file_name, file_path);

files.PushBack(file);
});
return files;
}

} // namespace RedFS
2 changes: 2 additions & 0 deletions src/FileSystemStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<class T>
inline Red::DynArray<Red::Handle<T>> _get_files();

public:
FileSystemStorage();
Expand Down

0 comments on commit c110d0a

Please sign in to comment.