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 | |
45 | namespace love |
46 | { |
47 | namespace filesystem |
48 | { |
49 | |
50 | #define instance() (Module::getInstance<Filesystem>(Module::M_FILESYSTEM)) |
51 | |
52 | bool hack_setupWriteDirectory() |
53 | { |
54 | if (instance() != 0) |
55 | return instance()->setupWriteDirectory(); |
56 | return false; |
57 | } |
58 | |
59 | int 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 | |
66 | int 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 | |
74 | int w_isFused(lua_State *L) |
75 | { |
76 | luax_pushboolean(L, instance()->isFused()); |
77 | return 1; |
78 | } |
79 | |
80 | int w_setAndroidSaveExternal(lua_State *L) |
81 | { |
82 | bool useExternal = luax_optboolean(L, 1, false); |
83 | instance()->setAndroidSaveExternal(useExternal); |
84 | return 0; |
85 | } |
86 | |
87 | int 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 | |
98 | int w_getIdentity(lua_State *L) |
99 | { |
100 | lua_pushstring(L, instance()->getIdentity()); |
101 | return 1; |
102 | } |
103 | |
104 | int 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 | |
114 | int w_getSource(lua_State *L) |
115 | { |
116 | lua_pushstring(L, instance()->getSource()); |
117 | return 1; |
118 | } |
119 | |
120 | int 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 | |
162 | int 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 | |
177 | int 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 | |
212 | File *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 | |
229 | FileData *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 | |
261 | Data *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 | |
293 | bool 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 | |
298 | bool 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 | |
303 | int 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 | |
356 | int w_getWorkingDirectory(lua_State *L) |
357 | { |
358 | lua_pushstring(L, instance()->getWorkingDirectory()); |
359 | return 1; |
360 | } |
361 | |
362 | int w_getUserDirectory(lua_State *L) |
363 | { |
364 | luax_pushstring(L, instance()->getUserDirectory()); |
365 | return 1; |
366 | } |
367 | |
368 | int w_getAppdataDirectory(lua_State *L) |
369 | { |
370 | luax_pushstring(L, instance()->getAppdataDirectory()); |
371 | return 1; |
372 | } |
373 | |
374 | int w_getSaveDirectory(lua_State *L) |
375 | { |
376 | lua_pushstring(L, instance()->getSaveDirectory()); |
377 | return 1; |
378 | } |
379 | |
380 | int w_getSourceBaseDirectory(lua_State *L) |
381 | { |
382 | luax_pushstring(L, instance()->getSourceBaseDirectory()); |
383 | return 1; |
384 | } |
385 | |
386 | int 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 | |
404 | int w_getExecutablePath(lua_State *L) |
405 | { |
406 | luax_pushstring(L, instance()->getExecutablePath()); |
407 | return 1; |
408 | } |
409 | |
410 | int 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 | |
467 | int 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 | |
474 | int 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 | |
481 | int 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 | |
521 | static 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 | |
558 | int w_write(lua_State *L) |
559 | { |
560 | return w_write_or_append(L, File::MODE_WRITE); |
561 | } |
562 | |
563 | int w_append(lua_State *L) |
564 | { |
565 | return w_write_or_append(L, File::MODE_APPEND); |
566 | } |
567 | |
568 | int 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 | |
587 | int 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 | |
614 | int 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 | |
644 | int w_setSymlinksEnabled(lua_State *L) |
645 | { |
646 | instance()->setSymlinksEnabled(luax_checkboolean(L, 1)); |
647 | return 0; |
648 | } |
649 | |
650 | int w_areSymlinksEnabled(lua_State *L) |
651 | { |
652 | luax_pushboolean(L, instance()->areSymlinksEnabled()); |
653 | return 1; |
654 | } |
655 | |
656 | int 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 | |
674 | int 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 | |
692 | int 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 | |
707 | int 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 | |
722 | static 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 | |
738 | int 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 | |
768 | static 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 | |
779 | int 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 | |
875 | int 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 | |
884 | int 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 | |
894 | int 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 | |
904 | int 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 | |
914 | int 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 | |
932 | int 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. |
953 | static 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 | |
1001 | static const lua_CFunction types[] = |
1002 | { |
1003 | luaopen_file, |
1004 | luaopen_droppedfile, |
1005 | luaopen_filedata, |
1006 | 0 |
1007 | }; |
1008 | |
1009 | extern "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 | |