Filesystem.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. /**
  2. * Copyright (c) 2006-2014 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. #include "common/config.h"
  21. #include <iostream>
  22. #include <sstream>
  23. #include "common/utf8.h"
  24. #include "common/b64.h"
  25. #include "Filesystem.h"
  26. namespace
  27. {
  28. size_t getDriveDelim(const std::string &input)
  29. {
  30. for (size_t i = 0; i < input.size(); ++i)
  31. if (input[i] == '/' || input[i] == '\\')
  32. return i;
  33. // Something's horribly wrong
  34. return 0;
  35. }
  36. std::string getDriveRoot(const std::string &input)
  37. {
  38. return input.substr(0, getDriveDelim(input)+1);
  39. }
  40. std::string skipDriveRoot(const std::string &input)
  41. {
  42. return input.substr(getDriveDelim(input)+1);
  43. }
  44. std::string normalize(const std::string &input)
  45. {
  46. std::stringstream out;
  47. bool seenSep = false, isSep = false;
  48. for (size_t i = 0; i < input.size(); ++i)
  49. {
  50. isSep = (input[i] == LOVE_PATH_SEPARATOR[0]);
  51. if (!isSep || !seenSep)
  52. out << input[i];
  53. seenSep = isSep;
  54. }
  55. return out.str();
  56. }
  57. }
  58. namespace love
  59. {
  60. namespace filesystem
  61. {
  62. namespace physfs
  63. {
  64. Filesystem::Filesystem()
  65. : initialized(false)
  66. , fused(false)
  67. , fusedSet(false)
  68. {
  69. moduleType = M_FILESYSTEM;
  70. }
  71. Filesystem::~Filesystem()
  72. {
  73. if (initialized)
  74. PHYSFS_deinit();
  75. }
  76. const char *Filesystem::getName() const
  77. {
  78. return "love.filesystem.physfs";
  79. }
  80. void Filesystem::init(const char *arg0)
  81. {
  82. if (!PHYSFS_init(arg0))
  83. throw Exception(PHYSFS_getLastError());
  84. initialized = true;
  85. }
  86. void Filesystem::setFused(bool fused)
  87. {
  88. if (fusedSet)
  89. return;
  90. this->fused = fused;
  91. fusedSet = true;
  92. }
  93. bool Filesystem::isFused() const
  94. {
  95. if (!fusedSet)
  96. return false;
  97. return fused;
  98. }
  99. bool Filesystem::setIdentity(const char *ident, bool appendToPath)
  100. {
  101. if (!initialized)
  102. return false;
  103. std::string old_save_path = save_path_full;
  104. // Store the save directory.
  105. save_identity = std::string(ident);
  106. // Generate the relative path to the game save folder.
  107. save_path_relative = std::string(LOVE_APPDATA_PREFIX LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + save_identity;
  108. // Generate the full path to the game save folder.
  109. save_path_full = std::string(getAppdataDirectory()) + std::string(LOVE_PATH_SEPARATOR);
  110. if (fused)
  111. save_path_full += std::string(LOVE_APPDATA_PREFIX) + save_identity;
  112. else
  113. save_path_full += save_path_relative;
  114. save_path_full = normalize(save_path_full);
  115. // We now have something like:
  116. // save_identity: game
  117. // save_path_relative: ./LOVE/game
  118. // save_path_full: C:\Documents and Settings\user\Application Data/LOVE/game
  119. // We don't want old read-only save paths to accumulate when we set a new
  120. // identity.
  121. if (!old_save_path.empty())
  122. PHYSFS_removeFromSearchPath(old_save_path.c_str());
  123. // Try to add the save directory to the search path.
  124. // (No error on fail, it means that the path doesn't exist).
  125. PHYSFS_addToSearchPath(save_path_full.c_str(), appendToPath);
  126. // HACK: This forces setupWriteDirectory to be called the next time a file
  127. // is opened for writing - otherwise it won't be called at all if it was
  128. // already called at least once before.
  129. PHYSFS_setWriteDir(nullptr);
  130. return true;
  131. }
  132. const char *Filesystem::getIdentity() const
  133. {
  134. return save_identity.c_str();
  135. }
  136. bool Filesystem::setSource(const char *source)
  137. {
  138. if (!initialized)
  139. return false;
  140. // Check whether directory is already set.
  141. if (!game_source.empty())
  142. return false;
  143. // Add the directory.
  144. if (!PHYSFS_addToSearchPath(source, 1))
  145. return false;
  146. // Save the game source.
  147. game_source = std::string(source);
  148. return true;
  149. }
  150. const char *Filesystem::getSource() const
  151. {
  152. return game_source.c_str();
  153. }
  154. bool Filesystem::setupWriteDirectory()
  155. {
  156. if (!initialized)
  157. return false;
  158. // These must all be set.
  159. if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty())
  160. return false;
  161. // We need to make sure the write directory is created. To do that, we also
  162. // need to make sure all its parent directories are also created.
  163. std::string temp_writedir = getDriveRoot(save_path_full);
  164. std::string temp_createdir = skipDriveRoot(save_path_full);
  165. // On some sandboxed platforms, physfs will break when its write directory
  166. // is the root of the drive and it tries to create a folder (even if the
  167. // folder's path is in a writable location.) If the user's home folder is
  168. // in the save path, we'll try starting from there instead.
  169. if (save_path_full.find(getUserDirectory()) == 0)
  170. {
  171. temp_writedir = getUserDirectory();
  172. temp_createdir = save_path_full.substr(getUserDirectory().length());
  173. // Strip leading '/' characters from the path we want to create.
  174. size_t startpos = temp_createdir.find_first_not_of('/');
  175. if (startpos != std::string::npos)
  176. temp_createdir = temp_createdir.substr(startpos);
  177. }
  178. // Set either '/' or the user's home as a writable directory.
  179. // (We must create the save folder before mounting it).
  180. if (!PHYSFS_setWriteDir(temp_writedir.c_str()))
  181. return false;
  182. // Create the save folder. (We're now "at" either '/' or the user's home).
  183. if (!createDirectory(temp_createdir.c_str()))
  184. {
  185. // Clear the write directory in case of error.
  186. PHYSFS_setWriteDir(nullptr);
  187. return false;
  188. }
  189. // Set the final write directory.
  190. if (!PHYSFS_setWriteDir(save_path_full.c_str()))
  191. return false;
  192. // Add the directory. (Will not be readded if already present).
  193. if (!PHYSFS_addToSearchPath(save_path_full.c_str(), 0))
  194. {
  195. PHYSFS_setWriteDir(nullptr); // Clear the write directory in case of error.
  196. return false;
  197. }
  198. return true;
  199. }
  200. bool Filesystem::mount(const char *archive, const char *mountpoint, bool appendToPath)
  201. {
  202. if (!initialized || !archive)
  203. return false;
  204. std::string realPath;
  205. std::string sourceBase = getSourceBaseDirectory();
  206. if (isFused() && sourceBase.compare(archive) == 0)
  207. {
  208. // Special case: if the game is fused and the archive is the source's
  209. // base directory, mount it even though it's outside of the save dir.
  210. realPath = sourceBase;
  211. }
  212. else
  213. {
  214. // Not allowed for safety reasons.
  215. if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0)
  216. return false;
  217. const char *realDir = PHYSFS_getRealDir(archive);
  218. if (!realDir)
  219. return false;
  220. realPath = realDir;
  221. // Always disallow mounting of files inside the game source, since it
  222. // won't work anyway if the game source is a zipped .love file.
  223. if (realPath.find(game_source) == 0)
  224. return false;
  225. realPath += LOVE_PATH_SEPARATOR;
  226. realPath += archive;
  227. }
  228. if (realPath.length() == 0)
  229. return false;
  230. return PHYSFS_mount(realPath.c_str(), mountpoint, appendToPath);
  231. }
  232. bool Filesystem::unmount(const char *archive)
  233. {
  234. if (!initialized || !archive)
  235. return false;
  236. std::string realPath;
  237. std::string sourceBase = getSourceBaseDirectory();
  238. if (isFused() && sourceBase.compare(archive) == 0)
  239. {
  240. // Special case: if the game is fused and the archive is the source's
  241. // base directory, unmount it even though it's outside of the save dir.
  242. realPath = sourceBase;
  243. }
  244. else
  245. {
  246. // Not allowed for safety reasons.
  247. if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0)
  248. return false;
  249. const char *realDir = PHYSFS_getRealDir(archive);
  250. if (!realDir)
  251. return false;
  252. realPath = realDir;
  253. realPath += LOVE_PATH_SEPARATOR;
  254. realPath += archive;
  255. }
  256. const char *mountPoint = PHYSFS_getMountPoint(realPath.c_str());
  257. if (!mountPoint)
  258. return false;
  259. return PHYSFS_removeFromSearchPath(realPath.c_str());
  260. }
  261. File *Filesystem::newFile(const char *filename) const
  262. {
  263. return new File(filename);
  264. }
  265. FileData *Filesystem::newFileData(void *data, unsigned int size, const char *filename) const
  266. {
  267. FileData *fd = new FileData(size, std::string(filename));
  268. // Copy the data into FileData.
  269. memcpy(fd->getData(), data, size);
  270. return fd;
  271. }
  272. FileData *Filesystem::newFileData(const char *b64, const char *filename) const
  273. {
  274. int size = strlen(b64);
  275. int outsize = 0;
  276. char *dst = b64_decode(b64, size, outsize);
  277. FileData *fd = new FileData(outsize, std::string(filename));
  278. // Copy the data into FileData.
  279. memcpy(fd->getData(), dst, outsize);
  280. delete [] dst;
  281. return fd;
  282. }
  283. const char *Filesystem::getWorkingDirectory()
  284. {
  285. if (cwd.empty())
  286. {
  287. #ifdef LOVE_WINDOWS
  288. WCHAR w_cwd[LOVE_MAX_PATH];
  289. _wgetcwd(w_cwd, LOVE_MAX_PATH);
  290. cwd = to_utf8(w_cwd);
  291. replace_char(cwd, '\\', '/');
  292. #else
  293. char *cwd_char = new char[LOVE_MAX_PATH];
  294. if (getcwd(cwd_char, LOVE_MAX_PATH))
  295. cwd = cwd_char; // if getcwd fails, cwd_char (and thus cwd) will still be empty
  296. delete [] cwd_char;
  297. #endif
  298. }
  299. return cwd.c_str();
  300. }
  301. std::string Filesystem::getUserDirectory()
  302. {
  303. static std::string userDir = normalize(PHYSFS_getUserDir());
  304. return userDir;
  305. }
  306. std::string Filesystem::getAppdataDirectory()
  307. {
  308. if (appdata.empty())
  309. {
  310. #ifdef LOVE_WINDOWS
  311. wchar_t *w_appdata = _wgetenv(L"APPDATA");
  312. appdata = to_utf8(w_appdata);
  313. replace_char(appdata, '\\', '/');
  314. #elif defined(LOVE_MACOSX)
  315. std::string udir = getUserDirectory();
  316. udir.append("/Library/Application Support");
  317. appdata = normalize(udir);
  318. #elif defined(LOVE_LINUX)
  319. char *xdgdatahome = getenv("XDG_DATA_HOME");
  320. if (!xdgdatahome)
  321. appdata = normalize(std::string(getUserDirectory()) + "/.local/share/");
  322. else
  323. appdata = xdgdatahome;
  324. #else
  325. appdata = getUserDirectory();
  326. #endif
  327. }
  328. return appdata;
  329. }
  330. const char *Filesystem::getSaveDirectory()
  331. {
  332. return save_path_full.c_str();
  333. }
  334. std::string Filesystem::getSourceBaseDirectory() const
  335. {
  336. size_t source_len = game_source.length();
  337. if (source_len == 0)
  338. return "";
  339. // FIXME: This doesn't take into account parent and current directory
  340. // symbols (i.e. '..' and '.')
  341. #ifdef LOVE_WINDOWS
  342. // In windows, delimiters can be either '/' or '\'.
  343. size_t base_end_pos = game_source.find_last_of("/\\", source_len - 2);
  344. #else
  345. size_t base_end_pos = game_source.find_last_of('/', source_len - 2);
  346. #endif
  347. if (base_end_pos == std::string::npos)
  348. return "";
  349. // If the source is in the unix root (aka '/'), we want to keep the '/'.
  350. if (base_end_pos == 0)
  351. base_end_pos = 1;
  352. return game_source.substr(0, base_end_pos);
  353. }
  354. std::string Filesystem::getRealDirectory(const char *filename) const
  355. {
  356. const char *dir = PHYSFS_getRealDir(filename);
  357. if (dir == nullptr)
  358. throw love::Exception("File does not exist.");
  359. return std::string(dir);
  360. }
  361. bool Filesystem::exists(const char *file) const
  362. {
  363. return PHYSFS_exists(file);
  364. }
  365. bool Filesystem::isDirectory(const char *file) const
  366. {
  367. return PHYSFS_isDirectory(file);
  368. }
  369. bool Filesystem::isFile(const char *file) const
  370. {
  371. return exists(file) && !isDirectory(file);
  372. }
  373. bool Filesystem::createDirectory(const char *dir)
  374. {
  375. if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory())
  376. return false;
  377. if (!PHYSFS_mkdir(dir))
  378. return false;
  379. return true;
  380. }
  381. bool Filesystem::remove(const char *file)
  382. {
  383. if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory())
  384. return false;
  385. if (!PHYSFS_delete(file))
  386. return false;
  387. return true;
  388. }
  389. Data *Filesystem::read(const char *filename, int64 size) const
  390. {
  391. File file(filename);
  392. file.open(File::READ);
  393. // close() is called in the File destructor.
  394. return file.read(size);
  395. }
  396. void Filesystem::write(const char *filename, const void *data, int64 size) const
  397. {
  398. File file(filename);
  399. file.open(File::WRITE);
  400. // close() is called in the File destructor.
  401. if (!file.write(data, size))
  402. throw love::Exception("Data could not be written.");
  403. }
  404. void Filesystem::append(const char *filename, const void *data, int64 size) const
  405. {
  406. File file(filename);
  407. file.open(File::APPEND);
  408. // close() is called in the File destructor.
  409. if (!file.write(data, size))
  410. throw love::Exception("Data could not be written.");
  411. }
  412. int Filesystem::getDirectoryItems(lua_State *L)
  413. {
  414. const char *dir = luaL_checkstring(L, 1);
  415. bool hascallback = !lua_isnoneornil(L, 2);
  416. if (hascallback)
  417. luaL_checktype(L, 2, LUA_TFUNCTION);
  418. char **rc = PHYSFS_enumerateFiles(dir);
  419. int index = 1;
  420. lua_newtable(L);
  421. for (char **i = rc; *i != 0; i++)
  422. {
  423. if (hascallback)
  424. {
  425. lua_pushvalue(L, 2);
  426. lua_pushstring(L, *i);
  427. lua_call(L, 1, 0);
  428. }
  429. lua_pushstring(L, *i);
  430. lua_rawseti(L, -2, index);
  431. index++;
  432. }
  433. PHYSFS_freeList(rc);
  434. return 1;
  435. }
  436. int Filesystem::lines_i(lua_State *L)
  437. {
  438. const int bufsize = 1024;
  439. char buf[bufsize];
  440. int linesize = 0;
  441. bool newline = false;
  442. File *file = luax_checktype<File>(L, lua_upvalueindex(1), "File", FILESYSTEM_FILE_T);
  443. // Only accept read mode at this point.
  444. if (file->getMode() != File::READ)
  445. return luaL_error(L, "File needs to stay in read mode.");
  446. int64 pos = file->tell();
  447. int64 userpos = -1;
  448. if (lua_isnoneornil(L, lua_upvalueindex(2)) == 0)
  449. {
  450. // User may have changed the file position.
  451. userpos = pos;
  452. pos = (int64) lua_tonumber(L, lua_upvalueindex(2));
  453. if (userpos != pos)
  454. file->seek(pos);
  455. }
  456. while (!newline && !file->eof())
  457. {
  458. // This 64-bit to 32-bit integer cast should be safe as it never exceeds bufsize.
  459. int read = (int) file->read(buf, bufsize);
  460. if (read < 0)
  461. return luaL_error(L, "Could not read from file.");
  462. linesize += read;
  463. for (int i = 0; i < read; i++)
  464. {
  465. if (buf[i] == '\n')
  466. {
  467. linesize -= read - i;
  468. newline = true;
  469. break;
  470. }
  471. }
  472. }
  473. if (newline || (file->eof() && linesize > 0))
  474. {
  475. if (linesize < bufsize)
  476. {
  477. // We have the line in the buffer on the stack. No 'new' and 'read' needed.
  478. lua_pushlstring(L, buf, linesize > 0 && buf[linesize - 1] == '\r' ? linesize - 1 : linesize);
  479. if (userpos < 0)
  480. file->seek(pos + linesize + 1);
  481. }
  482. else
  483. {
  484. char *str = 0;
  485. try
  486. {
  487. str = new char[linesize + 1];
  488. }
  489. catch(std::bad_alloc &)
  490. {
  491. // Can't lua_error (longjmp) in exception handlers.
  492. }
  493. if (!str)
  494. return luaL_error(L, "Out of memory.");
  495. file->seek(pos);
  496. // Read the \n anyway and save us a call to seek.
  497. if (file->read(str, linesize + 1) == -1)
  498. {
  499. delete [] str;
  500. return luaL_error(L, "Could not read from file.");
  501. }
  502. lua_pushlstring(L, str, str[linesize - 1] == '\r' ? linesize - 1 : linesize);
  503. delete [] str;
  504. }
  505. if (userpos >= 0)
  506. {
  507. // Save new position in upvalue.
  508. lua_pushnumber(L, (lua_Number)(pos + linesize + 1));
  509. lua_replace(L, lua_upvalueindex(2));
  510. file->seek(userpos);
  511. }
  512. return 1;
  513. }
  514. // EOF reached.
  515. if (userpos >= 0 && luax_toboolean(L, lua_upvalueindex(3)))
  516. file->seek(userpos);
  517. else
  518. file->close();
  519. return 0;
  520. }
  521. int64 Filesystem::getLastModified(const char *filename) const
  522. {
  523. PHYSFS_sint64 time = PHYSFS_getLastModTime(filename);
  524. if (time == -1)
  525. throw love::Exception("Could not determine file modification date.");
  526. return time;
  527. }
  528. int64 Filesystem::getSize(const char *filename) const
  529. {
  530. File file(filename);
  531. int64 size = file.getSize();
  532. return size;
  533. }
  534. } // physfs
  535. } // filesystem
  536. } // love