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