18#include "physfs/physfs.h"
20#ifdef EVENGINE_WINDOWS
33#include "webgpu/webplatform.h"
39#ifdef EVENGINE_ANDROID
42#include "android/android.h"
48size_t getDriveDelim(
const std::string &
input) {
49 for (
size_t i = 0; i <
input.size(); ++i)
50 if (
input[i] ==
'/' ||
input[i] ==
'\\')
return i;
55std::string getDriveRoot(
const std::string &
input) {
return input.substr(0, getDriveDelim(
input) + 1); }
57std::string skipDriveRoot(
const std::string &
input) {
return input.substr(getDriveDelim(
input) + 1); }
60 std::stringstream out;
62 bool seenSep =
false, isSep =
false;
63 for (
size_t i = 0; i <
input.size(); ++i) {
65 if (!isSep || !seenSep) out <<
input[i];
79 requirePath = {
"?.lua",
"?/init.lua"};
80 cRequirePath = {
"??"};
83 const std::string a0 =
c->getArgv(0);
84 if (a0.empty())
init(NULL);
85 else init(a0.c_str());
96 memoryArchive_ =
nullptr;
99#ifdef EVENGINE_ANDROID
100 android::deinitializeVirtualArchive();
103 if (PHYSFS_isInit()) PHYSFS_deinit();
107 if (!PHYSFS_init(arg0))
108 throw Exception(
"Failed to initialize filesystem: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
115 if (fusedSet)
return;
121 if (!fusedSet)
return false;
126 if (!PHYSFS_isInit())
return false;
128 std::string old_save_path = save_path_full;
131 save_identity = std::string(ident);
141 save_path_full += save_path_relative;
143 save_path_full = normalize(save_path_full);
145#ifdef EVENGINE_ANDROID
146 if (save_identity ==
"") save_identity =
"unnamed";
148 std::string storage_path;
150 storage_path = SDL_AndroidGetExternalStoragePath();
152 storage_path = SDL_AndroidGetInternalStoragePath();
154 std::string save_directory = storage_path +
"/save";
156 save_path_full = storage_path + std::string(
"/save/") + save_identity;
158 if (!android::directoryExists(save_path_full.c_str()) && !android::mkdir(save_path_full.c_str()))
159 SDL_Log(
"Error: Could not create save directory %s!", save_path_full.c_str());
169 if (!old_save_path.empty()) PHYSFS_unmount(old_save_path.c_str());
173 PHYSFS_mount(save_path_full.c_str(),
nullptr, appendToPath);
178 PHYSFS_setWriteDir(
nullptr);
186 if (!PHYSFS_isInit())
return false;
189 if (!game_source.empty())
return false;
191 std::string new_search_path =
source;
193#ifdef EVENGINE_ANDROID
194 if (!android::createStorageDirectories()) SDL_Log(
"Error creating storage directories!");
196 new_search_path =
"";
198 PHYSFS_Io *gameLoveIO;
199 bool hasFusedGame = android::checkFusedGame((
void **)&gameLoveIO);
204 gameLoveIO->destroy(gameLoveIO);
207 if (!android::initializeVirtualArchive()) {
208 SDL_Log(
"Unable to mount AAsset: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
214 new_search_path = android::getSelectedGameFile();
217 if (!PHYSFS_mount(new_search_path.c_str(),
nullptr, 1)) {
219 SDL_Log(
"Mounting %s did not work. Loading to memory.", new_search_path.c_str());
220 char * game_archive_ptr = NULL;
221 size_t game_archive_size = 0;
222 if (!android::loadGameArchiveToMemory(new_search_path.c_str(), &game_archive_ptr,
223 &game_archive_size)) {
224 SDL_Log(
"Failure memory loading archive %s", new_search_path.c_str());
227 if (!PHYSFS_mountMemory(game_archive_ptr, game_archive_size, android::freeGameArchiveMemory,
228 "archive.zip",
"/", 0)) {
229 SDL_Log(
"Failure mounting in-memory archive.");
230 android::freeGameArchiveMemory(game_archive_ptr);
237 if (!PHYSFS_mount(new_search_path.c_str(),
nullptr, 1))
return false;
241 game_source = new_search_path;
249 if (!PHYSFS_isInit() || !data ||
size == 0)
return false;
251 if (!game_source.empty())
return false;
256 if (!
owned)
return false;
260 if (!PHYSFS_mountMemory(
owned,
size,
nullptr,
"game.eve",
"/", 1)) {
261 const auto err = PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode());
262 std::cerr <<
"PhysFS mountMemory failed: " << (err ? err :
"unknown error") << std::endl;
267 memoryArchive_ =
owned;
268 game_source =
"game.eve";
273 if (!PHYSFS_isInit())
return false;
276 if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty())
return false;
280 std::string temp_writedir = getDriveRoot(save_path_full);
281 std::string temp_createdir = skipDriveRoot(save_path_full);
292 size_t startpos = temp_createdir.find_first_not_of(
'/');
293 if (startpos != std::string::npos) temp_createdir = temp_createdir.substr(startpos);
298 if (!PHYSFS_setWriteDir(temp_writedir.c_str()))
return false;
303 PHYSFS_setWriteDir(
nullptr);
308 if (!PHYSFS_setWriteDir(save_path_full.c_str()))
return false;
311 if (!PHYSFS_mount(save_path_full.c_str(),
nullptr, 0)) {
312 PHYSFS_setWriteDir(
nullptr);
320 if (!PHYSFS_isInit() || archive.empty())
return false;
322 std::string realPath;
326 auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
328 if (it != allowedMountPaths.end())
330 else if (
isFused() && sourceBase.compare(archive) == 0) {
333 realPath = sourceBase;
336 if (archive.size() == 0 || archive.find(
"..") != string::npos || archive ==
"/")
return false;
338 const char* realDir = PHYSFS_getRealDir(archive.c_str());
339 if (!realDir)
return false;
345 if (realPath.find(game_source) == 0)
return false;
351 if (realPath.length() == 0)
return false;
353 return PHYSFS_mount(realPath.c_str(), mountpoint.c_str(), appendToPath) != 0;
357 if (!PHYSFS_isInit())
return false;
359 if (PHYSFS_mountMemory(data->getData(), data->getSize(),
nullptr, archivename.c_str(), mountpoint.c_str(), appendToPath) != 0) {
360 mountedData[archivename] = data;
368 if (!PHYSFS_isInit() || realDir.empty())
return false;
370 if (mountpoint.empty()) mountpoint =
"/";
374 std::lock_guard<std::mutex> lock(mountMu_);
375 if (std::find(mountedRealDirs_.begin(), mountedRealDirs_.end(), realDir) == mountedRealDirs_.end()) {
376 if (!PHYSFS_mount(realDir.c_str(), mountpoint.c_str(), appendToPath ? 1 : 0))
return false;
377 mountedRealDirs_.push_back(realDir);
383 std::lock_guard<std::mutex> lock(mountMu_);
384 auto it = std::find(mountedRealDirs_.begin(), mountedRealDirs_.end(), realDir);
385 if (it == mountedRealDirs_.end())
return false;
386 const bool ok = PHYSFS_unmount(realDir.c_str()) != 0;
387 mountedRealDirs_.erase(it);
392 if (!PHYSFS_isInit() || archive.empty())
return false;
394 auto datait = mountedData.find(archive);
396 if (datait != mountedData.end() && PHYSFS_unmount(archive.c_str()) != 0) {
397 mountedData.erase(datait);
401 std::string realPath;
405 auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
407 if (it != allowedMountPaths.end())
409 else if (
isFused() && sourceBase.compare(archive) == 0) {
412 realPath = sourceBase;
415 if (archive.size() == 0 || archive.find(
"..") != string::npos || archive ==
"/")
return false;
417 const char* realDir = PHYSFS_getRealDir(archive.c_str());
418 if (!realDir)
return false;
425 const char* mountPoint = PHYSFS_getMountPoint(realPath.c_str());
426 if (!mountPoint)
return false;
428 return PHYSFS_unmount(realPath.c_str()) != 0;
432 for (
const auto &datapair : mountedData) {
433 if (datapair.second == data) {
434 std::string archive = datapair.first;
435 return unmount(archive.c_str());
446#ifdef EVENGINE_WINDOWS
450 cwd = to_utf8(w_cwd);
451 replace_char(cwd,
'\\',
'/');
468 static std::string userDir = normalize(ios::getHomeDirectory());
469#elif defined(EVENGINE_WEBGPU)
470 static std::string userDir = normalize(eve::webgpu_platform::getHomeDirectory());
476#pragma GCC diagnostic push
477#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
479 const char*
dir = PHYSFS_getUserDir();
481#pragma GCC diagnostic pop
483 static std::string userDir;
484 if (
dir) userDir = normalize(
dir);
490 if (appdata.empty()) {
491#ifdef EVENGINE_WINDOWS_UWP
493#elif defined(EVENGINE_WEBGPU)
494 appdata = normalize(eve::webgpu_platform::getAppdataDirectory());
495#elif defined(EVENGINE_WINDOWS)
496 wchar_t *w_appdata = _wgetenv(L
"APPDATA");
497 appdata = to_utf8(w_appdata);
498 replace_char(appdata,
'\\',
'/');
499#elif defined(EVENGINE_MACOSX)
501 udir.append(
"/Library/Application Support");
502 appdata = normalize(udir);
503#elif defined(EVENGINE_IOS)
504 appdata = normalize(ios::getAppdataDirectory());
505#elif defined(EVENGINE_LINUX)
506 char *xdgdatahome = getenv(
"XDG_DATA_HOME");
510 appdata = xdgdatahome;
521 size_t source_len = game_source.length();
523 if (source_len == 0)
return "";
527#ifdef EVENGINE_WINDOWS
529 size_t base_end_pos = game_source.find_last_of(
"/\\", source_len - 2);
531 size_t base_end_pos = game_source.find_last_of(
'/', source_len - 2);
534 if (base_end_pos == std::string::npos)
return "";
537 if (base_end_pos == 0) base_end_pos = 1;
539 return game_source.substr(0, base_end_pos);
543 if (!PHYSFS_isInit())
throw Exception(
"PhysFS is not initialized.");
545 const char*
dir = PHYSFS_getRealDir(filename.c_str());
547 if (
dir ==
nullptr)
throw Exception(
"File does not exist on disk.");
549 return std::string(
dir);
553 if (!PHYSFS_isInit())
return false;
555 PHYSFS_Stat stat = {};
556 if (!PHYSFS_stat(filepath.c_str(), &stat))
return false;
558 info.size = (int64_t)stat.filesize;
559 info.modtime = (int64_t)stat.modtime;
561 if (stat.filetype == PHYSFS_FILETYPE_REGULAR)
563 else if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY)
564 info.type =
"directory";
565 else if (stat.filetype == PHYSFS_FILETYPE_SYMLINK)
566 info.type =
"symlink";
574 if (!PHYSFS_isInit())
return false;
578 if (!PHYSFS_mkdir(
dir.c_str()))
return false;
584 if (!PHYSFS_isInit())
return false;
588 if (!PHYSFS_delete(
file.c_str()))
return false;
621 std::vector<std::string> items;
622 if (!PHYSFS_isInit())
return items;
624 char **rc = PHYSFS_enumerateFiles(
dir.c_str());
626 if (rc ==
nullptr)
return items;
628 for (
char **i = rc; *i != 0; i++) items.push_back(*i);
635 if (!PHYSFS_isInit())
return;
637 PHYSFS_permitSymbolicLinks(enable ? 1 : 0);
641 if (!PHYSFS_isInit())
return false;
643 return PHYSFS_symbolicLinksPermitted() != 0;
651 if (std::find(allowedMountPaths.begin(), allowedMountPaths.end(),
path) == allowedMountPaths.end())
652 allowedMountPaths.push_back(
path);
657bool looksAbsolute(
const std::string &
path) {
658 if (
path.empty())
return false;
659 if (
path[0] ==
'/' ||
path[0] ==
'\\')
return true;
660#ifdef EVENGINE_WINDOWS
661 if (
path.size() >= 2 &&
path[1] ==
':')
return true;
666std::string joinPath(
const std::string &
a,
const std::string &
b) {
667 if (
a.empty())
return b;
668 if (
b.empty())
return a;
669 char last =
a.back();
670 if (last ==
'/' || last ==
'\\')
return a +
b;
674std::string parentDir(
const std::string &
path) {
675 auto pos =
path.find_last_of(
"/\\");
676 if (
pos == std::string::npos)
return ".";
677 if (
pos == 0)
return "/";
681std::string baseName(
const std::string &
path) {
682 auto pos =
path.find_last_of(
"/\\");
683 if (
pos == std::string::npos)
return path;
687bool hasParentSegment(
const std::string &
path) {
688 if (
path ==
"..")
return true;
689 if (
path.size() >= 3 && (
path.compare(0, 3,
"../") == 0 ||
path.compare(0, 3,
"..\\") == 0))
691 return path.find(
"/..") != std::string::npos ||
path.find(
"\\..") != std::string::npos;
694std::filesystem::path pathFromUtf8(
const std::string &
text) {
695 const auto *data =
reinterpret_cast<const char8_t *
>(
text.data());
696 return std::filesystem::path(std::u8string_view(data,
text.size()));
699std::string pathToUtf8(
const std::filesystem::path &
p) {
700 auto u8 =
p.u8string();
701 return std::string(
reinterpret_cast<const char *
>(u8.data()), u8.size());
706FileWatch &Filesystem::watchers() {
707 if (!fileWatch_) fileWatch_ = std::make_unique<FileWatch>();
711bool Filesystem::resolveWatchTarget(
const std::string &
path, std::string &realDir,
712 std::string &filterName, std::string &reportPath) {
716 if (
path.empty())
return false;
726 if (looksAbsolute(
path)) {
731 realDir = parentDir(
path);
732 filterName = baseName(
path);
738 if (hasParentSegment(
path)) {
740 auto candidate = std::filesystem::weakly_canonical(
743 const std::string full = pathToUtf8(candidate);
749 realDir = parentDir(full);
750 filterName = baseName(full);
763 std::string full = joinPath(
root,
path);
764 if (
info.type ==
"directory") {
769 realDir = parentDir(full);
770 filterName = baseName(full);
786 filterName = baseName(
path);
796 realDir = workingDir;
797 filterName = baseName(
path);
806 filterName = baseName(
path);
813 std::string realDir,
filter, report;
814 if (!resolveWatchTarget(
path, realDir,
filter, report))
return false;
815 return watchers().
add(realDir,
filter, report, 1);
819 if (realDir.empty() || reportPath.empty()) {
822 "watchRealDirectory requires a real directory and a report path"));
828 if (!watchers().add(std::move(realDir),
"", std::move(reportPath), 1)) {
838 if (fileWatch_) fileWatch_->clear();
842 if (!fileWatch_)
return 0;
843 return fileWatch_->count();
847 if (!fileWatch_)
return "";
849 if (!fileWatch_->poll(ev)) {
850 lastWatchPath_.clear();
851 lastWatchRealPath_.clear();
854 lastWatchPath_ = ev.
path;
Stable, structured diagnostics shared by engine modules.
#define EVENGINE_MAX_PATH
#define EVENGINE_PATH_SEPARATOR
#define EVENGINE_APPDATA_PREFIX
#define EVENGINE_APPDATA_FOLDER
const UnitySourceAsset & source
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
EVENGINE_API_FOUNDATION public API.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
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
Returns the save directory.
void append(std::string filename, const void *data, int64_t size) const override
Append.
bool setSource(std::string source) override
Sets the source.
int getWatchCount() const override
Returns the watch count.
bool setIdentity(std::string ident, bool appendToPath=false) override
Sets the identity.
void unwatchAll() override
Stops watching all.
std::string getRealDirectory(std::string filename) const override
Returns the real directory.
void setFused(bool value) override
Sets the fused.
bool setSourceFromMemory(const void *data, size_t size) override
Sets the source from memory.
~Filesystem() override
Filesystem.
eve::Result< void > watchRealDirectory(std::string realDir, std::string reportPath) override
Watches real directory.
bool watch(std::string path) override
Watches watch.
std::string getSource() const override
Returns the source.
filesystem::File * newFile(std::string filename) const override
Creates a file. @ownership Caller deletes unless documented otherwise.
std::string getUserDirectory() override
Returns the user directory.
bool createDirectory(std::string dir) override
Creates directory.
bool remove(std::string file) override
Removes remove.
std::vector< std::string > getDirectoryItems(std::string dir) override
Returns the directory items.
bool isFused() const override
True when fused.
bool unmount(std::string archive) override
Unmounts unmount.
std::string pollWatch() override
Polls watch.
std::string getLastWatchRealPath() const override
Returns the last watch real path.
bool mountRealDirectory(std::string realDir, std::string mountpoint, bool appendToPath=false) override
Mounts real directory.
bool unwatch(std::string path) override
Stops watching unwatch.
bool mount(std::string archive, std::string mountpoint, bool appendToPath=false) override
Mounts mount.
std::string getIdentity() const override
Returns the identity.
void init(const char *arg0) override
Initializes init.
bool setupWriteDirectory() override
Setup write directory.
std::vector< std::string > & getCRequirePath() override
Returns the c require path.
std::string getWorkingDirectory() override
Returns the working directory.
FileData * read(std::string filename, int64_t size=File::ALL) const override
Reads read.
bool unmountRealDirectory(std::string realDir) override
Unmounts real directory.
void allowMountingForPath(const std::string &path) override
Allow mounting for path.
void write(std::string filename, const void *data, int64_t size) const override
Writes write.
void setSymlinksEnabled(bool enable) override
Sets the symlinks enabled.
bool getInfo(std::string filepath, Info &info) const override
Returns the info.
bool areSymlinksEnabled() const override
Are symlinks enabled.
std::vector< std::string > & getRequirePath() override
Returns the require path.
std::string getSourceBaseDirectory() const override
Returns the source base directory.
std::string getAppdataDirectory() override
Returns the appdata directory.
std::string getLastWatchPath() const override
Returns the last watch path.
Vec2 normalize(const Vec2 &a)
Normalize.
Build metadata (engine git commit, build time, third-party version).