diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 89f4ac5..2523730 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -199,6 +199,8 @@ add_executable(MassBuilderSaveTool
Managers/MassManager.cpp
Managers/ProfileManager.h
Managers/ProfileManager.cpp
+ Managers/Vfs/VirtualFileSystem.h
+ Managers/Vfs/VfsDirectory.h
Maps/ArmourSlots.hpp
Maps/BulletLauncherAttachmentStyles.hpp
Maps/BulletLauncherSockets.hpp
diff --git a/src/Managers/Vfs/VfsDirectory.h b/src/Managers/Vfs/VfsDirectory.h
new file mode 100644
index 0000000..e0c203f
--- /dev/null
+++ b/src/Managers/Vfs/VfsDirectory.h
@@ -0,0 +1,88 @@
+#pragma once
+
+// MassBuilderSaveTool
+// Copyright (C) 2021-2024 Guillaume Jacquemin
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+#include
+#include
+
+using namespace Corrade;
+
+namespace mbst { namespace Managers { namespace Vfs {
+
+CORRADE_HAS_TYPE(HasFilename, decltype(T::filename));
+
+template
+class VfsDirectory {
+ public:
+ static_assert(HasFilename::value && (std::is_same::value || std::is_same::value),
+ "FileType has no string-like member named filename.");
+
+ using DirType = VfsDirectory;
+
+ explicit VfsDirectory(Containers::StringView name): _name{name} {
+ // ctor
+ }
+
+ VfsDirectory(const VfsDirectory& other) = delete;
+ VfsDirectory& operator=(const VfsDirectory& other) = delete;
+
+ VfsDirectory(VfsDirectory&& other) = default;
+ VfsDirectory& operator=(VfsDirectory&& other) = default;
+
+ auto name() const -> Containers::StringView {
+ return _name;
+ }
+
+ auto subdirs() const -> Containers::ArrayView {
+ return _subdirs;
+ }
+
+ auto files() const -> Containers::ArrayView {
+ return _files;
+ }
+
+ protected:
+ void buildNestedPath(VfsDirectory& root, Containers::ArrayView components,
+ FileType& file)
+ {
+ if(components.size() > 1) {
+ bool found = false;
+ for(auto& subdir : root._subdirs) {
+ if(subdir._name == components[0]) {
+ found = true;
+ buildNestedPath(subdir, components.exceptPrefix(1), file);
+ break;
+ }
+ }
+
+ if(!found) {
+ arrayAppend(root._subdirs, InPlaceInit, components[0]);
+ buildNestedPath(root._subdirs.back(), components.exceptPrefix(1), file);
+ }
+ }
+ else if(components.size() == 1) {
+ arrayAppend(root._files, &file);
+ }
+ }
+
+ Containers::String _name;
+ Containers::Array _subdirs;
+ Containers::Array _files;
+};
+
+}}}
diff --git a/src/Managers/Vfs/VirtualFileSystem.h b/src/Managers/Vfs/VirtualFileSystem.h
new file mode 100644
index 0000000..0f3333c
--- /dev/null
+++ b/src/Managers/Vfs/VirtualFileSystem.h
@@ -0,0 +1,47 @@
+#pragma once
+
+// MassBuilderSaveTool
+// Copyright (C) 2021-2024 Guillaume Jacquemin
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include "VfsDirectory.h"
+
+namespace mbst { namespace Managers { namespace Vfs {
+
+template
+class VirtualFileSystem: public VfsDirectory {
+ public:
+ explicit VirtualFileSystem():
+ VfsDirectory("")
+ {
+ // ctor
+ }
+ explicit VirtualFileSystem(Containers::ArrayView files):
+ VfsDirectory("")
+ {
+ build(files);
+ }
+
+ void build(Containers::ArrayView files) {
+ for(auto& file : files) {
+ auto components = file.filename.split('/');
+ CORRADE_INTERNAL_ASSERT(components.size() == 0);
+
+ VfsDirectory::buildNestedPath(*this, components, file);
+ }
+ }
+};
+
+}}}