galaxy 1.0.0
Real-Time C++23 Game Programming Framework. Built on data-driven design principles and agile software engineering.
Loading...
Searching...
No Matches
VirtualFileSystem.cpp
Go to the documentation of this file.
1
7
8#include <entt/locator/locator.hpp>
9#include <mimalloc.h>
10#include <physfs.h>
11#include <tinyfiledialogs.h>
12#include <zip.h>
13
19
20#include "VirtualFileSystem.hpp"
21
22#ifdef GALAXY_WIN_PLATFORM
26#endif
27
28namespace galaxy
29{
31 {
32 // Create data directories.
33 if (!std::filesystem::exists(Settings::root_dir() / Settings::assets_dir()))
34 {
35 std::filesystem::create_directories(Settings::root_dir() / Settings::assets_dir());
36 }
37
38 // Set up allocators for physfs.
39 PHYSFS_Allocator a = {};
40 a.Init = nullptr;
41 a.Deinit = nullptr;
42 a.Free = mi_free;
43 a.Malloc = mi_malloc;
44 a.Realloc = mi_realloc;
45 log::physfs_check(PHYSFS_setAllocator(&a));
46
47 // Set up vfs.
48 if (log::physfs_check(PHYSFS_init(nullptr)))
49 {
50 PHYSFS_permitSymbolicLinks(false);
51
52 auto write_dir = Settings::root_dir() / Settings::assets_dir();
53 write_dir.make_preferred();
54 log::physfs_check(PHYSFS_setWriteDir(write_dir.string().c_str()));
55
56 auto read_dir = Settings::root_dir() / Settings::asset_pack();
57 read_dir.make_preferred();
58
60 {
73
74 read_dir = write_dir;
75 }
76
77 log::physfs_check(PHYSFS_mount(read_dir.string().c_str(), nullptr, true));
78 }
79 }
80
82 {
83 log::physfs_check(PHYSFS_deinit());
84 }
85
86 std::string VirtualFileSystem::read(const std::string& file)
87 {
88 PHYSFS_File* f = PHYSFS_openRead(file.c_str());
89 if (log::physfs_check(f))
90 {
91 const auto len = PHYSFS_fileLength(f);
92
93 if (log::physfs_check(len))
94 {
95 std::string buffer;
96 buffer.resize(len);
97
98 if (log::physfs_check(PHYSFS_readBytes(f, buffer.data(), buffer.size())))
99 {
100 return buffer;
101 }
102 else
103 {
104 GALAXY_LOG(GALAXY_ERROR, "Failed to read '{0}'.", file);
105 }
106 }
107 else
108 {
109 GALAXY_LOG(GALAXY_ERROR, "Failed to check file length for '{0}'.", file);
110 }
111
112 log::physfs_check(PHYSFS_close(f));
113 }
114 else
115 {
116 GALAXY_LOG(GALAXY_ERROR, "Failed to read '{0}'.", file);
117 }
118
119 return {};
120 }
121
122 std::vector<std::uint8_t> VirtualFileSystem::read_binary(const std::string& file)
123 {
124 PHYSFS_File* f = PHYSFS_openRead(file.c_str());
125 if (log::physfs_check(f))
126 {
127 const auto len = PHYSFS_fileLength(f);
128
129 if (log::physfs_check(len))
130 {
131 std::vector<std::uint8_t> buffer;
132 buffer.resize(len);
133
134 if (log::physfs_check(PHYSFS_readBytes(f, &buffer[0], buffer.size())))
135 {
136 return buffer;
137 }
138 else
139 {
140 GALAXY_LOG(GALAXY_ERROR, "Failed to read '{0}'.", file);
141 }
142 }
143 else
144 {
145 GALAXY_LOG(GALAXY_ERROR, "Failed to check file length for '{0}'.", file);
146 }
147
148 log::physfs_check(PHYSFS_close(f));
149 }
150 else
151 {
152 GALAXY_LOG(GALAXY_ERROR, "Failed to read '{0}'.", file);
153 }
154
155 return {};
156 }
157
158 bool VirtualFileSystem::write(const std::string& data, const std::string& file)
159 {
160 return write_raw(data.data(), data.size() * sizeof(char), file);
161 }
162
163 bool VirtualFileSystem::write_binary(std::span<std::uint8_t> data, const std::string& file)
164 {
165 return write_raw(data.data(), data.size_bytes(), file);
166 }
167
168 bool VirtualFileSystem::write_raw(const void* data, const std::size_t size, const std::string& file)
169 {
170 auto path = std::filesystem::path(file);
171 if (path.is_absolute())
172 {
173 path = path.filename();
174 }
175
176 PHYSFS_File* f = PHYSFS_openWrite(path.string().c_str());
177 if (log::physfs_check(f))
178 {
179 const auto len = PHYSFS_fileLength(f);
180
181 if (log::physfs_check(PHYSFS_writeBytes(f, data, size)))
182 {
183 return true;
184 }
185 else
186 {
187 GALAXY_LOG(GALAXY_ERROR, "Failed to read '{0}'.", path.string());
188 }
189
190 log::physfs_check(PHYSFS_close(f));
191 }
192 else
193 {
194 GALAXY_LOG(GALAXY_ERROR, "Failed to read '{0}'.", path.string());
195 }
196
197 return false;
198 }
199
200 std::optional<ray::Image> VirtualFileSystem::load_ray_image(const std::string& filename) noexcept
201 {
202 const auto extension = fileutils::extension(filename);
203 if (extension.has_value())
204 {
205 auto data = read_binary(filename);
206 if (!data.empty())
207 {
208 ray::Image image = ray::LoadImageFromMemory(extension.value().c_str(), data.data(), static_cast<int>(data.size()));
209 if (image.data)
210 {
211 return std::make_optional(image);
212 }
213 else
214 {
215 GALAXY_LOG(GALAXY_ERROR, "Failed to load {0} from memory.", filename);
216 }
217 }
218 else
219 {
220 GALAXY_LOG(GALAXY_ERROR, "Failed to read {0} from vfs.", filename);
221 }
222 }
223 else
224 {
225 GALAXY_LOG(GALAXY_ERROR, "Failed to read {0} extension.", filename);
226 }
227
228 return std::nullopt;
229 }
230
231 std::optional<ray::Image> VirtualFileSystem::load_ray_image_anim(const std::string& filename, int* frames) noexcept
232 {
233 const auto extension = fileutils::extension(filename);
234 if (extension.has_value())
235 {
236 auto data = read_binary(filename);
237 if (!data.empty())
238 {
239 ray::Image image = ray::LoadImageAnimFromMemory(extension.value().c_str(), data.data(), static_cast<int>(data.size()), frames);
240 if (image.data)
241 {
242 return std::make_optional(image);
243 }
244 else
245 {
246 GALAXY_LOG(GALAXY_ERROR, "Failed to load {0} from memory.", filename);
247 }
248 }
249 else
250 {
251 GALAXY_LOG(GALAXY_ERROR, "Failed to read {0} from vfs.", filename);
252 }
253 }
254 else
255 {
256 GALAXY_LOG(GALAXY_ERROR, "Failed to read {0} extension.", filename);
257 }
258
259 return std::nullopt;
260 }
261
262 std::optional<ray::Texture2D> VirtualFileSystem::load_ray_texture(const std::string& filename) noexcept
263 {
264 const auto image = load_ray_image(filename);
265 if (image.has_value())
266 {
267 ray::Texture2D texture = ray::LoadTextureFromImage(image.value());
268 if (texture.id != 0)
269 {
270 ray::UnloadImage(image.value());
271 return std::make_optional(texture);
272 }
273 else
274 {
275 GALAXY_LOG(GALAXY_ERROR, "Failed to load texture {0} from image.");
276 }
277 }
278 else
279 {
280 GALAXY_LOG(GALAXY_ERROR, "Failed to load texture {0} because of failing to read image.");
281 }
282
283 return std::nullopt;
284 }
285
286 std::optional<ray::Wave> VirtualFileSystem::load_ray_wave(const std::string& filename) noexcept
287 {
288 const auto extension = fileutils::extension(filename);
289 if (extension.has_value())
290 {
291 auto data = read_binary(filename);
292 if (!data.empty())
293 {
294 ray::Wave wave = ray::LoadWaveFromMemory(extension.value().c_str(), data.data(), static_cast<int>(data.size()));
295 if (wave.data)
296 {
297 return std::make_optional(wave);
298 }
299 else
300 {
301 GALAXY_LOG(GALAXY_ERROR, "Failed to load {0} from memory.", filename);
302 }
303 }
304 else
305 {
306 GALAXY_LOG(GALAXY_ERROR, "Failed to read {0} from vfs.", filename);
307 }
308 }
309 else
310 {
311 GALAXY_LOG(GALAXY_ERROR, "Failed to read {0} extension.", filename);
312 }
313
314 return std::nullopt;
315 }
316
317 std::optional<ray::Music> VirtualFileSystem::load_ray_music_stream(const std::string& filename) noexcept
318 {
319 const auto extension = fileutils::extension(filename);
320 if (extension.has_value())
321 {
322 auto data = read_binary(filename);
323 if (!data.empty())
324 {
325 ray::Music music = ray::LoadMusicStreamFromMemory(extension.value().c_str(), data.data(), static_cast<int>(data.size()));
326 if (music.ctxData)
327 {
328 return std::make_optional(music);
329 }
330 else
331 {
332 GALAXY_LOG(GALAXY_ERROR, "Failed to load {0} from memory.", filename);
333 }
334 }
335 else
336 {
337 GALAXY_LOG(GALAXY_ERROR, "Failed to read {0} from vfs.", filename);
338 }
339 }
340 else
341 {
342 GALAXY_LOG(GALAXY_ERROR, "Failed to read {0} extension.", filename);
343 }
344
345 return std::nullopt;
346 }
347
348 std::optional<ray::Font> VirtualFileSystem::load_ray_font(const std::string& filename, int font_size, std::span<int> font_chars) noexcept
349 {
350 const auto extension = fileutils::extension(filename);
351 if (extension.has_value())
352 {
353 auto data = read_binary(filename);
354 if (!data.empty())
355 {
356 ray::Font font = ray::LoadFontFromMemory(
357 extension.value().c_str(),
358 data.data(),
359 static_cast<int>(data.size()),
360 font_size,
361 font_chars.data(),
362 static_cast<int>(font_chars.size())
363 );
364 if (font.texture.id != 0)
365 {
366 return std::make_optional(font);
367 }
368 else
369 {
370 GALAXY_LOG(GALAXY_ERROR, "Failed to load {0} from memory.", filename);
371 }
372 }
373 else
374 {
375 GALAXY_LOG(GALAXY_ERROR, "Failed to read {0} from vfs.", filename);
376 }
377 }
378 else
379 {
380 GALAXY_LOG(GALAXY_ERROR, "Failed to read {0} extension.", filename);
381 }
382
383 return std::nullopt;
384 }
385
386 void VirtualFileSystem::mkdir(const std::string& dir) noexcept
387 {
388 if (!exists(dir))
389 {
390 log::physfs_check(PHYSFS_mkdir(dir.c_str()));
391 }
392 }
393
394 void VirtualFileSystem::remove(const std::string& path) noexcept
395 {
396 log::physfs_check(PHYSFS_delete(path.c_str()));
397 }
398
399 bool VirtualFileSystem::exists(const std::string& file) noexcept
400 {
401 return PHYSFS_exists(file.c_str());
402 }
403
404 bool VirtualFileSystem::is_dir(const std::string& path) noexcept
405 {
406 return PHYSFS_isDirectory(path.c_str());
407 }
408
409 std::vector<std::string> VirtualFileSystem::list(const std::string& dir)
410 {
411 std::vector<std::string> list;
412
413 log::physfs_check(PHYSFS_enumerate(
414 dir.c_str(),
415 [](void* data, const char* origdir, const char* fname) -> PHYSFS_EnumerateCallbackResult {
416 if (data != nullptr && fname != nullptr)
417 {
418 std::string o = origdir;
419 std::string f = fname;
420
421 auto* my_list = static_cast<std::vector<std::string>*>(data);
422 my_list->emplace_back(o + f);
423 }
424
425 return PHYSFS_ENUM_OK;
426 },
427 &list
428 ));
429
430 return list;
431 }
432
434 {
435 tinyfd_beep();
436 }
437
438 void VirtualFileSystem::notification(const std::string& title, const std::string& msg, const DialogIcon icon) noexcept
439 {
440 std::string tinyfd_icon {magic_enum::enum_name(icon)};
441 tinyfd_notifyPopup(title.c_str(), msg.c_str(), tinyfd_icon.c_str());
442 }
443
444 int VirtualFileSystem::message_box(const std::string& title, const std::string& msg, const DialogType type, const DialogIcon icon, const DialogButton btn) noexcept
445 {
446 std::string tinyfd_type {magic_enum::enum_name(type)};
447 std::string tinyfd_icon {magic_enum::enum_name(icon)};
448
449 return tinyfd_messageBox(title.c_str(), msg.c_str(), tinyfd_type.c_str(), tinyfd_icon.c_str(), static_cast<int>(btn));
450 }
451
452 std::string VirtualFileSystem::input_box(const std::string& title, const std::string& msg, const std::string& default_text, const bool password) noexcept
453 {
454 const char* dt = password ? nullptr : default_text.c_str();
455 return tinyfd_inputBox(title.c_str(), msg.c_str(), dt);
456 }
457
458 std::string VirtualFileSystem::open_save_dialog(const std::string& default_filename, const std::vector<const char*>& filters)
459 {
460 const char* const* filter_patterns = (filters.size() > 0) ? filters.data() : nullptr;
461 const char* result = tinyfd_saveFileDialog("Save file", default_filename.c_str(), static_cast<int>(filters.size()), filter_patterns, nullptr);
462
463 if (result != nullptr)
464 {
465 return {result};
466 }
467 else
468 {
469 return {};
470 }
471 }
472
473 std::string VirtualFileSystem::open_file_dialog(const std::vector<const char*>& filters, const std::string& def_path)
474 {
475 const auto default_path = (Settings::root_dir() / def_path).string();
476
477 const char* const* filter_patterns = (filters.size() > 0) ? filters.data() : nullptr;
478 const char* result = tinyfd_openFileDialog("Open file", default_path.c_str(), static_cast<int>(filters.size()), filter_patterns, "Select a file", false);
479
480 if (result != nullptr)
481 {
482 return {result};
483 }
484 else
485 {
486 return {};
487 }
488 }
489
490 std::string VirtualFileSystem::select_folder_dialog(const std::string& def_path)
491 {
492 const auto default_path = (Settings::root_dir() / def_path).string();
493 const char* result = tinyfd_selectFolderDialog("Select folder", default_path.c_str());
494
495 if (result != nullptr)
496 {
497 return {result};
498 }
499 else
500 {
501 return {};
502 }
503 }
504} // namespace galaxy
505
506#ifdef GALAXY_WIN_PLATFORM
508#endif
#define GALAXY_LOG(level, msg,...)
Definition Log.hpp:29
#define GALAXY_ERROR
Definition Log.hpp:25
#define GALAXY_DISABLE_WARNING_POP
Definition Pragma.hpp:60
#define GALAXY_DISABLE_WARNING(x)
Definition Pragma.hpp:61
#define GALAXY_DISABLE_WARNING_PUSH
Macro for windows platform detection.
Definition Pragma.hpp:59
void alert() noexcept
Trigger a standard filesystem audio alert.
int message_box(const std::string &title, const std::string &msg, const DialogType type, const DialogIcon icon, const DialogButton btn) noexcept
Opens a platform specific message box.
~VirtualFileSystem() noexcept
Destructor.
bool is_dir(const std::string &path) noexcept
Checks if a file is a folder.
bool write_binary(std::span< std::uint8_t > data, const std::string &file)
Writes a binary file to disk.
std::vector< std::string > list(const std::string &dir)
Get assets in an asset folder.
std::optional< ray::Image > load_ray_image_anim(const std::string &filename, int *frames) noexcept
Load an animated image from VFS (e.g. GIF). You must manually free the data after.
std::string read(const std::string &file)
Read a file.
std::optional< ray::Wave > load_ray_wave(const std::string &filename) noexcept
Load wave data from VFS. You must manually free the data after.
std::optional< ray::Texture2D > load_ray_texture(const std::string &filename) noexcept
Load a texture from VFS. You must manually free the data after.
bool write_raw(const void *data, const std::size_t size, const std::string &file)
Raw data writing.
bool write(const std::string &data, const std::string &file)
Writes a file to disk.
void notification(const std::string &title, const std::string &msg, const DialogIcon icon) noexcept
Trigger a system notification.
std::string input_box(const std::string &title, const std::string &msg, const std::string &default_text="", const bool password=false) noexcept
Opens a platform specific text input box.
bool exists(const std::string &file) noexcept
Does the file exist in the vfs.
std::string open_file_dialog(const std::vector< const char * > &filters={}, const std::string &def_path="")
Open a file dialog.
std::optional< ray::Image > load_ray_image(const std::string &filename) noexcept
Load an image from VFS. You must manually free the data after.
std::optional< ray::Music > load_ray_music_stream(const std::string &filename) noexcept
Load module music from VFS. You must manually free the data after.
std::string open_save_dialog(const std::string &default_filename, const std::vector< const char * > &filters={})
Open a save file dialog.
std::string select_folder_dialog(const std::string &def_path="")
Select a folder using a dialog.
void remove(const std::string &path) noexcept
Delete a file or folder.
std::optional< ray::Font > load_ray_font(const std::string &filename, int font_size, std::span< int > font_chars) noexcept
Load font from VFS. You must manually free the data after.
VirtualFileSystem() noexcept
Constructor.
std::vector< std::uint8_t > read_binary(const std::string &file)
Read a binary file.
void mkdir(const std::string &dir) noexcept
Creates an empty folder in the filesystem relative to the root.
std::optional< std::string > extension(const std::string &filepath) noexcept
Get a file or path's extension.
Definition FileUtils.cpp:21
bool physfs_check(const int code) noexcept
Call a physfs function with error handling and logs a message for you.
Application.hpp galaxy.
DialogButton
Type of button for tinyfd boxes.
DialogType
Type of dialog box for use with tinyfd.
DialogIcon
Type of native system icon to display on file dialogs.
static auto root_dir() noexcept -> std::filesystem::path
Current root directory of application, unless it has been changed.
Definition Settings.cpp:203
static auto assets_dir_prefabs() noexcept -> const std::string &
Prefab asset location.
Definition Settings.cpp:263
static auto assets_dir_texture() noexcept -> const std::string &
Textures asset location.
Definition Settings.cpp:258
static auto assets_dir_voice() noexcept -> const std::string &
Voice asset location.
Definition Settings.cpp:233
static auto assets_dir_shaders() noexcept -> const std::string &
Shaders asset location.
Definition Settings.cpp:248
static auto assets_dir_ui() noexcept -> const std::string &
UI asset location.
Definition Settings.cpp:278
static auto use_loose_assets() noexcept -> bool
Should asset data be read from pack or assets dir.
Definition Settings.cpp:218
static auto assets_dir_animation() noexcept -> const std::string &
Animation data location.
Definition Settings.cpp:253
static auto assets_dir_script() noexcept -> const std::string &
Scripts asset location.
Definition Settings.cpp:243
static auto assets_dir_music() noexcept -> const std::string &
Music asset location.
Definition Settings.cpp:223
static auto assets_dir() noexcept -> std::filesystem::path
Main data directory.
Definition Settings.cpp:208
static auto asset_pack() noexcept -> const std::string &
Name of packed assets file.
Definition Settings.cpp:213
static auto assets_dir_sfx() noexcept -> const std::string &
SFX asset location.
Definition Settings.cpp:228
static auto assets_dir_maps() noexcept -> const std::string &
Maps asset location.
Definition Settings.cpp:268
static auto assets_dir_video() noexcept -> const std::string &
Video asset location.
Definition Settings.cpp:273
static auto assets_dir_font() noexcept -> const std::string &
Font asset location.
Definition Settings.cpp:238