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// LOVE
22#include "common/config.h"
23#include "wrap_Filesystem.h"
24#include "wrap_File.h"
25#include "wrap_DroppedFile.h"
26#include "wrap_FileData.h"
27#include "data/wrap_Data.h"
28#include "data/wrap_DataModule.h"
29
30#include "physfs/Filesystem.h"
31
32#ifdef LOVE_ANDROID
33#include "common/android.h"
34#endif
35
36// SDL
37#include <SDL_loadso.h>
38
39// STL
40#include <vector>
41#include <string>
42#include <sstream>
43#include <algorithm>
44
45namespace love
46{
47namespace filesystem
48{
49
50#define instance() (Module::getInstance<Filesystem>(Module::M_FILESYSTEM))
51
52bool hack_setupWriteDirectory()
53{
54 if (instance() != 0)
55 return instance()->setupWriteDirectory();
56 return false;
57}
58
59int w_init(lua_State *L)
60{
61 const char *arg0 = luaL_checkstring(L, 1);
62 luax_catchexcept(L, [&](){ instance()->init(arg0); });
63 return 0;
64}
65
66int w_setFused(lua_State *L)
67{
68 // no error checking needed, everything, even nothing
69 // can be converted to a boolean
70 instance()->setFused(luax_toboolean(L, 1));
71 return 0;
72}
73
74int w_isFused(lua_State *L)
75{
76 luax_pushboolean(L, instance()->isFused());
77 return 1;
78}
79
80int w_setAndroidSaveExternal(lua_State *L)
81{
82 bool useExternal = luax_optboolean(L, 1, false);
83 instance()->setAndroidSaveExternal(useExternal);
84 return 0;
85}
86
87int w_setIdentity(lua_State *L)
88{
89 const char *arg = luaL_checkstring(L, 1);
90 bool append = luax_optboolean(L, 2, false);
91
92 if (!instance()->setIdentity(arg, append))
93 return luaL_error(L, "Could not set write directory.");
94
95 return 0;
96}
97
98int w_getIdentity(lua_State *L)
99{
100 lua_pushstring(L, instance()->getIdentity());
101 return 1;
102}
103
104int w_setSource(lua_State *L)
105{
106 const char *arg = luaL_checkstring(L, 1);
107
108 if (!instance()->setSource(arg))
109 return luaL_error(L, "Could not set source.");
110
111 return 0;
112}
113
114int w_getSource(lua_State *L)
115{
116 lua_pushstring(L, instance()->getSource());
117 return 1;
118}
119
120int w_mount(lua_State *L)
121{
122 std::string archive;
123
124 if (luax_istype(L, 1, Data::type))
125 {
126 Data *data = love::data::luax_checkdata(L, 1);
127 int startidx = 2;
128
129 if (luax_istype(L, 1, FileData::type) && !lua_isstring(L, 3))
130 {
131 FileData *filedata = luax_checkfiledata(L, 1);
132 archive = filedata->getFilename();
133 startidx = 2;
134 }
135 else
136 {
137 archive = luax_checkstring(L, 2);
138 startidx = 3;
139 }
140
141 const char *mountpoint = luaL_checkstring(L, startidx + 0);
142 bool append = luax_optboolean(L, startidx + 1, false);
143
144 luax_pushboolean(L, instance()->mount(data, archive.c_str(), mountpoint, append));
145 return 1;
146 }
147 else if (luax_istype(L, 1, DroppedFile::type))
148 {
149 DroppedFile *file = luax_totype<DroppedFile>(L, 1);
150 archive = file->getFilename();
151 }
152 else
153 archive = luax_checkstring(L, 1);
154
155 const char *mountpoint = luaL_checkstring(L, 2);
156 bool append = luax_optboolean(L, 3, false);
157
158 luax_pushboolean(L, instance()->mount(archive.c_str(), mountpoint, append));
159 return 1;
160}
161
162int w_unmount(lua_State *L)
163{
164 if (luax_istype(L, 1, Data::type))
165 {
166 Data *data = love::data::luax_checkdata(L, 1);
167 luax_pushboolean(L, instance()->unmount(data));
168 }
169 else
170 {
171 const char *archive = luaL_checkstring(L, 1);
172 luax_pushboolean(L, instance()->unmount(archive));
173 }
174 return 1;
175}
176
177int w_newFile(lua_State *L)
178{
179 const char *filename = luaL_checkstring(L, 1);
180
181 const char *str = 0;
182 File::Mode mode = File::MODE_CLOSED;
183
184 if (lua_isstring(L, 2))
185 {
186 str = luaL_checkstring(L, 2);
187 if (!File::getConstant(str, mode))
188 return luax_enumerror(L, "file open mode", File::getConstants(mode), str);
189 }
190
191 File *t = instance()->newFile(filename);
192
193 if (mode != File::MODE_CLOSED)
194 {
195 try
196 {
197 if (!t->open(mode))
198 throw love::Exception("Could not open file.");
199 }
200 catch (love::Exception &e)
201 {
202 t->release();
203 return luax_ioError(L, "%s", e.what());
204 }
205 }
206
207 luax_pushtype(L, t);
208 t->release();
209 return 1;
210}
211
212File *luax_getfile(lua_State *L, int idx)
213{
214 File *file = nullptr;
215 if (lua_isstring(L, idx))
216 {
217 const char *filename = luaL_checkstring(L, idx);
218 file = instance()->newFile(filename);
219 }
220 else
221 {
222 file = luax_checkfile(L, idx);
223 file->retain();
224 }
225
226 return file;
227}
228
229FileData *luax_getfiledata(lua_State *L, int idx)
230{
231 FileData *data = nullptr;
232 File *file = nullptr;
233
234 if (lua_isstring(L, idx) || luax_istype(L, idx, File::type))
235 {
236 file = luax_getfile(L, idx);
237 }
238 else if (luax_istype(L, idx, FileData::type))
239 {
240 data = luax_checkfiledata(L, idx);
241 data->retain();
242 }
243
244 if (!data && !file)
245 {
246 luaL_argerror(L, idx, "filename, File, or FileData expected");
247 return nullptr; // Never reached.
248 }
249
250 if (file)
251 {
252 luax_catchexcept(L,
253 [&]() { data = file->read(); },
254 [&](bool) { file->release(); }
255 );
256 }
257
258 return data;
259}
260
261Data *luax_getdata(lua_State *L, int idx)
262{
263 Data *data = nullptr;
264 File *file = nullptr;
265
266 if (lua_isstring(L, idx) || luax_istype(L, idx, File::type))
267 {
268 file = luax_getfile(L, idx);
269 }
270 else if (luax_istype(L, idx, Data::type))
271 {
272 data = data::luax_checkdata(L, idx);
273 data->retain();
274 }
275
276 if (!data && !file)
277 {
278 luaL_argerror(L, idx, "filename, File, or Data expected");
279 return nullptr; // Never reached.
280 }
281
282 if (file)
283 {
284 luax_catchexcept(L,
285 [&]() { data = file->read(); },
286 [&](bool) { file->release(); }
287 );
288 }
289
290 return data;
291}
292
293bool luax_cangetfiledata(lua_State *L, int idx)
294{
295 return lua_isstring(L, idx) || luax_istype(L, idx, File::type) || luax_istype(L, idx, FileData::type);
296}
297
298bool luax_cangetdata(lua_State *L, int idx)
299{
300 return lua_isstring(L, idx) || luax_istype(L, idx, File::type) || luax_istype(L, idx, Data::type);
301}
302
303int w_newFileData(lua_State *L)
304{
305 // Single argument: treat as filepath or File.
306 if (lua_gettop(L) == 1)
307 {
308 // We don't use luax_getfiledata because we want to use an ioError.
309 if (lua_isstring(L, 1))
310 luax_convobj(L, 1, "filesystem", "newFile");
311
312 // Get FileData from the File.
313 if (luax_istype(L, 1, File::type))
314 {
315 File *file = luax_checkfile(L, 1);
316
317 StrongRef<FileData> data;
318 try
319 {
320 data.set(file->read(), Acquire::NORETAIN);
321 }
322 catch (love::Exception &e)
323 {
324 return luax_ioError(L, "%s", e.what());
325 }
326 luax_pushtype(L, data);
327 return 1;
328 }
329 else
330 return luaL_argerror(L, 1, "filename or File expected");
331 }
332
333 size_t length = 0;
334 const void *ptr = nullptr;
335 if (luax_istype(L, 1, Data::type))
336 {
337 Data *data = data::luax_checkdata(L, 1);
338 ptr = data->getData();
339 length = data->getSize();
340 }
341 else if (lua_isstring(L, 1))
342 ptr = luaL_checklstring(L, 1, &length);
343 else
344 return luaL_argerror(L, 1, "string or Data expected");
345
346 const char *filename = luaL_checkstring(L, 2);
347
348 FileData *t = nullptr;
349 luax_catchexcept(L, [&](){ t = instance()->newFileData(ptr, length, filename); });
350
351 luax_pushtype(L, t);
352 t->release();
353 return 1;
354}
355
356int w_getWorkingDirectory(lua_State *L)
357{
358 lua_pushstring(L, instance()->getWorkingDirectory());
359 return 1;
360}
361
362int w_getUserDirectory(lua_State *L)
363{
364 luax_pushstring(L, instance()->getUserDirectory());
365 return 1;
366}
367
368int w_getAppdataDirectory(lua_State *L)
369{
370 luax_pushstring(L, instance()->getAppdataDirectory());
371 return 1;
372}
373
374int w_getSaveDirectory(lua_State *L)
375{
376 lua_pushstring(L, instance()->getSaveDirectory());
377 return 1;
378}
379
380int w_getSourceBaseDirectory(lua_State *L)
381{
382 luax_pushstring(L, instance()->getSourceBaseDirectory());
383 return 1;
384}
385
386int w_getRealDirectory(lua_State *L)
387{
388 const char *filename = luaL_checkstring(L, 1);
389 std::string dir;
390
391 try
392 {
393 dir = instance()->getRealDirectory(filename);
394 }
395 catch (love::Exception &e)
396 {
397 return luax_ioError(L, "%s", e.what());
398 }
399
400 lua_pushstring(L, dir.c_str());
401 return 1;
402}
403
404int w_getExecutablePath(lua_State *L)
405{
406 luax_pushstring(L, instance()->getExecutablePath());
407 return 1;
408}
409
410int w_getInfo(lua_State *L)
411{
412 const char *filepath = luaL_checkstring(L, 1);
413 Filesystem::Info info = {};
414
415 int startidx = 2;
416 Filesystem::FileType filtertype = Filesystem::FILETYPE_MAX_ENUM;
417 if (lua_isstring(L, startidx))
418 {
419 const char *typestr = luaL_checkstring(L, startidx);
420 if (!Filesystem::getConstant(typestr, filtertype))
421 return luax_enumerror(L, "file type", Filesystem::getConstants(filtertype), typestr);
422
423 startidx++;
424 }
425
426 if (instance()->getInfo(filepath, info))
427 {
428 if (filtertype != Filesystem::FILETYPE_MAX_ENUM && info.type != filtertype)
429 {
430 lua_pushnil(L);
431 return 1;
432 }
433
434 const char *typestr = nullptr;
435 if (!Filesystem::getConstant(info.type, typestr))
436 return luaL_error(L, "Unknown file type.");
437
438 if (lua_istable(L, startidx))
439 lua_pushvalue(L, startidx);
440 else
441 lua_createtable(L, 0, 3);
442
443 lua_pushstring(L, typestr);
444 lua_setfield(L, -2, "type");
445
446 // Lua numbers (doubles) can't fit the full range of 64 bit ints.
447 info.size = std::min<int64>(info.size, 0x20000000000000LL);
448 if (info.size >= 0)
449 {
450 lua_pushnumber(L, (lua_Number) info.size);
451 lua_setfield(L, -2, "size");
452 }
453
454 info.modtime = std::min<int64>(info.modtime, 0x20000000000000LL);
455 if (info.modtime >= 0)
456 {
457 lua_pushnumber(L, (lua_Number) info.modtime);
458 lua_setfield(L, -2, "modtime");
459 }
460 }
461 else
462 lua_pushnil(L);
463
464 return 1;
465}
466
467int w_createDirectory(lua_State *L)
468{
469 const char *arg = luaL_checkstring(L, 1);
470 luax_pushboolean(L, instance()->createDirectory(arg));
471 return 1;
472}
473
474int w_remove(lua_State *L)
475{
476 const char *arg = luaL_checkstring(L, 1);
477 luax_pushboolean(L, instance()->remove(arg));
478 return 1;
479}
480
481int w_read(lua_State *L)
482{
483 love::data::ContainerType ctype = love::data::CONTAINER_STRING;
484 int startidx = 1;
485
486 if (lua_type(L, 2) == LUA_TSTRING)
487 {
488 ctype = love::data::luax_checkcontainertype(L, 1);
489 startidx = 2;
490 }
491
492 const char *filename = luaL_checkstring(L, startidx + 0);
493 int64 len = (int64) luaL_optinteger(L, startidx + 1, File::ALL);
494
495 FileData *data = nullptr;
496 try
497 {
498 data = instance()->read(filename, len);
499 }
500 catch (love::Exception &e)
501 {
502 return luax_ioError(L, "%s", e.what());
503 }
504
505 if (data == nullptr)
506 return luax_ioError(L, "File could not be read.");
507
508 if (ctype == love::data::CONTAINER_DATA)
509 luax_pushtype(L, data);
510 else
511 lua_pushlstring(L, (const char *) data->getData(), data->getSize());
512
513 lua_pushinteger(L, data->getSize());
514
515 // Lua has a copy now, so we can free it.
516 data->release();
517
518 return 2;
519}
520
521static int w_write_or_append(lua_State *L, File::Mode mode)
522{
523 const char *filename = luaL_checkstring(L, 1);
524
525 const char *input = nullptr;
526 size_t len = 0;
527
528 if (luax_istype(L, 2, love::Data::type))
529 {
530 love::Data *data = luax_totype<love::Data>(L, 2);
531 input = (const char *) data->getData();
532 len = data->getSize();
533 }
534 else if (lua_isstring(L, 2))
535 input = lua_tolstring(L, 2, &len);
536 else
537 return luaL_argerror(L, 2, "string or Data expected");
538
539 // Get how much we should write. Length of string default.
540 len = luaL_optinteger(L, 3, len);
541
542 try
543 {
544 if (mode == File::MODE_APPEND)
545 instance()->append(filename, (const void *) input, len);
546 else
547 instance()->write(filename, (const void *) input, len);
548 }
549 catch (love::Exception &e)
550 {
551 return luax_ioError(L, "%s", e.what());
552 }
553
554 luax_pushboolean(L, true);
555 return 1;
556}
557
558int w_write(lua_State *L)
559{
560 return w_write_or_append(L, File::MODE_WRITE);
561}
562
563int w_append(lua_State *L)
564{
565 return w_write_or_append(L, File::MODE_APPEND);
566}
567
568int w_getDirectoryItems(lua_State *L)
569{
570 const char *dir = luaL_checkstring(L, 1);
571 std::vector<std::string> items;
572
573 instance()->getDirectoryItems(dir, items);
574
575 lua_createtable(L, (int) items.size(), 0);
576
577 for (int i = 0; i < (int) items.size(); i++)
578 {
579 lua_pushstring(L, items[i].c_str());
580 lua_rawseti(L, -2, i + 1);
581 }
582
583 // Return the table.
584 return 1;
585}
586
587int w_lines(lua_State *L)
588{
589 if (lua_isstring(L, 1))
590 {
591 File *file = instance()->newFile(lua_tostring(L, 1));
592 bool success = false;
593
594 luax_catchexcept(L, [&](){ success = file->open(File::MODE_READ); });
595
596 if (!success)
597 {
598 file->release();
599 return luaL_error(L, "Could not open file.");
600 }
601
602 luax_pushtype(L, file);
603 file->release();
604 }
605 else
606 return luaL_argerror(L, 1, "expected filename.");
607
608 lua_pushstring(L, ""); // buffer
609 lua_pushstring(L, 0); // buffer offset
610 lua_pushcclosure(L, w_File_lines_i, 3);
611 return 1;
612}
613
614int w_load(lua_State *L)
615{
616 std::string filename = std::string(luaL_checkstring(L, 1));
617
618 Data *data = nullptr;
619 try
620 {
621 data = instance()->read(filename.c_str());
622 }
623 catch (love::Exception &e)
624 {
625 return luax_ioError(L, "%s", e.what());
626 }
627
628 int status = luaL_loadbuffer(L, (const char *)data->getData(), data->getSize(), ("@" + filename).c_str());
629
630 data->release();
631
632 // Load the chunk, but don't run it.
633 switch (status)
634 {
635 case LUA_ERRMEM:
636 return luaL_error(L, "Memory allocation error: %s\n", lua_tostring(L, -1));
637 case LUA_ERRSYNTAX:
638 return luaL_error(L, "Syntax error: %s\n", lua_tostring(L, -1));
639 default: // success
640 return 1;
641 }
642}
643
644int w_setSymlinksEnabled(lua_State *L)
645{
646 instance()->setSymlinksEnabled(luax_checkboolean(L, 1));
647 return 0;
648}
649
650int w_areSymlinksEnabled(lua_State *L)
651{
652 luax_pushboolean(L, instance()->areSymlinksEnabled());
653 return 1;
654}
655
656int w_getRequirePath(lua_State *L)
657{
658 std::stringstream path;
659 bool seperator = false;
660 for (auto &element : instance()->getRequirePath())
661 {
662 if (seperator)
663 path << ";";
664 else
665 seperator = true;
666
667 path << element;
668 }
669
670 luax_pushstring(L, path.str());
671 return 1;
672}
673
674int w_getCRequirePath(lua_State *L)
675{
676 std::stringstream path;
677 bool seperator = false;
678 for (auto &element : instance()->getCRequirePath())
679 {
680 if (seperator)
681 path << ";";
682 else
683 seperator = true;
684
685 path << element;
686 }
687
688 luax_pushstring(L, path.str());
689 return 1;
690}
691
692int w_setRequirePath(lua_State *L)
693{
694 std::string element = luax_checkstring(L, 1);
695 auto &requirePath = instance()->getRequirePath();
696
697 requirePath.clear();
698 std::stringstream path;
699 path << element;
700
701 while(std::getline(path, element, ';'))
702 requirePath.push_back(element);
703
704 return 0;
705}
706
707int w_setCRequirePath(lua_State *L)
708{
709 std::string element = luax_checkstring(L, 1);
710 auto &requirePath = instance()->getCRequirePath();
711
712 requirePath.clear();
713 std::stringstream path;
714 path << element;
715
716 while(std::getline(path, element, ';'))
717 requirePath.push_back(element);
718
719 return 0;
720}
721
722static void replaceAll(std::string &str, const std::string &substr, const std::string &replacement)
723{
724 std::vector<size_t> locations;
725 size_t pos = 0;
726 size_t sublen = substr.length();
727
728 while ((pos = str.find(substr, pos)) != std::string::npos)
729 {
730 locations.push_back(pos);
731 pos += sublen;
732 }
733
734 for (int i = (int) locations.size() - 1; i >= 0; i--)
735 str.replace(locations[i], sublen, replacement);
736}
737
738int loader(lua_State *L)
739{
740 std::string modulename = luax_checkstring(L, 1);
741
742 for (char &c : modulename)
743 {
744 if (c == '.')
745 c = '/';
746 }
747
748 auto *inst = instance();
749 for (std::string element : inst->getRequirePath())
750 {
751 replaceAll(element, "?", modulename);
752
753 Filesystem::Info info = {};
754 if (inst->getInfo(element.c_str(), info) && info.type != Filesystem::FILETYPE_DIRECTORY)
755 {
756 lua_pop(L, 1);
757 lua_pushstring(L, element.c_str());
758 return w_load(L);
759 }
760 }
761
762 std::string errstr = "\n\tno '%s' in LOVE game directories.";
763
764 lua_pushfstring(L, errstr.c_str(), modulename.c_str());
765 return 1;
766}
767
768static const char *library_extensions[] =
769{
770#ifdef LOVE_WINDOWS
771 ".dll"
772#elif defined(LOVE_MACOSX) || defined(LOVE_IOS)
773 ".dylib", ".so"
774#else
775 ".so"
776#endif
777};
778
779int extloader(lua_State *L)
780{
781 std::string filename = luax_checkstring(L, 1);
782 std::string tokenized_name(filename);
783 std::string tokenized_function(filename);
784
785 // We need both the tokenized filename (dots replaced with slashes)
786 // and the tokenized function name (dots replaced with underscores)
787 // NOTE: Lua's loader queries more names than this one.
788 for (unsigned int i = 0; i < tokenized_name.size(); i++)
789 {
790 if (tokenized_name[i] == '.')
791 {
792 tokenized_name[i] = '/';
793 tokenized_function[i] = '_';
794 }
795 }
796
797 void *handle = nullptr;
798 auto *inst = instance();
799
800#ifdef LOVE_ANDROID
801 // Specifically Android, look the library path based on getCRequirePath first
802 std::string androidPath(love::android::getCRequirePath());
803
804 if (!androidPath.empty())
805 {
806 // Replace ? with just the dotted filename (not tokenized_name)
807 replaceAll(androidPath, "?", filename);
808
809 // Load directly, don't check for existence.
810 handle = SDL_LoadObject(androidPath.c_str());
811 }
812
813 if (!handle)
814 {
815#endif // LOVE_ANDROID
816
817 for (const std::string &el : inst->getCRequirePath())
818 {
819 for (const char *ext : library_extensions)
820 {
821 std::string element = el;
822
823 // Replace ?? with the filename and extension
824 replaceAll(element, "??", tokenized_name + ext);
825
826 // And ? with just the filename
827 replaceAll(element, "?", tokenized_name);
828
829 Filesystem::Info info = {};
830 if (!inst->getInfo(element.c_str(), info) || info.type == Filesystem::FILETYPE_DIRECTORY)
831 continue;
832
833 // Now resolve the full path, as we're bypassing physfs for the next part.
834 std::string filepath = inst->getRealDirectory(element.c_str()) + LOVE_PATH_SEPARATOR + element;
835
836 handle = SDL_LoadObject(filepath.c_str());
837 // Can fail, for instance if it turned out the source was a zip
838 if (handle)
839 break;
840 }
841
842 if (handle)
843 break;
844 }
845
846#ifdef LOVE_ANDROID
847 } // if (!handle)
848#endif
849
850 if (!handle)
851 {
852 lua_pushfstring(L, "\n\tno file '%s' in LOVE paths.", tokenized_name.c_str());
853 return 1;
854 }
855
856 // We look for both loveopen_ and luaopen_, so libraries with specific love support
857 // can tell when they've been loaded by love.
858 void *func = SDL_LoadFunction(handle, ("loveopen_" + tokenized_function).c_str());
859 if (!func)
860 func = SDL_LoadFunction(handle, ("luaopen_" + tokenized_function).c_str());
861
862 if (!func)
863 {
864 SDL_UnloadObject(handle);
865 lua_pushfstring(L, "\n\tC library '%s' is incompatible.", tokenized_name.c_str());
866 return 1;
867 }
868
869 lua_pushcfunction(L, (lua_CFunction) func);
870 return 1;
871}
872
873// Deprecated functions.
874
875int w_exists(lua_State *L)
876{
877 luax_markdeprecated(L, "love.filesystem.exists", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
878 const char *arg = luaL_checkstring(L, 1);
879 Filesystem::Info info = {};
880 luax_pushboolean(L, instance()->getInfo(arg, info));
881 return 1;
882}
883
884int w_isDirectory(lua_State *L)
885{
886 luax_markdeprecated(L, "love.filesystem.isDirectory", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
887 const char *arg = luaL_checkstring(L, 1);
888 Filesystem::Info info = {};
889 bool exists = instance()->getInfo(arg, info);
890 luax_pushboolean(L, exists && info.type == Filesystem::FILETYPE_DIRECTORY);
891 return 1;
892}
893
894int w_isFile(lua_State *L)
895{
896 luax_markdeprecated(L, "love.filesystem.isFile", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
897 const char *arg = luaL_checkstring(L, 1);
898 Filesystem::Info info = {};
899 bool exists = instance()->getInfo(arg, info);
900 luax_pushboolean(L, exists && info.type == Filesystem::FILETYPE_FILE);
901 return 1;
902}
903
904int w_isSymlink(lua_State *L)
905{
906 luax_markdeprecated(L, "love.filesystem.isSymlink", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
907 const char *filename = luaL_checkstring(L, 1);
908 Filesystem::Info info = {};
909 bool exists = instance()->getInfo(filename, info);
910 luax_pushboolean(L, exists && info.type == Filesystem::FILETYPE_SYMLINK);
911 return 1;
912}
913
914int w_getLastModified(lua_State *L)
915{
916 luax_markdeprecated(L, "love.filesystem.getLastModified", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
917
918 const char *filename = luaL_checkstring(L, 1);
919
920 Filesystem::Info info = {};
921 bool exists = instance()->getInfo(filename, info);
922
923 if (!exists)
924 return luax_ioError(L, "File does not exist");
925 else if (info.modtime == -1)
926 return luax_ioError(L, "Could not determine file modification date.");
927
928 lua_pushnumber(L, (lua_Number) info.modtime);
929 return 1;
930}
931
932int w_getSize(lua_State *L)
933{
934 luax_markdeprecated(L, "love.filesystem.getSize", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
935
936 const char *filename = luaL_checkstring(L, 1);
937
938 Filesystem::Info info = {};
939 bool exists = instance()->getInfo(filename, info);
940
941 if (!exists)
942 luax_ioError(L, "File does not exist");
943 else if (info.size == -1)
944 return luax_ioError(L, "Could not determine file size.");
945 else if (info.size >= 0x20000000000000LL)
946 return luax_ioError(L, "Size too large to fit into a Lua number!");
947
948 lua_pushnumber(L, (lua_Number) info.size);
949 return 1;
950}
951
952// List of functions to wrap.
953static const luaL_Reg functions[] =
954{
955 { "init", w_init },
956 { "setFused", w_setFused },
957 { "isFused", w_isFused },
958 { "_setAndroidSaveExternal", w_setAndroidSaveExternal },
959 { "setIdentity", w_setIdentity },
960 { "getIdentity", w_getIdentity },
961 { "setSource", w_setSource },
962 { "getSource", w_getSource },
963 { "mount", w_mount },
964 { "unmount", w_unmount },
965 { "newFile", w_newFile },
966 { "getWorkingDirectory", w_getWorkingDirectory },
967 { "getUserDirectory", w_getUserDirectory },
968 { "getAppdataDirectory", w_getAppdataDirectory },
969 { "getSaveDirectory", w_getSaveDirectory },
970 { "getSourceBaseDirectory", w_getSourceBaseDirectory },
971 { "getRealDirectory", w_getRealDirectory },
972 { "getExecutablePath", w_getExecutablePath },
973 { "createDirectory", w_createDirectory },
974 { "remove", w_remove },
975 { "read", w_read },
976 { "write", w_write },
977 { "append", w_append },
978 { "getDirectoryItems", w_getDirectoryItems },
979 { "lines", w_lines },
980 { "load", w_load },
981 { "getInfo", w_getInfo },
982 { "setSymlinksEnabled", w_setSymlinksEnabled },
983 { "areSymlinksEnabled", w_areSymlinksEnabled },
984 { "newFileData", w_newFileData },
985 { "getRequirePath", w_getRequirePath },
986 { "setRequirePath", w_setRequirePath },
987 { "getCRequirePath", w_getCRequirePath },
988 { "setCRequirePath", w_setCRequirePath },
989
990 // Deprecated.
991 { "exists", w_exists },
992 { "isDirectory", w_isDirectory },
993 { "isFile", w_isFile },
994 { "isSymlink", w_isSymlink },
995 { "getLastModified", w_getLastModified },
996 { "getSize", w_getSize },
997
998 { 0, 0 }
999};
1000
1001static const lua_CFunction types[] =
1002{
1003 luaopen_file,
1004 luaopen_droppedfile,
1005 luaopen_filedata,
1006 0
1007};
1008
1009extern "C" int luaopen_love_filesystem(lua_State *L)
1010{
1011 Filesystem *instance = instance();
1012 if (instance == nullptr)
1013 {
1014 luax_catchexcept(L, [&](){ instance = new physfs::Filesystem(); });
1015 }
1016 else
1017 instance->retain();
1018
1019 // The love loaders should be tried after package.preload.
1020 love::luax_register_searcher(L, loader, 2);
1021 love::luax_register_searcher(L, extloader, 3);
1022
1023 WrappedModule w;
1024 w.module = instance;
1025 w.name = "filesystem";
1026 w.type = &Filesystem::type;
1027 w.functions = functions;
1028 w.types = types;
1029
1030 return luax_register_module(L, w);
1031}
1032
1033} // filesystem
1034} // love
1035