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