17#include "physfs/physfs.h"
19#ifdef EVENGINE_WINDOWS
32#include "webgpu/webplatform.h"
37#ifdef EVENGINE_ANDROID
40#include "android/android.h"
46size_t getDriveDelim(
const std::string &input) {
47 for (
size_t i = 0; i < input.size(); ++i)
48 if (input[i] ==
'/' || input[i] ==
'\\')
return i;
53std::string getDriveRoot(
const std::string &input) {
return input.substr(0, getDriveDelim(input) + 1); }
55std::string skipDriveRoot(
const std::string &input) {
return input.substr(getDriveDelim(input) + 1); }
57std::string normalize(
const std::string &input) {
58 std::stringstream out;
60 bool seenSep =
false, isSep =
false;
61 for (
size_t i = 0; i < input.size(); ++i) {
63 if (!isSep || !seenSep) out << input[i];
77 requirePath = {
"?.lua",
"?/init.lua"};
78 cRequirePath = {
"??"};
81 const std::string a0 =
c->getArgv(0);
82 if (a0.empty())
init(NULL);
83 else init(a0.c_str());
94 memoryArchive_ =
nullptr;
97#ifdef EVENGINE_ANDROID
98 android::deinitializeVirtualArchive();
101 if (PHYSFS_isInit()) PHYSFS_deinit();
105 if (!PHYSFS_init(arg0))
106 throw Exception(
"Failed to initialize filesystem: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
113 if (fusedSet)
return;
119 if (!fusedSet)
return false;
124 if (!PHYSFS_isInit())
return false;
126 std::string old_save_path = save_path_full;
129 save_identity = std::string(ident);
139 save_path_full += save_path_relative;
141 save_path_full = normalize(save_path_full);
143#ifdef EVENGINE_ANDROID
144 if (save_identity ==
"") save_identity =
"unnamed";
146 std::string storage_path;
148 storage_path = SDL_AndroidGetExternalStoragePath();
150 storage_path = SDL_AndroidGetInternalStoragePath();
152 std::string save_directory = storage_path +
"/save";
154 save_path_full = storage_path + std::string(
"/save/") + save_identity;
156 if (!android::directoryExists(save_path_full.c_str()) && !android::mkdir(save_path_full.c_str()))
157 SDL_Log(
"Error: Could not create save directory %s!", save_path_full.c_str());
167 if (!old_save_path.empty()) PHYSFS_unmount(old_save_path.c_str());
171 PHYSFS_mount(save_path_full.c_str(),
nullptr, appendToPath);
176 PHYSFS_setWriteDir(
nullptr);
184 if (!PHYSFS_isInit())
return false;
187 if (!game_source.empty())
return false;
189 std::string new_search_path = source;
191#ifdef EVENGINE_ANDROID
192 if (!android::createStorageDirectories()) SDL_Log(
"Error creating storage directories!");
194 new_search_path =
"";
196 PHYSFS_Io *gameLoveIO;
197 bool hasFusedGame = android::checkFusedGame((
void **)&gameLoveIO);
202 gameLoveIO->destroy(gameLoveIO);
205 if (!android::initializeVirtualArchive()) {
206 SDL_Log(
"Unable to mount AAsset: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
212 new_search_path = android::getSelectedGameFile();
215 if (!PHYSFS_mount(new_search_path.c_str(),
nullptr, 1)) {
217 SDL_Log(
"Mounting %s did not work. Loading to memory.", new_search_path.c_str());
218 char * game_archive_ptr = NULL;
219 size_t game_archive_size = 0;
220 if (!android::loadGameArchiveToMemory(new_search_path.c_str(), &game_archive_ptr,
221 &game_archive_size)) {
222 SDL_Log(
"Failure memory loading archive %s", new_search_path.c_str());
225 if (!PHYSFS_mountMemory(game_archive_ptr, game_archive_size, android::freeGameArchiveMemory,
226 "archive.zip",
"/", 0)) {
227 SDL_Log(
"Failure mounting in-memory archive.");
228 android::freeGameArchiveMemory(game_archive_ptr);
235 if (!PHYSFS_mount(new_search_path.c_str(),
nullptr, 1))
return false;
239 game_source = new_search_path;
247 if (!PHYSFS_isInit() || !
data || size == 0)
return false;
249 if (!game_source.empty())
return false;
253 void* owned = malloc(size);
254 if (!owned)
return false;
255 memcpy(owned,
data, size);
258 if (!PHYSFS_mountMemory(owned, size,
nullptr,
"game.eve",
"/", 1)) {
259 const auto err = PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode());
260 std::cerr <<
"PhysFS mountMemory failed: " << (err ? err :
"unknown error") << std::endl;
265 memoryArchive_ = owned;
266 game_source =
"game.eve";
271 if (!PHYSFS_isInit())
return false;
274 if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty())
return false;
278 std::string temp_writedir = getDriveRoot(save_path_full);
279 std::string temp_createdir = skipDriveRoot(save_path_full);
290 size_t startpos = temp_createdir.find_first_not_of(
'/');
291 if (startpos != std::string::npos) temp_createdir = temp_createdir.substr(startpos);
296 if (!PHYSFS_setWriteDir(temp_writedir.c_str()))
return false;
301 PHYSFS_setWriteDir(
nullptr);
306 if (!PHYSFS_setWriteDir(save_path_full.c_str()))
return false;
309 if (!PHYSFS_mount(save_path_full.c_str(),
nullptr, 0)) {
310 PHYSFS_setWriteDir(
nullptr);
318 if (!PHYSFS_isInit() || archive.empty())
return false;
320 std::string realPath;
324 auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
326 if (it != allowedMountPaths.end())
328 else if (
isFused() && sourceBase.compare(archive) == 0) {
331 realPath = sourceBase;
334 if (archive.size() == 0 || archive.find(
"..") != string::npos || archive ==
"/")
return false;
336 const char* realDir = PHYSFS_getRealDir(archive.c_str());
337 if (!realDir)
return false;
343 if (realPath.find(game_source) == 0)
return false;
349 if (realPath.length() == 0)
return false;
351 return PHYSFS_mount(realPath.c_str(), mountpoint.c_str(), appendToPath) != 0;
355 if (!PHYSFS_isInit())
return false;
357 if (PHYSFS_mountMemory(
data->getData(),
data->getSize(),
nullptr, archivename.c_str(), mountpoint.c_str(), appendToPath) != 0) {
358 mountedData[archivename] =
data;
366 if (!PHYSFS_isInit() || realDir.empty())
return false;
368 if (mountpoint.empty()) mountpoint =
"/";
372 std::lock_guard<std::mutex> lock(mountMu_);
373 if (std::find(mountedRealDirs_.begin(), mountedRealDirs_.end(), realDir) == mountedRealDirs_.end()) {
374 if (!PHYSFS_mount(realDir.c_str(), mountpoint.c_str(), appendToPath ? 1 : 0))
return false;
375 mountedRealDirs_.push_back(realDir);
381 std::lock_guard<std::mutex> lock(mountMu_);
382 auto it = std::find(mountedRealDirs_.begin(), mountedRealDirs_.end(), realDir);
383 if (it == mountedRealDirs_.end())
return false;
384 const bool ok = PHYSFS_unmount(realDir.c_str()) != 0;
385 mountedRealDirs_.erase(it);
390 if (!PHYSFS_isInit() || archive.empty())
return false;
392 auto datait = mountedData.find(archive);
394 if (datait != mountedData.end() && PHYSFS_unmount(archive.c_str()) != 0) {
395 mountedData.erase(datait);
399 std::string realPath;
403 auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
405 if (it != allowedMountPaths.end())
407 else if (
isFused() && sourceBase.compare(archive) == 0) {
410 realPath = sourceBase;
413 if (archive.size() == 0 || archive.find(
"..") != string::npos || archive ==
"/")
return false;
415 const char* realDir = PHYSFS_getRealDir(archive.c_str());
416 if (!realDir)
return false;
423 const char* mountPoint = PHYSFS_getMountPoint(realPath.c_str());
424 if (!mountPoint)
return false;
426 return PHYSFS_unmount(realPath.c_str()) != 0;
430 for (
const auto &datapair : mountedData) {
431 if (datapair.second ==
data) {
432 std::string archive = datapair.first;
433 return unmount(archive.c_str());
444#ifdef EVENGINE_WINDOWS
448 cwd = to_utf8(w_cwd);
449 replace_char(cwd,
'\\',
'/');
466 static std::string userDir = normalize(ios::getHomeDirectory());
467#elif defined(EVENGINE_WEBGPU)
468 static std::string userDir = normalize(eve::webgpu_platform::getHomeDirectory());
470 const char*
dir = PHYSFS_getUserDir();
471 static std::string userDir;
472 if (
dir) userDir = normalize(
dir);
478 if (appdata.empty()) {
479#ifdef EVENGINE_WINDOWS_UWP
481#elif defined(EVENGINE_WEBGPU)
482 appdata = normalize(eve::webgpu_platform::getAppdataDirectory());
483#elif defined(EVENGINE_WINDOWS)
484 wchar_t *w_appdata = _wgetenv(L
"APPDATA");
485 appdata = to_utf8(w_appdata);
486 replace_char(appdata,
'\\',
'/');
487#elif defined(EVENGINE_MACOSX)
489 udir.append(
"/Library/Application Support");
490 appdata = normalize(udir);
491#elif defined(EVENGINE_IOS)
492 appdata = normalize(ios::getAppdataDirectory());
493#elif defined(EVENGINE_LINUX)
494 char *xdgdatahome = getenv(
"XDG_DATA_HOME");
498 appdata = xdgdatahome;
509 size_t source_len = game_source.length();
511 if (source_len == 0)
return "";
515#ifdef EVENGINE_WINDOWS
517 size_t base_end_pos = game_source.find_last_of(
"/\\", source_len - 2);
519 size_t base_end_pos = game_source.find_last_of(
'/', source_len - 2);
522 if (base_end_pos == std::string::npos)
return "";
525 if (base_end_pos == 0) base_end_pos = 1;
527 return game_source.substr(0, base_end_pos);
531 if (!PHYSFS_isInit())
throw Exception(
"PhysFS is not initialized.");
533 const char*
dir = PHYSFS_getRealDir(filename.c_str());
535 if (
dir ==
nullptr)
throw Exception(
"File does not exist on disk.");
537 return std::string(
dir);
541 if (!PHYSFS_isInit())
return false;
543 PHYSFS_Stat stat = {};
544 if (!PHYSFS_stat(filepath.c_str(), &stat))
return false;
546 info.
size = (int64_t)stat.filesize;
547 info.
modtime = (int64_t)stat.modtime;
549 if (stat.filetype == PHYSFS_FILETYPE_REGULAR)
551 else if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY)
552 info.
type =
"directory";
553 else if (stat.filetype == PHYSFS_FILETYPE_SYMLINK)
554 info.
type =
"symlink";
562 if (!PHYSFS_isInit())
return false;
566 if (!PHYSFS_mkdir(
dir.c_str()))
return false;
572 if (!PHYSFS_isInit())
return false;
576 if (!PHYSFS_delete(
file.c_str()))
return false;
587 return file.read(size);
609 std::vector<std::string> items;
610 if (!PHYSFS_isInit())
return items;
612 char **rc = PHYSFS_enumerateFiles(
dir.c_str());
614 if (rc ==
nullptr)
return items;
616 for (
char **i = rc; *i != 0; i++) items.push_back(*i);
623 if (!PHYSFS_isInit())
return;
625 PHYSFS_permitSymbolicLinks(enable ? 1 : 0);
629 if (!PHYSFS_isInit())
return false;
631 return PHYSFS_symbolicLinksPermitted() != 0;
639 if (std::find(allowedMountPaths.begin(), allowedMountPaths.end(), path) == allowedMountPaths.end())
640 allowedMountPaths.push_back(path);
645bool looksAbsolute(
const std::string &path) {
646 if (path.empty())
return false;
647 if (path[0] ==
'/' || path[0] ==
'\\')
return true;
648#ifdef EVENGINE_WINDOWS
649 if (path.size() >= 2 && path[1] ==
':')
return true;
654std::string joinPath(
const std::string &
a,
const std::string &
b) {
655 if (
a.empty())
return b;
656 if (
b.empty())
return a;
657 char last =
a.back();
658 if (last ==
'/' || last ==
'\\')
return a +
b;
662std::string parentDir(
const std::string &path) {
663 auto pos = path.find_last_of(
"/\\");
664 if (
pos == std::string::npos)
return ".";
665 if (
pos == 0)
return "/";
666 return path.substr(0,
pos);
669std::string baseName(
const std::string &path) {
670 auto pos = path.find_last_of(
"/\\");
671 if (
pos == std::string::npos)
return path;
672 return path.substr(
pos + 1);
677FileWatch &Filesystem::watchers() {
678 if (!fileWatch_) fileWatch_ = std::make_unique<FileWatch>();
682bool Filesystem::resolveWatchTarget(
const std::string &path, std::string &realDir,
683 std::string &filterName, std::string &reportPath) {
687 if (path.empty())
return false;
690 if (path ==
"." || path ==
"./") {
697 if (looksAbsolute(path)) {
702 realDir = parentDir(path);
703 filterName = baseName(path);
715 std::string full = joinPath(root, path);
716 if (info.type ==
"directory") {
721 realDir = parentDir(full);
722 filterName = baseName(full);
726 std::string
parent = parentDir(path);
737 realDir = joinPath(root,
parent);
738 filterName = baseName(path);
749 filterName = baseName(path);
758 filterName = baseName(path);
765 std::string realDir,
filter, report;
766 if (!resolveWatchTarget(path, realDir,
filter, report))
return false;
767 return watchers().
add(realDir,
filter, report, 1);
773 if (fileWatch_) fileWatch_->clear();
777 if (!fileWatch_)
return 0;
778 return fileWatch_->count();
782 if (!fileWatch_)
return "";
784 if (!fileWatch_->poll(ev)) {
785 lastWatchPath_.clear();
786 lastWatchRealPath_.clear();
789 lastWatchPath_ = ev.
path;
#define EVENGINE_MAX_PATH
#define EVENGINE_PATH_SEPARATOR
#define EVENGINE_APPDATA_PREFIX
#define EVENGINE_APPDATA_FOLDER
Data buffer paired with a filename (used for type identification).
bool add(const std::string &realDir, const std::string &filterName, const std::string &reportPath, int scanInterval=1)
bool remove(const std::string &reportPath)
Remove by reportPath previously passed to add().
A File interface, providing generic means of reading from and writing to files.
virtual bool isAndroidSaveExternal() const
Gets whether the Android save is external. Returns a bool.
virtual bool isRealDirectory(const std::string &path) const
Gets whether the given full (OS-dependent) path is a directory.
std::string getSaveDirectory() override
Gets the full path of the save folder.
void append(std::string filename, const void *data, int64_t size) const override
Append data to a file, creating it if it doesn't exist.
bool setSource(std::string source) override
Sets the path to the game source. This can only be set once.
int getWatchCount() const override
bool setIdentity(std::string ident, bool appendToPath=false) override
Sets the name of the save folder.
void unwatchAll() override
std::string getRealDirectory(std::string filename) const override
Gets the real directory path containing the file.
bool setSourceFromMemory(const void *data, size_t size) override
Loads the game source from a packaged archive (.eve / zip) held entirely in memory and mounts it at "...
void setFused(bool fused) override
bool watch(std::string path) override
Watch a virtual or absolute OS path (file or directory). File watches monitor the parent directory an...
std::string getSource() const override
Gets the path to the game source. Returns a 0-length string if the source has not been set.
filesystem::File * newFile(std::string filename) const override
Creates a new file.
std::string getUserDirectory() override
Gets the user home directory.
bool createDirectory(std::string dir) override
Creates a directory. Write dir must be set.
bool remove(std::string file) override
Removes a file (or directory).
std::vector< std::string > getDirectoryItems(std::string dir) override
This "native" method returns a table of all files in a given directory.
bool isFused() const override
bool unmount(std::string archive) override
std::string pollWatch() override
Pop next watch event kind: "added"|"removed"|"modified"|"movedFrom"|"movedTo". Empty string if queue ...
std::string getLastWatchRealPath() const override
bool mountRealDirectory(std::string realDir, std::string mountpoint, bool appendToPath=false) override
bool unwatch(std::string path) override
Stop watching a path previously passed to watch().
bool mount(std::string archive, std::string mountpoint, bool appendToPath=false) override
std::string getIdentity() const override
void init(const char *arg0) override
bool setupWriteDirectory() override
This sets up the save directory. If the it is already set up, nothing happens.
std::vector< std::string > & getCRequirePath() override
std::string getWorkingDirectory() override
Gets the current working directory.
FileData * read(std::string filename, int64_t size=File::ALL) const override
Reads data from a file.
bool unmountRealDirectory(std::string realDir) override
void allowMountingForPath(const std::string &path) override
Allows a full (OS-dependent) path to be used with Filesystem::mount.
void write(std::string filename, const void *data, int64_t size) const override
Write data to a file.
void setSymlinksEnabled(bool enable) override
Enable or disable symbolic link support in love.filesystem.
bool getInfo(std::string filepath, Info &info) const override
Gets information about the item at the specified filepath. Returns false if nothing exists at the pat...
bool areSymlinksEnabled() const override
Gets whether symbolic link support is enabled.
std::vector< std::string > & getRequirePath() override
std::string getSourceBaseDirectory() const override
Gets the full path to the directory containing the game source. For example if the game source is C:\...
std::string getAppdataDirectory() override
Gets the APPDATA directory. On Windows, this is the folder in the APPDATA% enviroment variable....
std::string getLastWatchPath() const override