Filesystem.cpp 15 KB

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