1 /**
2  * Copyright (c) 2006-2016 LOVE Development Team
3  *
4  * This software is provided 'as-is', without any express or implied
5  * warranty.  In no event will the authors be held liable for any damages
6  * arising from the use of this software.
7  *
8  * Permission is granted to anyone to use this software for any purpose,
9  * including commercial applications, and to alter it and redistribute it
10  * freely, subject to the following restrictions:
11  *
12  * 1. The origin of this software must not be misrepresented; you must not
13  *    claim that you wrote the original software. If you use this software
14  *    in a product, an acknowledgment in the product documentation would be
15  *    appreciated but is not required.
16  * 2. Altered source versions must be plainly marked as such, and must not be
17  *    misrepresented as being the original software.
18  * 3. This notice may not be removed or altered from any source distribution.
19  **/
20 
21 #include <iostream>
22 #include <sstream>
23 #include <algorithm>
24 
25 #include "common/utf8.h"
26 #include "common/b64.h"
27 
28 #include "Filesystem.h"
29 #include "File.h"
30 
31 // PhysFS
32 #ifdef LOVE_APPLE_USE_FRAMEWORKS
33 #include <physfs/physfs.h>
34 #else
35 #include <physfs.h>
36 #endif
37 
38 // For great CWD. (Current Working Directory)
39 // Using this instead of boost::filesystem which totally
40 // cramped our style.
41 #ifdef LOVE_WINDOWS
42 #	include <windows.h>
43 #	include <direct.h>
44 #else
45 #	include <sys/param.h>
46 #	include <unistd.h>
47 #endif
48 
49 #ifdef LOVE_IOS
50 #	include "common/ios.h"
51 #endif
52 
53 #include <string>
54 
55 #ifdef LOVE_ANDROID
56 #include <SDL.h>
57 #include "common/android.h"
58 #endif
59 
60 namespace
61 {
getDriveDelim(const std::string & input)62 	size_t getDriveDelim(const std::string &input)
63 	{
64 		for (size_t i = 0; i < input.size(); ++i)
65 			if (input[i] == '/' || input[i] == '\\')
66 				return i;
67 		// Something's horribly wrong
68 		return 0;
69 	}
70 
getDriveRoot(const std::string & input)71 	std::string getDriveRoot(const std::string &input)
72 	{
73 		return input.substr(0, getDriveDelim(input)+1);
74 	}
75 
skipDriveRoot(const std::string & input)76 	std::string skipDriveRoot(const std::string &input)
77 	{
78 		return input.substr(getDriveDelim(input)+1);
79 	}
80 
normalize(const std::string & input)81 	std::string normalize(const std::string &input)
82 	{
83 		std::stringstream out;
84 		bool seenSep = false, isSep = false;
85 		for (size_t i = 0; i < input.size(); ++i)
86 		{
87 			isSep = (input[i] == LOVE_PATH_SEPARATOR[0]);
88 			if (!isSep || !seenSep)
89 				out << input[i];
90 			seenSep = isSep;
91 		}
92 
93 		return out.str();
94 	}
95 
96 }
97 
98 namespace love
99 {
100 namespace filesystem
101 {
102 namespace physfs
103 {
104 
Filesystem()105 Filesystem::Filesystem()
106 	: fused(false)
107 	, fusedSet(false)
108 {
109 	requirePath = {"?.lua", "?/init.lua"};
110 }
111 
~Filesystem()112 Filesystem::~Filesystem()
113 {
114 	if (PHYSFS_isInit())
115 		PHYSFS_deinit();
116 }
117 
getName() const118 const char *Filesystem::getName() const
119 {
120 	return "love.filesystem.physfs";
121 }
122 
init(const char * arg0)123 void Filesystem::init(const char *arg0)
124 {
125 	if (!PHYSFS_init(arg0))
126 		throw love::Exception("%s", PHYSFS_getLastError());
127 
128 	// Enable symlinks by default. Also fixes an issue in PhysFS 2.1-alpha.
129 	setSymlinksEnabled(true);
130 }
131 
setFused(bool fused)132 void Filesystem::setFused(bool fused)
133 {
134 	if (fusedSet)
135 		return;
136 	this->fused = fused;
137 	fusedSet = true;
138 }
139 
isFused() const140 bool Filesystem::isFused() const
141 {
142 	if (!fusedSet)
143 		return false;
144 	return fused;
145 }
146 
setIdentity(const char * ident,bool appendToPath)147 bool Filesystem::setIdentity(const char *ident, bool appendToPath)
148 {
149 	if (!PHYSFS_isInit())
150 		return false;
151 
152 	std::string old_save_path = save_path_full;
153 
154 	// Store the save directory.
155 	save_identity = std::string(ident);
156 
157 	// Generate the relative path to the game save folder.
158 	save_path_relative = std::string(LOVE_APPDATA_PREFIX LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + save_identity;
159 
160 	// Generate the full path to the game save folder.
161 	save_path_full = std::string(getAppdataDirectory()) + std::string(LOVE_PATH_SEPARATOR);
162 	if (fused)
163 		save_path_full += std::string(LOVE_APPDATA_PREFIX) + save_identity;
164 	else
165 		save_path_full += save_path_relative;
166 
167 	save_path_full = normalize(save_path_full);
168 
169 #ifdef LOVE_ANDROID
170 	if (save_identity == "")
171 		save_identity = "unnamed";
172 
173 	std::string storage_path;
174 	if (isAndroidSaveExternal())
175 		storage_path = SDL_AndroidGetExternalStoragePath();
176 	else
177 		storage_path = SDL_AndroidGetInternalStoragePath();
178 
179 	std::string save_directory = storage_path + "/save";
180 
181 	save_path_full = storage_path + std::string("/save/") + save_identity;
182 
183 	if (!love::android::directoryExists(save_path_full.c_str()) &&
184 			!love::android::mkdir(save_path_full.c_str()))
185 		SDL_Log("Error: Could not create save directory %s!", save_path_full.c_str());
186 #endif
187 
188 	// We now have something like:
189 	// save_identity: game
190 	// save_path_relative: ./LOVE/game
191 	// save_path_full: C:\Documents and Settings\user\Application Data/LOVE/game
192 
193 	// We don't want old read-only save paths to accumulate when we set a new
194 	// identity.
195 	if (!old_save_path.empty())
196 	{
197 #ifdef LOVE_USE_PHYSFS_2_1
198 		PHYSFS_unmount(old_save_path.c_str());
199 #else
200 		PHYSFS_removeFromSearchPath(old_save_path.c_str());
201 #endif
202 	}
203 
204 	// Try to add the save directory to the search path.
205 	// (No error on fail, it means that the path doesn't exist).
206 	PHYSFS_mount(save_path_full.c_str(), nullptr, appendToPath);
207 
208 	// HACK: This forces setupWriteDirectory to be called the next time a file
209 	// is opened for writing - otherwise it won't be called at all if it was
210 	// already called at least once before.
211 	PHYSFS_setWriteDir(nullptr);
212 
213 	return true;
214 }
215 
getIdentity() const216 const char *Filesystem::getIdentity() const
217 {
218 	return save_identity.c_str();
219 }
220 
setSource(const char * source)221 bool Filesystem::setSource(const char *source)
222 {
223 	if (!PHYSFS_isInit())
224 		return false;
225 
226 	// Check whether directory is already set.
227 	if (!game_source.empty())
228 		return false;
229 
230 	std::string new_search_path = source;
231 
232 #ifdef LOVE_ANDROID
233 	if (!love::android::createStorageDirectories())
234 		SDL_Log("Error creating storage directories!");
235 
236 	char* game_archive_ptr = NULL;
237 	size_t game_archive_size = 0;
238 	bool archive_loaded = false;
239 
240 	// try to load the game that was sent to LÖVE via a Intent
241 	archive_loaded = love::android::loadGameArchiveToMemory(love::android::getSelectedGameFile(), &game_archive_ptr, &game_archive_size);
242 
243 	if (!archive_loaded)
244 	{
245 		// try to load the game in the assets/ folder
246 		archive_loaded = love::android::loadGameArchiveToMemory("game.love", &game_archive_ptr, &game_archive_size);
247 	}
248 
249 	if (archive_loaded)
250 	{
251 		if (!PHYSFS_mountMemory(game_archive_ptr, game_archive_size, love::android::freeGameArchiveMemory, "archive.zip", "/", 0))
252 		{
253 			SDL_Log("Mounting of in-memory game archive failed!");
254 			love::android::freeGameArchiveMemory(game_archive_ptr);
255 			return false;
256 		}
257 	}
258 	else
259 	{
260 		// try to load the game in the directory that was sent to LÖVE via an
261 		// Intent ...
262 		std::string game_path = std::string(love::android::getSelectedGameFile());
263 
264 		if (game_path == "")
265 		{
266 			// ... or fall back to the game at /sdcard/lovegame
267 			game_path = "/sdcard/lovegame/";
268 		}
269 
270 		SDL_RWops *sdcard_main = SDL_RWFromFile(std::string(game_path + "main.lua").c_str(), "rb");
271 
272 		if (sdcard_main)
273 		{
274 			new_search_path = game_path;
275 			sdcard_main->close(sdcard_main);
276 
277 			if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
278 			{
279 				SDL_Log("mounting of %s failed", new_search_path.c_str());
280 				return false;
281 			}
282 		}
283 		else
284 		{
285 			// Neither assets/game.love or /sdcard/lovegame was mounted
286 			// sucessfully, therefore simply fail.
287 			return false;
288 		}
289 	}
290 #else
291 	// Add the directory.
292 	if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
293 		return false;
294 #endif
295 
296 	// Save the game source.
297 	game_source = new_search_path;
298 
299 	return true;
300 }
301 
getSource() const302 const char *Filesystem::getSource() const
303 {
304 	return game_source.c_str();
305 }
306 
setupWriteDirectory()307 bool Filesystem::setupWriteDirectory()
308 {
309 	if (!PHYSFS_isInit())
310 		return false;
311 
312 	// These must all be set.
313 	if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty())
314 		return false;
315 
316 	// We need to make sure the write directory is created. To do that, we also
317 	// need to make sure all its parent directories are also created.
318 	std::string temp_writedir = getDriveRoot(save_path_full);
319 	std::string temp_createdir = skipDriveRoot(save_path_full);
320 
321 	// On some sandboxed platforms, physfs will break when its write directory
322 	// is the root of the drive and it tries to create a folder (even if the
323 	// folder's path is in a writable location.) If the user's home folder is
324 	// in the save path, we'll try starting from there instead.
325 	if (save_path_full.find(getUserDirectory()) == 0)
326 	{
327 		temp_writedir = getUserDirectory();
328 		temp_createdir = save_path_full.substr(getUserDirectory().length());
329 
330 		// Strip leading '/' characters from the path we want to create.
331 		size_t startpos = temp_createdir.find_first_not_of('/');
332 		if (startpos != std::string::npos)
333 			temp_createdir = temp_createdir.substr(startpos);
334 	}
335 
336 	// Set either '/' or the user's home as a writable directory.
337 	// (We must create the save folder before mounting it).
338 	if (!PHYSFS_setWriteDir(temp_writedir.c_str()))
339 		return false;
340 
341 	// Create the save folder. (We're now "at" either '/' or the user's home).
342 	if (!createDirectory(temp_createdir.c_str()))
343 	{
344 		// Clear the write directory in case of error.
345 		PHYSFS_setWriteDir(nullptr);
346 		return false;
347 	}
348 
349 	// Set the final write directory.
350 	if (!PHYSFS_setWriteDir(save_path_full.c_str()))
351 		return false;
352 
353 	// Add the directory. (Will not be readded if already present).
354 	if (!PHYSFS_mount(save_path_full.c_str(), nullptr, 0))
355 	{
356 		PHYSFS_setWriteDir(nullptr); // Clear the write directory in case of error.
357 		return false;
358 	}
359 
360 	return true;
361 }
362 
mount(const char * archive,const char * mountpoint,bool appendToPath)363 bool Filesystem::mount(const char *archive, const char *mountpoint, bool appendToPath)
364 {
365 	if (!PHYSFS_isInit() || !archive)
366 		return false;
367 
368 	std::string realPath;
369 	std::string sourceBase = getSourceBaseDirectory();
370 
371 	// Check whether the given archive path is in the list of allowed full paths.
372 	auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
373 
374 	if (it != allowedMountPaths.end())
375 		realPath = *it;
376 	else if (isFused() && sourceBase.compare(archive) == 0)
377 	{
378 		// Special case: if the game is fused and the archive is the source's
379 		// base directory, mount it even though it's outside of the save dir.
380 		realPath = sourceBase;
381 	}
382 	else
383 	{
384 		// Not allowed for safety reasons.
385 		if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0)
386 			return false;
387 
388 		const char *realDir = PHYSFS_getRealDir(archive);
389 		if (!realDir)
390 			return false;
391 
392 		realPath = realDir;
393 
394 		// Always disallow mounting of files inside the game source, since it
395 		// won't work anyway if the game source is a zipped .love file.
396 		if (realPath.find(game_source) == 0)
397 			return false;
398 
399 		realPath += LOVE_PATH_SEPARATOR;
400 		realPath += archive;
401 	}
402 
403 	if (realPath.length() == 0)
404 		return false;
405 
406 	return PHYSFS_mount(realPath.c_str(), mountpoint, appendToPath) != 0;
407 }
408 
unmount(const char * archive)409 bool Filesystem::unmount(const char *archive)
410 {
411 	if (!PHYSFS_isInit() || !archive)
412 		return false;
413 
414 	std::string realPath;
415 	std::string sourceBase = getSourceBaseDirectory();
416 
417 	// Check whether the given archive path is in the list of allowed full paths.
418 	auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
419 
420 	if (it != allowedMountPaths.end())
421 		realPath = *it;
422 	else if (isFused() && sourceBase.compare(archive) == 0)
423 	{
424 		// Special case: if the game is fused and the archive is the source's
425 		// base directory, unmount it even though it's outside of the save dir.
426 		realPath = sourceBase;
427 	}
428 	else
429 	{
430 		// Not allowed for safety reasons.
431 		if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0)
432 			return false;
433 
434 		const char *realDir = PHYSFS_getRealDir(archive);
435 		if (!realDir)
436 			return false;
437 
438 		realPath = realDir;
439 		realPath += LOVE_PATH_SEPARATOR;
440 		realPath += archive;
441 	}
442 
443 	const char *mountPoint = PHYSFS_getMountPoint(realPath.c_str());
444 	if (!mountPoint)
445 		return false;
446 
447 #ifdef LOVE_USE_PHYSFS_2_1
448 	return PHYSFS_unmount(realPath.c_str()) != 0;
449 #else
450 	return PHYSFS_removeFromSearchPath(realPath.c_str()) != 0;
451 #endif
452 }
453 
newFile(const char * filename) const454 love::filesystem::File *Filesystem::newFile(const char *filename) const
455 {
456 	return new File(filename);
457 }
458 
newFileData(void * data,unsigned int size,const char * filename) const459 FileData *Filesystem::newFileData(void *data, unsigned int size, const char *filename) const
460 {
461 	FileData *fd = new FileData(size, std::string(filename));
462 
463 	// Copy the data into FileData.
464 	memcpy(fd->getData(), data, size);
465 
466 	return fd;
467 }
468 
newFileData(const char * b64,const char * filename) const469 FileData *Filesystem::newFileData(const char *b64, const char *filename) const
470 {
471 	int size = (int) strlen(b64);
472 	int outsize = 0;
473 	char *dst = b64_decode(b64, size, outsize);
474 	FileData *fd = new FileData(outsize, std::string(filename));
475 
476 	// Copy the data into FileData.
477 	memcpy(fd->getData(), dst, outsize);
478 	delete [] dst;
479 
480 	return fd;
481 }
482 
getWorkingDirectory()483 const char *Filesystem::getWorkingDirectory()
484 {
485 	if (cwd.empty())
486 	{
487 #ifdef LOVE_WINDOWS
488 
489 		WCHAR w_cwd[LOVE_MAX_PATH];
490 		_wgetcwd(w_cwd, LOVE_MAX_PATH);
491 		cwd = to_utf8(w_cwd);
492 		replace_char(cwd, '\\', '/');
493 #else
494 		char *cwd_char = new char[LOVE_MAX_PATH];
495 
496 		if (getcwd(cwd_char, LOVE_MAX_PATH))
497 			cwd = cwd_char; // if getcwd fails, cwd_char (and thus cwd) will still be empty
498 
499 		delete [] cwd_char;
500 #endif
501 	}
502 
503 	return cwd.c_str();
504 }
505 
getUserDirectory()506 std::string Filesystem::getUserDirectory()
507 {
508 #ifdef LOVE_IOS
509 	// PHYSFS_getUserDir doesn't give exactly the path we want on iOS.
510 	static std::string userDir = normalize(love::ios::getHomeDirectory());
511 #else
512 	static std::string userDir = normalize(PHYSFS_getUserDir());
513 #endif
514 
515 	return userDir;
516 }
517 
getAppdataDirectory()518 std::string Filesystem::getAppdataDirectory()
519 {
520 	if (appdata.empty())
521 	{
522 #ifdef LOVE_WINDOWS_UWP
523 		appdata = getUserDirectory();
524 #elif defined(LOVE_WINDOWS)
525 		wchar_t *w_appdata = _wgetenv(L"APPDATA");
526 		appdata = to_utf8(w_appdata);
527 		replace_char(appdata, '\\', '/');
528 #elif defined(LOVE_MACOSX)
529 		std::string udir = getUserDirectory();
530 		udir.append("/Library/Application Support");
531 		appdata = normalize(udir);
532 #elif defined(LOVE_IOS)
533 		appdata = normalize(love::ios::getAppdataDirectory());
534 #elif defined(LOVE_LINUX)
535 		char *xdgdatahome = getenv("XDG_DATA_HOME");
536 		if (!xdgdatahome)
537 			appdata = normalize(std::string(getUserDirectory()) + "/.local/share/");
538 		else
539 			appdata = xdgdatahome;
540 #else
541 		appdata = getUserDirectory();
542 #endif
543 	}
544 	return appdata;
545 }
546 
547 
getSaveDirectory()548 const char *Filesystem::getSaveDirectory()
549 {
550 	return save_path_full.c_str();
551 }
552 
getSourceBaseDirectory() const553 std::string Filesystem::getSourceBaseDirectory() const
554 {
555 	size_t source_len = game_source.length();
556 
557 	if (source_len == 0)
558 		return "";
559 
560 	// FIXME: This doesn't take into account parent and current directory
561 	// symbols (i.e. '..' and '.')
562 #ifdef LOVE_WINDOWS
563 	// In windows, delimiters can be either '/' or '\'.
564 	size_t base_end_pos = game_source.find_last_of("/\\", source_len - 2);
565 #else
566 	size_t base_end_pos = game_source.find_last_of('/', source_len - 2);
567 #endif
568 
569 	if (base_end_pos == std::string::npos)
570 		return "";
571 
572 	// If the source is in the unix root (aka '/'), we want to keep the '/'.
573 	if (base_end_pos == 0)
574 		base_end_pos = 1;
575 
576 	return game_source.substr(0, base_end_pos);
577 }
578 
getRealDirectory(const char * filename) const579 std::string Filesystem::getRealDirectory(const char *filename) const
580 {
581 	if (!PHYSFS_isInit())
582 		throw love::Exception("PhysFS is not initialized.");
583 
584 	const char *dir = PHYSFS_getRealDir(filename);
585 
586 	if (dir == nullptr)
587 		throw love::Exception("File does not exist.");
588 
589 	return std::string(dir);
590 }
591 
exists(const char * path) const592 bool Filesystem::exists(const char *path) const
593 {
594 	if (!PHYSFS_isInit())
595 		return false;
596 
597 	return PHYSFS_exists(path) != 0;
598 }
599 
isDirectory(const char * dir) const600 bool Filesystem::isDirectory(const char *dir) const
601 {
602 	if (!PHYSFS_isInit())
603 		return false;
604 
605 #ifdef LOVE_USE_PHYSFS_2_1
606 	PHYSFS_Stat stat = {};
607 	if (PHYSFS_stat(dir, &stat))
608 		return stat.filetype == PHYSFS_FILETYPE_DIRECTORY;
609 	else
610 		return false;
611 #else
612 	return PHYSFS_isDirectory(dir) != 0;
613 #endif
614 }
615 
isFile(const char * file) const616 bool Filesystem::isFile(const char *file) const
617 {
618 	if (!PHYSFS_isInit())
619 		return false;
620 
621 	return PHYSFS_exists(file) && !isDirectory(file);
622 }
623 
isSymlink(const char * filename) const624 bool Filesystem::isSymlink(const char *filename) const
625 {
626 	if (!PHYSFS_isInit())
627 		return false;
628 
629 #ifdef LOVE_USE_PHYSFS_2_1
630 	PHYSFS_Stat stat = {};
631 	if (PHYSFS_stat(filename, &stat))
632 		return stat.filetype == PHYSFS_FILETYPE_SYMLINK;
633 	else
634 		return false;
635 #else
636 	return PHYSFS_isSymbolicLink(filename) != 0;
637 #endif
638 }
639 
createDirectory(const char * dir)640 bool Filesystem::createDirectory(const char *dir)
641 {
642 	if (!PHYSFS_isInit())
643 		return false;
644 
645 	if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory())
646 		return false;
647 
648 	if (!PHYSFS_mkdir(dir))
649 		return false;
650 
651 	return true;
652 }
653 
remove(const char * file)654 bool Filesystem::remove(const char *file)
655 {
656 	if (!PHYSFS_isInit())
657 		return false;
658 
659 	if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory())
660 		return false;
661 
662 	if (!PHYSFS_delete(file))
663 		return false;
664 
665 	return true;
666 }
667 
read(const char * filename,int64 size) const668 FileData *Filesystem::read(const char *filename, int64 size) const
669 {
670 	File file(filename);
671 
672 	file.open(File::MODE_READ);
673 
674 	// close() is called in the File destructor.
675 	return file.read(size);
676 }
677 
write(const char * filename,const void * data,int64 size) const678 void Filesystem::write(const char *filename, const void *data, int64 size) const
679 {
680 	File file(filename);
681 
682 	file.open(File::MODE_WRITE);
683 
684 	// close() is called in the File destructor.
685 	if (!file.write(data, size))
686 		throw love::Exception("Data could not be written.");
687 }
688 
append(const char * filename,const void * data,int64 size) const689 void Filesystem::append(const char *filename, const void *data, int64 size) const
690 {
691 	File file(filename);
692 
693 	file.open(File::MODE_APPEND);
694 
695 	// close() is called in the File destructor.
696 	if (!file.write(data, size))
697 		throw love::Exception("Data could not be written.");
698 }
699 
getDirectoryItems(const char * dir,std::vector<std::string> & items)700 void Filesystem::getDirectoryItems(const char *dir, std::vector<std::string> &items)
701 {
702 	if (!PHYSFS_isInit())
703 		return;
704 
705 	char **rc = PHYSFS_enumerateFiles(dir);
706 
707 	if (rc == nullptr)
708 		return;
709 
710 	for (char **i = rc; *i != 0; i++)
711 		items.push_back(*i);
712 
713 	PHYSFS_freeList(rc);
714 }
715 
getLastModified(const char * filename) const716 int64 Filesystem::getLastModified(const char *filename) const
717 {
718 	PHYSFS_sint64 time = -1;
719 
720 	if (!PHYSFS_isInit())
721 		return -1;
722 
723 #ifdef LOVE_USE_PHYSFS_2_1
724 	PHYSFS_Stat stat = {};
725 	if (PHYSFS_stat(filename, &stat))
726 		time = stat.modtime;
727 #else
728 	time = PHYSFS_getLastModTime(filename);
729 #endif
730 
731 	if (time == -1)
732 		throw love::Exception("Could not determine file modification date.");
733 
734 	return time;
735 }
736 
getSize(const char * filename) const737 int64 Filesystem::getSize(const char *filename) const
738 {
739 	File file(filename);
740 	int64 size = file.getSize();
741 	return size;
742 }
743 
setSymlinksEnabled(bool enable)744 void Filesystem::setSymlinksEnabled(bool enable)
745 {
746 	if (!PHYSFS_isInit())
747 		return;
748 
749 	if (!enable)
750 	{
751 		PHYSFS_Version version = {};
752 		PHYSFS_getLinkedVersion(&version);
753 
754 		// FIXME: This is a workaround for a bug in PHYSFS_enumerateFiles in
755 		// PhysFS 2.1-alpha.
756 		if (version.major == 2 && version.minor == 1)
757 			return;
758 	}
759 
760 	PHYSFS_permitSymbolicLinks(enable ? 1 : 0);
761 }
762 
areSymlinksEnabled() const763 bool Filesystem::areSymlinksEnabled() const
764 {
765 	if (!PHYSFS_isInit())
766 		return false;
767 
768 	return PHYSFS_symbolicLinksPermitted() != 0;
769 }
770 
getRequirePath()771 std::vector<std::string> &Filesystem::getRequirePath()
772 {
773 	return requirePath;
774 }
775 
allowMountingForPath(const std::string & path)776 void Filesystem::allowMountingForPath(const std::string &path)
777 {
778 	if (std::find(allowedMountPaths.begin(), allowedMountPaths.end(), path) == allowedMountPaths.end())
779 		allowedMountPaths.push_back(path);
780 }
781 
782 } // physfs
783 } // filesystem
784 } // love
785