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