1 | /** |
2 | * Copyright (c) 2006-2023 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 | #include "PhysfsIo.h" |
31 | |
32 | // PhysFS |
33 | #include "libraries/physfs/physfs.h" |
34 | |
35 | // For great CWD. (Current Working Directory) |
36 | // Using this instead of boost::filesystem which totally |
37 | // cramped our style. |
38 | #ifdef LOVE_WINDOWS |
39 | # include <windows.h> |
40 | # include <direct.h> |
41 | #else |
42 | # include <sys/param.h> |
43 | # include <unistd.h> |
44 | #endif |
45 | |
46 | #ifdef LOVE_IOS |
47 | # include "common/ios.h" |
48 | #endif |
49 | |
50 | #include <string> |
51 | |
52 | #ifdef LOVE_ANDROID |
53 | #include <SDL.h> |
54 | #include "common/android.h" |
55 | #endif |
56 | |
57 | namespace |
58 | { |
59 | size_t getDriveDelim(const std::string &input) |
60 | { |
61 | for (size_t i = 0; i < input.size(); ++i) |
62 | if (input[i] == '/' || input[i] == '\\') |
63 | return i; |
64 | // Something's horribly wrong |
65 | return 0; |
66 | } |
67 | |
68 | std::string getDriveRoot(const std::string &input) |
69 | { |
70 | return input.substr(0, getDriveDelim(input)+1); |
71 | } |
72 | |
73 | std::string skipDriveRoot(const std::string &input) |
74 | { |
75 | return input.substr(getDriveDelim(input)+1); |
76 | } |
77 | |
78 | std::string normalize(const std::string &input) |
79 | { |
80 | std::stringstream out; |
81 | bool seenSep = false, isSep = false; |
82 | for (size_t i = 0; i < input.size(); ++i) |
83 | { |
84 | isSep = (input[i] == LOVE_PATH_SEPARATOR[0]); |
85 | if (!isSep || !seenSep) |
86 | out << input[i]; |
87 | seenSep = isSep; |
88 | } |
89 | |
90 | return out.str(); |
91 | } |
92 | |
93 | } |
94 | |
95 | namespace love |
96 | { |
97 | namespace filesystem |
98 | { |
99 | namespace physfs |
100 | { |
101 | |
102 | Filesystem::Filesystem() |
103 | : fused(false) |
104 | , fusedSet(false) |
105 | { |
106 | requirePath = {"?.lua" , "?/init.lua" }; |
107 | cRequirePath = {"??" }; |
108 | } |
109 | |
110 | Filesystem::~Filesystem() |
111 | { |
112 | #ifdef LOVE_ANDROID |
113 | love::android::deinitializeVirtualArchive(); |
114 | #endif |
115 | |
116 | if (PHYSFS_isInit()) |
117 | PHYSFS_deinit(); |
118 | } |
119 | |
120 | const char *Filesystem::getName() const |
121 | { |
122 | return "love.filesystem.physfs" ; |
123 | } |
124 | |
125 | void Filesystem::init(const char *arg0) |
126 | { |
127 | #ifdef LOVE_ANDROID |
128 | // TODO: This should be a pointer to an initializeed PHYSFS_AndroidInit |
129 | // struct on android. But it's only used for PHYSFS_getBaseDir and |
130 | // PHYSFS_getPrefDir, which we don't use right now... |
131 | arg0 = nullptr; |
132 | #endif |
133 | |
134 | if (!PHYSFS_init(arg0)) |
135 | throw love::Exception("Failed to initialize filesystem: %s" , PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); |
136 | |
137 | // Enable symlinks by default. |
138 | setSymlinksEnabled(true); |
139 | } |
140 | |
141 | void Filesystem::setFused(bool fused) |
142 | { |
143 | if (fusedSet) |
144 | return; |
145 | this->fused = fused; |
146 | fusedSet = true; |
147 | } |
148 | |
149 | bool Filesystem::isFused() const |
150 | { |
151 | if (!fusedSet) |
152 | return false; |
153 | return fused; |
154 | } |
155 | |
156 | bool Filesystem::setIdentity(const char *ident, bool appendToPath) |
157 | { |
158 | if (!PHYSFS_isInit()) |
159 | return false; |
160 | |
161 | std::string old_save_path = save_path_full; |
162 | |
163 | // Store the save directory. |
164 | save_identity = std::string(ident); |
165 | |
166 | // Generate the relative path to the game save folder. |
167 | save_path_relative = std::string(LOVE_APPDATA_PREFIX LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + save_identity; |
168 | |
169 | // Generate the full path to the game save folder. |
170 | save_path_full = std::string(getAppdataDirectory()) + std::string(LOVE_PATH_SEPARATOR); |
171 | if (fused) |
172 | save_path_full += std::string(LOVE_APPDATA_PREFIX) + save_identity; |
173 | else |
174 | save_path_full += save_path_relative; |
175 | |
176 | save_path_full = normalize(save_path_full); |
177 | |
178 | #ifdef LOVE_ANDROID |
179 | if (save_identity == "" ) |
180 | save_identity = "unnamed" ; |
181 | |
182 | std::string storage_path; |
183 | if (isAndroidSaveExternal()) |
184 | storage_path = SDL_AndroidGetExternalStoragePath(); |
185 | else |
186 | storage_path = SDL_AndroidGetInternalStoragePath(); |
187 | |
188 | std::string save_directory = storage_path + "/save" ; |
189 | |
190 | save_path_full = storage_path + std::string("/save/" ) + save_identity; |
191 | |
192 | if (!love::android::directoryExists(save_path_full.c_str()) && |
193 | !love::android::mkdir(save_path_full.c_str())) |
194 | SDL_Log("Error: Could not create save directory %s!" , save_path_full.c_str()); |
195 | #endif |
196 | |
197 | // We now have something like: |
198 | // save_identity: game |
199 | // save_path_relative: ./LOVE/game |
200 | // save_path_full: C:\Documents and Settings\user\Application Data/LOVE/game |
201 | |
202 | // We don't want old read-only save paths to accumulate when we set a new |
203 | // identity. |
204 | if (!old_save_path.empty()) |
205 | PHYSFS_unmount(old_save_path.c_str()); |
206 | |
207 | // Try to add the save directory to the search path. |
208 | // (No error on fail, it means that the path doesn't exist). |
209 | PHYSFS_mount(save_path_full.c_str(), nullptr, appendToPath); |
210 | |
211 | // HACK: This forces setupWriteDirectory to be called the next time a file |
212 | // is opened for writing - otherwise it won't be called at all if it was |
213 | // already called at least once before. |
214 | PHYSFS_setWriteDir(nullptr); |
215 | |
216 | return true; |
217 | } |
218 | |
219 | const char *Filesystem::getIdentity() const |
220 | { |
221 | return save_identity.c_str(); |
222 | } |
223 | |
224 | bool Filesystem::setSource(const char *source) |
225 | { |
226 | if (!PHYSFS_isInit()) |
227 | return false; |
228 | |
229 | // Check whether directory is already set. |
230 | if (!game_source.empty()) |
231 | return false; |
232 | |
233 | std::string new_search_path = source; |
234 | |
235 | #ifdef LOVE_ANDROID |
236 | if (!love::android::createStorageDirectories()) |
237 | SDL_Log("Error creating storage directories!" ); |
238 | |
239 | new_search_path = "" ; |
240 | |
241 | PHYSFS_Io *gameLoveIO; |
242 | bool hasFusedGame = love::android::checkFusedGame((void **) &gameLoveIO); |
243 | bool isAAssetMounted = false; |
244 | |
245 | if (hasFusedGame) |
246 | { |
247 | if (gameLoveIO) |
248 | // Actually we should just be able to mount gameLoveIO, but that's experimental. |
249 | gameLoveIO->destroy(gameLoveIO); |
250 | else |
251 | { |
252 | if (!love::android::initializeVirtualArchive()) |
253 | { |
254 | SDL_Log("Unable to mount AAsset: %s" , PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); |
255 | return false; |
256 | } |
257 | |
258 | // See love::android::initializeVirtualArchive() |
259 | new_search_path = "ASET.AASSET" ; |
260 | isAAssetMounted = true; |
261 | } |
262 | } |
263 | |
264 | if (!isAAssetMounted) |
265 | { |
266 | new_search_path = love::android::getSelectedGameFile(); |
267 | |
268 | // try mounting first, if that fails, load to memory and mount |
269 | if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1)) |
270 | { |
271 | // PHYSFS cannot yet mount a zip file inside an .apk |
272 | SDL_Log("Mounting %s did not work. Loading to memory." , |
273 | new_search_path.c_str()); |
274 | char* game_archive_ptr = NULL; |
275 | size_t game_archive_size = 0; |
276 | if (!love::android::loadGameArchiveToMemory( |
277 | new_search_path.c_str(), &game_archive_ptr, |
278 | &game_archive_size)) |
279 | { |
280 | SDL_Log("Failure memory loading archive %s" , new_search_path.c_str()); |
281 | return false; |
282 | } |
283 | if (!PHYSFS_mountMemory( |
284 | game_archive_ptr, game_archive_size, |
285 | love::android::freeGameArchiveMemory, "archive.zip" , "/" , 0)) |
286 | { |
287 | SDL_Log("Failure mounting in-memory archive." ); |
288 | love::android::freeGameArchiveMemory(game_archive_ptr); |
289 | return false; |
290 | } |
291 | } |
292 | } |
293 | #else |
294 | // Add the directory. |
295 | if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1)) |
296 | { |
297 | // It's possible there is additional data at the end of the fused executable, |
298 | // e.g. for signed windows executables (the signature). |
299 | // In this case let's try a little bit harder to find the zip file. |
300 | // This is not used by default because I assume that the physfs IOs are probably |
301 | // more robust and more performant, so they should be favored, if possible. |
302 | auto io = StripSuffixIo::create(new_search_path); |
303 | if (!io->determineStrippedLength()) |
304 | { |
305 | delete io; |
306 | return false; |
307 | } |
308 | if (!PHYSFS_mountIo(io, io->filename.c_str(), nullptr, 1)) |
309 | { |
310 | // If PHYSFS_mountIo fails, io->destroy(io) is not called and we have |
311 | // to delete ourselves. |
312 | delete io; |
313 | return false; |
314 | } |
315 | return true; |
316 | } |
317 | #endif |
318 | |
319 | // Save the game source. |
320 | game_source = new_search_path; |
321 | |
322 | return true; |
323 | } |
324 | |
325 | const char *Filesystem::getSource() const |
326 | { |
327 | return game_source.c_str(); |
328 | } |
329 | |
330 | bool Filesystem::setupWriteDirectory() |
331 | { |
332 | if (!PHYSFS_isInit()) |
333 | return false; |
334 | |
335 | // These must all be set. |
336 | if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty()) |
337 | return false; |
338 | |
339 | // We need to make sure the write directory is created. To do that, we also |
340 | // need to make sure all its parent directories are also created. |
341 | std::string temp_writedir = getDriveRoot(save_path_full); |
342 | std::string temp_createdir = skipDriveRoot(save_path_full); |
343 | |
344 | // On some sandboxed platforms, physfs will break when its write directory |
345 | // is the root of the drive and it tries to create a folder (even if the |
346 | // folder's path is in a writable location.) If the user's home folder is |
347 | // in the save path, we'll try starting from there instead. |
348 | if (save_path_full.find(getUserDirectory()) == 0) |
349 | { |
350 | temp_writedir = getUserDirectory(); |
351 | temp_createdir = save_path_full.substr(getUserDirectory().length()); |
352 | |
353 | // Strip leading '/' characters from the path we want to create. |
354 | size_t startpos = temp_createdir.find_first_not_of('/'); |
355 | if (startpos != std::string::npos) |
356 | temp_createdir = temp_createdir.substr(startpos); |
357 | } |
358 | |
359 | // Set either '/' or the user's home as a writable directory. |
360 | // (We must create the save folder before mounting it). |
361 | if (!PHYSFS_setWriteDir(temp_writedir.c_str())) |
362 | return false; |
363 | |
364 | // Create the save folder. (We're now "at" either '/' or the user's home). |
365 | if (!createDirectory(temp_createdir.c_str())) |
366 | { |
367 | // Clear the write directory in case of error. |
368 | PHYSFS_setWriteDir(nullptr); |
369 | return false; |
370 | } |
371 | |
372 | // Set the final write directory. |
373 | if (!PHYSFS_setWriteDir(save_path_full.c_str())) |
374 | return false; |
375 | |
376 | // Add the directory. (Will not be readded if already present). |
377 | if (!PHYSFS_mount(save_path_full.c_str(), nullptr, 0)) |
378 | { |
379 | PHYSFS_setWriteDir(nullptr); // Clear the write directory in case of error. |
380 | return false; |
381 | } |
382 | |
383 | return true; |
384 | } |
385 | |
386 | bool Filesystem::mount(const char *archive, const char *mountpoint, bool appendToPath) |
387 | { |
388 | if (!PHYSFS_isInit() || !archive) |
389 | return false; |
390 | |
391 | std::string realPath; |
392 | std::string sourceBase = getSourceBaseDirectory(); |
393 | |
394 | // Check whether the given archive path is in the list of allowed full paths. |
395 | auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive); |
396 | |
397 | if (it != allowedMountPaths.end()) |
398 | realPath = *it; |
399 | else if (isFused() && sourceBase.compare(archive) == 0) |
400 | { |
401 | // Special case: if the game is fused and the archive is the source's |
402 | // base directory, mount it even though it's outside of the save dir. |
403 | realPath = sourceBase; |
404 | } |
405 | else |
406 | { |
407 | // Not allowed for safety reasons. |
408 | if (strlen(archive) == 0 || strstr(archive, ".." ) || strcmp(archive, "/" ) == 0) |
409 | return false; |
410 | |
411 | const char *realDir = PHYSFS_getRealDir(archive); |
412 | if (!realDir) |
413 | return false; |
414 | |
415 | realPath = realDir; |
416 | |
417 | // Always disallow mounting of files inside the game source, since it |
418 | // won't work anyway if the game source is a zipped .love file. |
419 | if (realPath.find(game_source) == 0) |
420 | return false; |
421 | |
422 | realPath += LOVE_PATH_SEPARATOR; |
423 | realPath += archive; |
424 | } |
425 | |
426 | if (realPath.length() == 0) |
427 | return false; |
428 | |
429 | return PHYSFS_mount(realPath.c_str(), mountpoint, appendToPath) != 0; |
430 | } |
431 | |
432 | bool Filesystem::mount(Data *data, const char *archivename, const char *mountpoint, bool appendToPath) |
433 | { |
434 | if (!PHYSFS_isInit()) |
435 | return false; |
436 | |
437 | if (PHYSFS_mountMemory(data->getData(), data->getSize(), nullptr, archivename, mountpoint, appendToPath) != 0) |
438 | { |
439 | mountedData[archivename] = data; |
440 | return true; |
441 | } |
442 | |
443 | return false; |
444 | } |
445 | |
446 | bool Filesystem::unmount(const char *archive) |
447 | { |
448 | if (!PHYSFS_isInit() || !archive) |
449 | return false; |
450 | |
451 | auto datait = mountedData.find(archive); |
452 | |
453 | if (datait != mountedData.end() && PHYSFS_unmount(archive) != 0) |
454 | { |
455 | mountedData.erase(datait); |
456 | return true; |
457 | } |
458 | |
459 | std::string realPath; |
460 | std::string sourceBase = getSourceBaseDirectory(); |
461 | |
462 | // Check whether the given archive path is in the list of allowed full paths. |
463 | auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive); |
464 | |
465 | if (it != allowedMountPaths.end()) |
466 | realPath = *it; |
467 | else if (isFused() && sourceBase.compare(archive) == 0) |
468 | { |
469 | // Special case: if the game is fused and the archive is the source's |
470 | // base directory, unmount it even though it's outside of the save dir. |
471 | realPath = sourceBase; |
472 | } |
473 | else |
474 | { |
475 | // Not allowed for safety reasons. |
476 | if (strlen(archive) == 0 || strstr(archive, ".." ) || strcmp(archive, "/" ) == 0) |
477 | return false; |
478 | |
479 | const char *realDir = PHYSFS_getRealDir(archive); |
480 | if (!realDir) |
481 | return false; |
482 | |
483 | realPath = realDir; |
484 | realPath += LOVE_PATH_SEPARATOR; |
485 | realPath += archive; |
486 | } |
487 | |
488 | const char *mountPoint = PHYSFS_getMountPoint(realPath.c_str()); |
489 | if (!mountPoint) |
490 | return false; |
491 | |
492 | return PHYSFS_unmount(realPath.c_str()) != 0; |
493 | } |
494 | |
495 | bool Filesystem::unmount(Data *data) |
496 | { |
497 | for (const auto &datapair : mountedData) |
498 | { |
499 | if (datapair.second.get() == data) |
500 | { |
501 | std::string archive = datapair.first; |
502 | return unmount(archive.c_str()); |
503 | } |
504 | } |
505 | |
506 | return false; |
507 | } |
508 | |
509 | love::filesystem::File *Filesystem::newFile(const char *filename) const |
510 | { |
511 | return new File(filename); |
512 | } |
513 | |
514 | const char *Filesystem::getWorkingDirectory() |
515 | { |
516 | if (cwd.empty()) |
517 | { |
518 | #ifdef LOVE_WINDOWS |
519 | |
520 | WCHAR w_cwd[LOVE_MAX_PATH]; |
521 | _wgetcwd(w_cwd, LOVE_MAX_PATH); |
522 | cwd = to_utf8(w_cwd); |
523 | replace_char(cwd, '\\', '/'); |
524 | #else |
525 | char *cwd_char = new char[LOVE_MAX_PATH]; |
526 | |
527 | if (getcwd(cwd_char, LOVE_MAX_PATH)) |
528 | cwd = cwd_char; // if getcwd fails, cwd_char (and thus cwd) will still be empty |
529 | |
530 | delete [] cwd_char; |
531 | #endif |
532 | } |
533 | |
534 | return cwd.c_str(); |
535 | } |
536 | |
537 | std::string Filesystem::getUserDirectory() |
538 | { |
539 | #ifdef LOVE_IOS |
540 | // PHYSFS_getUserDir doesn't give exactly the path we want on iOS. |
541 | static std::string userDir = normalize(love::ios::getHomeDirectory()); |
542 | #else |
543 | static std::string userDir = normalize(PHYSFS_getUserDir()); |
544 | #endif |
545 | |
546 | return userDir; |
547 | } |
548 | |
549 | std::string Filesystem::getAppdataDirectory() |
550 | { |
551 | if (appdata.empty()) |
552 | { |
553 | #ifdef LOVE_WINDOWS_UWP |
554 | appdata = getUserDirectory(); |
555 | #elif defined(LOVE_WINDOWS) |
556 | wchar_t *w_appdata = _wgetenv(L"APPDATA" ); |
557 | appdata = to_utf8(w_appdata); |
558 | replace_char(appdata, '\\', '/'); |
559 | #elif defined(LOVE_MACOSX) |
560 | std::string udir = getUserDirectory(); |
561 | udir.append("/Library/Application Support" ); |
562 | appdata = normalize(udir); |
563 | #elif defined(LOVE_IOS) |
564 | appdata = normalize(love::ios::getAppdataDirectory()); |
565 | #elif defined(LOVE_LINUX) |
566 | char *xdgdatahome = getenv("XDG_DATA_HOME" ); |
567 | if (!xdgdatahome) |
568 | appdata = normalize(std::string(getUserDirectory()) + "/.local/share/" ); |
569 | else |
570 | appdata = xdgdatahome; |
571 | #else |
572 | appdata = getUserDirectory(); |
573 | #endif |
574 | } |
575 | return appdata; |
576 | } |
577 | |
578 | |
579 | const char *Filesystem::getSaveDirectory() |
580 | { |
581 | return save_path_full.c_str(); |
582 | } |
583 | |
584 | std::string Filesystem::getSourceBaseDirectory() const |
585 | { |
586 | size_t source_len = game_source.length(); |
587 | |
588 | if (source_len == 0) |
589 | return "" ; |
590 | |
591 | // FIXME: This doesn't take into account parent and current directory |
592 | // symbols (i.e. '..' and '.') |
593 | #ifdef LOVE_WINDOWS |
594 | // In windows, delimiters can be either '/' or '\'. |
595 | size_t base_end_pos = game_source.find_last_of("/\\" , source_len - 2); |
596 | #else |
597 | size_t base_end_pos = game_source.find_last_of('/', source_len - 2); |
598 | #endif |
599 | |
600 | if (base_end_pos == std::string::npos) |
601 | return "" ; |
602 | |
603 | // If the source is in the unix root (aka '/'), we want to keep the '/'. |
604 | if (base_end_pos == 0) |
605 | base_end_pos = 1; |
606 | |
607 | return game_source.substr(0, base_end_pos); |
608 | } |
609 | |
610 | std::string Filesystem::getRealDirectory(const char *filename) const |
611 | { |
612 | if (!PHYSFS_isInit()) |
613 | throw love::Exception("PhysFS is not initialized." ); |
614 | |
615 | const char *dir = PHYSFS_getRealDir(filename); |
616 | |
617 | if (dir == nullptr) |
618 | throw love::Exception("File does not exist on disk." ); |
619 | |
620 | return std::string(dir); |
621 | } |
622 | |
623 | bool Filesystem::getInfo(const char *filepath, Info &info) const |
624 | { |
625 | if (!PHYSFS_isInit()) |
626 | return false; |
627 | |
628 | PHYSFS_Stat stat = {}; |
629 | if (!PHYSFS_stat(filepath, &stat)) |
630 | return false; |
631 | |
632 | info.size = (int64) stat.filesize; |
633 | info.modtime = (int64) stat.modtime; |
634 | |
635 | if (stat.filetype == PHYSFS_FILETYPE_REGULAR) |
636 | info.type = FILETYPE_FILE; |
637 | else if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY) |
638 | info.type = FILETYPE_DIRECTORY; |
639 | else if (stat.filetype == PHYSFS_FILETYPE_SYMLINK) |
640 | info.type = FILETYPE_SYMLINK; |
641 | else |
642 | info.type = FILETYPE_OTHER; |
643 | |
644 | return true; |
645 | } |
646 | |
647 | bool Filesystem::createDirectory(const char *dir) |
648 | { |
649 | if (!PHYSFS_isInit()) |
650 | return false; |
651 | |
652 | if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory()) |
653 | return false; |
654 | |
655 | if (!PHYSFS_mkdir(dir)) |
656 | return false; |
657 | |
658 | return true; |
659 | } |
660 | |
661 | bool Filesystem::remove(const char *file) |
662 | { |
663 | if (!PHYSFS_isInit()) |
664 | return false; |
665 | |
666 | if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory()) |
667 | return false; |
668 | |
669 | if (!PHYSFS_delete(file)) |
670 | return false; |
671 | |
672 | return true; |
673 | } |
674 | |
675 | FileData *Filesystem::read(const char *filename, int64 size) const |
676 | { |
677 | File file(filename); |
678 | |
679 | file.open(File::MODE_READ); |
680 | |
681 | // close() is called in the File destructor. |
682 | return file.read(size); |
683 | } |
684 | |
685 | void Filesystem::write(const char *filename, const void *data, int64 size) const |
686 | { |
687 | File file(filename); |
688 | |
689 | file.open(File::MODE_WRITE); |
690 | |
691 | // close() is called in the File destructor. |
692 | if (!file.write(data, size)) |
693 | throw love::Exception("Data could not be written." ); |
694 | } |
695 | |
696 | void Filesystem::append(const char *filename, const void *data, int64 size) const |
697 | { |
698 | File file(filename); |
699 | |
700 | file.open(File::MODE_APPEND); |
701 | |
702 | // close() is called in the File destructor. |
703 | if (!file.write(data, size)) |
704 | throw love::Exception("Data could not be written." ); |
705 | } |
706 | |
707 | void Filesystem::getDirectoryItems(const char *dir, std::vector<std::string> &items) |
708 | { |
709 | if (!PHYSFS_isInit()) |
710 | return; |
711 | |
712 | char **rc = PHYSFS_enumerateFiles(dir); |
713 | |
714 | if (rc == nullptr) |
715 | return; |
716 | |
717 | for (char **i = rc; *i != 0; i++) |
718 | items.push_back(*i); |
719 | |
720 | PHYSFS_freeList(rc); |
721 | } |
722 | |
723 | void Filesystem::setSymlinksEnabled(bool enable) |
724 | { |
725 | if (!PHYSFS_isInit()) |
726 | return; |
727 | |
728 | PHYSFS_permitSymbolicLinks(enable ? 1 : 0); |
729 | } |
730 | |
731 | bool Filesystem::areSymlinksEnabled() const |
732 | { |
733 | if (!PHYSFS_isInit()) |
734 | return false; |
735 | |
736 | return PHYSFS_symbolicLinksPermitted() != 0; |
737 | } |
738 | |
739 | std::vector<std::string> &Filesystem::getRequirePath() |
740 | { |
741 | return requirePath; |
742 | } |
743 | |
744 | std::vector<std::string> &Filesystem::getCRequirePath() |
745 | { |
746 | return cRequirePath; |
747 | } |
748 | |
749 | void Filesystem::allowMountingForPath(const std::string &path) |
750 | { |
751 | if (std::find(allowedMountPaths.begin(), allowedMountPaths.end(), path) == allowedMountPaths.end()) |
752 | allowedMountPaths.push_back(path); |
753 | } |
754 | |
755 | } // physfs |
756 | } // filesystem |
757 | } // love |
758 | |