Mercurial > love-experiments
annotate src/modules/filesystem/physfs/Filesystem.cpp @ 1380:cb7e512d785c
Added love.filesystem.getSize (issue #541)
| author | Alex Szpakowski <slime73@gmail.com> |
|---|---|
| date | Sun, 17 Mar 2013 01:56:24 -0300 |
| parents | b7f097053d74 |
| children | 4031a4e6cf25 |
| rev | line source |
|---|---|
| 1096 | 1 /** |
|
1282
b7f097053d74
Updated copyright text for the new year
Alex Szpakowski <slime73@gmail.com>
parents:
1146
diff
changeset
|
2 * Copyright (c) 2006-2013 LOVE Development Team |
| 1096 | 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 #include "common/config.h" | |
| 22 | |
| 23 #include <iostream> | |
| 24 | |
| 25 #include "common/utf8.h" | |
| 26 #include "common/b64.h" | |
| 27 | |
| 28 #include "Filesystem.h" | |
| 29 | |
| 30 namespace love | |
| 31 { | |
| 32 namespace filesystem | |
| 33 { | |
| 34 namespace physfs | |
| 35 { | |
| 36 | |
| 37 Filesystem::Filesystem() | |
| 38 : open_count(0) | |
| 39 , buffer(0) | |
| 40 , isInited(false) | |
| 41 , release(false) | |
| 42 , releaseSet(false) | |
| 43 { | |
| 44 } | |
| 45 | |
| 46 Filesystem::~Filesystem() | |
| 47 { | |
| 48 if (isInited) | |
| 49 { | |
| 50 isInited = false; | |
| 51 PHYSFS_deinit(); | |
| 52 } | |
| 53 } | |
| 54 | |
| 55 const char *Filesystem::getName() const | |
| 56 { | |
| 57 return "love.filesystem.physfs"; | |
| 58 } | |
| 59 | |
| 60 void Filesystem::init(const char *arg0) | |
| 61 { | |
| 62 if (!PHYSFS_init(arg0)) | |
| 63 throw Exception(PHYSFS_getLastError()); | |
| 64 isInited = true; | |
| 65 } | |
| 66 | |
| 67 void Filesystem::setRelease(bool release) | |
| 68 { | |
| 69 if (releaseSet) | |
| 70 return; | |
| 71 this->release = release; | |
| 72 releaseSet = true; | |
| 73 } | |
| 74 | |
| 75 bool Filesystem::isRelease() const | |
| 76 { | |
| 77 if (!releaseSet) | |
| 78 return false; | |
| 79 return release; | |
| 80 } | |
| 81 | |
| 82 bool Filesystem::setIdentity(const char *ident) | |
| 83 { | |
| 84 if (!isInited) | |
| 85 return false; | |
| 86 | |
| 87 // Store the save directory. | |
| 88 save_identity = std::string(ident); | |
| 89 | |
| 90 // Generate the relative path to the game save folder. | |
| 91 save_path_relative = std::string(LOVE_APPDATA_PREFIX LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + save_identity; | |
| 92 | |
| 93 // Generate the full path to the game save folder. | |
| 94 save_path_full = std::string(getAppdataDirectory()) + std::string(LOVE_PATH_SEPARATOR); | |
| 95 if (release) | |
| 96 save_path_full += std::string(LOVE_APPDATA_PREFIX) + save_identity; | |
| 97 else | |
| 98 save_path_full += save_path_relative; | |
| 99 | |
| 100 // We now have something like: | |
| 101 // save_identity: game | |
| 102 // save_path_relative: ./LOVE/game | |
| 103 // save_path_full: C:\Documents and Settings\user\Application Data/LOVE/game | |
| 104 | |
| 105 // Try to add the save directory to the search path. | |
| 106 // (No error on fail, it means that the path doesn't exist). | |
| 107 PHYSFS_addToSearchPath(save_path_full.c_str(), 0); | |
| 108 | |
| 109 return true; | |
| 110 } | |
| 111 | |
| 112 const char *Filesystem::getIdentity() const | |
| 113 { | |
| 114 return save_identity.c_str(); | |
| 115 } | |
| 116 | |
| 117 bool Filesystem::setSource(const char *source) | |
| 118 { | |
| 119 if (!isInited) | |
| 120 return false; | |
| 121 | |
| 122 // Check whether directory is already set. | |
| 123 if (!game_source.empty()) | |
| 124 return false; | |
| 125 | |
| 126 // Add the directory. | |
| 127 if (!PHYSFS_addToSearchPath(source, 1)) | |
| 128 return false; | |
| 129 | |
| 130 // Save the game source. | |
| 131 game_source = std::string(source); | |
| 132 | |
| 133 return true; | |
| 134 } | |
| 135 | |
| 136 bool Filesystem::setupWriteDirectory() | |
| 137 { | |
| 138 if (!isInited) | |
| 139 return false; | |
| 140 | |
| 141 // These must all be set. | |
| 142 if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty()) | |
| 143 return false; | |
| 144 | |
| 145 // Set the appdata folder as writable directory. | |
| 146 // (We must create the save folder before mounting it). | |
| 147 if (!PHYSFS_setWriteDir(getAppdataDirectory())) | |
| 148 return false; | |
| 149 | |
| 150 // Create the save folder. (We're now "at" %APPDATA%). | |
| 151 bool success = false; | |
| 152 if (release) | |
| 153 success = mkdir(save_identity.c_str()); | |
| 154 else | |
| 155 success = mkdir(save_path_relative.c_str()); | |
| 156 | |
| 157 if (!success) | |
| 158 { | |
| 159 PHYSFS_setWriteDir(0); // Clear the write directory in case of error. | |
| 160 return false; | |
| 161 } | |
| 162 | |
| 163 // Set the final write directory. | |
| 164 if (!PHYSFS_setWriteDir(save_path_full.c_str())) | |
| 165 return false; | |
| 166 | |
| 167 // Add the directory. (Will not be readded if already present). | |
| 168 if (!PHYSFS_addToSearchPath(save_path_full.c_str(), 0)) | |
| 169 { | |
| 170 PHYSFS_setWriteDir(0); // Clear the write directory in case of error. | |
| 171 return false; | |
| 172 } | |
| 173 | |
| 174 return true; | |
| 175 } | |
| 176 | |
| 177 File *Filesystem::newFile(const char *filename) | |
| 178 { | |
| 179 return new File(filename); | |
| 180 } | |
| 181 | |
| 182 FileData *Filesystem::newFileData(void *data, unsigned int size, const char *filename) | |
| 183 { | |
| 184 FileData *fd = new FileData(size, std::string(filename)); | |
| 185 | |
| 186 // Copy the data into FileData. | |
| 187 memcpy(fd->getData(), data, size); | |
| 188 | |
| 189 return fd; | |
| 190 } | |
| 191 | |
| 192 FileData *Filesystem::newFileData(const char *b64, const char *filename) | |
| 193 { | |
| 194 int size = strlen(b64); | |
| 195 int outsize = 0; | |
| 196 char *dst = b64_decode(b64, size, outsize); | |
| 197 FileData *fd = new FileData(outsize, std::string(filename)); | |
| 198 | |
| 199 // Copy the data into FileData. | |
| 200 memcpy(fd->getData(), dst, outsize); | |
| 201 delete [] dst; | |
| 202 | |
| 203 return fd; | |
| 204 } | |
| 205 | |
| 206 const char *Filesystem::getWorkingDirectory() | |
| 207 { | |
| 208 if (cwd.empty()) | |
| 209 { | |
| 210 #ifdef LOVE_WINDOWS | |
| 211 | |
| 212 WCHAR w_cwd[LOVE_MAX_PATH]; | |
| 213 _wgetcwd(w_cwd, LOVE_MAX_PATH); | |
| 214 cwd = to_utf8(w_cwd); | |
| 215 replace_char(cwd, '\\', '/'); | |
| 216 #else | |
| 217 char *cwd_char = new char[LOVE_MAX_PATH]; | |
| 218 | |
| 219 if (getcwd(cwd_char, LOVE_MAX_PATH)) | |
| 220 cwd = cwd_char; // if getcwd fails, cwd_char (and thus cwd) will still be empty | |
| 221 | |
| 222 delete [] cwd_char; | |
| 223 #endif | |
| 224 } | |
| 225 | |
| 226 return cwd.c_str(); | |
| 227 } | |
| 228 | |
| 229 const char *Filesystem::getUserDirectory() | |
| 230 { | |
| 231 return PHYSFS_getUserDir(); | |
| 232 } | |
| 233 | |
| 234 const char *Filesystem::getAppdataDirectory() | |
| 235 { | |
| 236 #ifdef LOVE_WINDOWS | |
| 237 if (appdata.empty()) | |
| 238 { | |
| 1146 | 239 wchar_t *w_appdata = _wgetenv(L"APPDATA"); |
| 1096 | 240 appdata = to_utf8(w_appdata); |
| 241 replace_char(appdata, '\\', '/'); | |
| 242 } | |
| 243 return appdata.c_str(); | |
| 244 #elif defined(LOVE_MACOSX) | |
| 245 if (appdata.empty()) | |
| 246 { | |
| 247 std::string udir = getUserDirectory(); | |
| 248 udir.append("/Library/Application Support"); | |
| 249 appdata = udir; | |
| 250 } | |
| 251 return appdata.c_str(); | |
| 252 #elif defined(LOVE_LINUX) | |
| 253 if (appdata.empty()) | |
| 254 { | |
| 255 char *xdgdatahome = getenv("XDG_DATA_HOME"); | |
| 256 if (!xdgdatahome) | |
| 257 appdata = std::string(getUserDirectory()) + "/.local/share/"; | |
| 258 else | |
| 259 appdata = xdgdatahome; | |
| 260 } | |
| 261 return appdata.c_str(); | |
| 262 #else | |
| 263 return getUserDirectory(); | |
| 264 #endif | |
| 265 } | |
| 266 | |
| 267 | |
| 268 const char *Filesystem::getSaveDirectory() | |
| 269 { | |
| 270 return save_path_full.c_str(); | |
| 271 } | |
| 272 | |
| 273 bool Filesystem::exists(const char *file) | |
| 274 { | |
| 275 if (PHYSFS_exists(file)) | |
| 276 return true; | |
| 277 return false; | |
| 278 } | |
| 279 | |
| 280 bool Filesystem::isDirectory(const char *file) | |
| 281 { | |
| 282 if (PHYSFS_isDirectory(file)) | |
| 283 return true; | |
| 284 return false; | |
| 285 } | |
| 286 | |
| 287 bool Filesystem::isFile(const char *file) | |
| 288 { | |
| 289 return exists(file) && !isDirectory(file); | |
| 290 } | |
| 291 | |
| 292 bool Filesystem::mkdir(const char *file) | |
| 293 { | |
| 294 if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory()) | |
| 295 return false; | |
| 296 | |
| 297 if (!PHYSFS_mkdir(file)) | |
| 298 return false; | |
| 299 return true; | |
| 300 } | |
| 301 | |
| 302 bool Filesystem::remove(const char *file) | |
| 303 { | |
| 304 if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory()) | |
| 305 return false; | |
| 306 | |
| 307 if (!PHYSFS_delete(file)) | |
| 308 return false; | |
| 309 return true; | |
| 310 } | |
| 311 | |
| 312 int Filesystem::read(lua_State *L) | |
| 313 { | |
| 314 // The file to read from. The file must either be created | |
| 315 // on-the-fly, or passed as a parameter. | |
| 316 File *file; | |
| 317 | |
| 318 if (lua_isstring(L, 1)) | |
| 319 { | |
| 320 // Create the file. | |
| 321 file = newFile(lua_tostring(L, 1)); | |
| 322 } | |
| 323 else | |
| 324 return luaL_error(L, "Expected filename."); | |
| 325 | |
| 326 // Optionally, the caller can specify whether to read | |
| 327 // the whole file, or just a part of it. | |
| 328 int count = luaL_optint(L, 2, (lua_Integer)file->getSize()); // FIXME | |
| 329 | |
| 330 // Read the data. | |
| 331 Data *data = file->read(count); | |
| 332 | |
| 333 // Error check. | |
| 334 if (data == 0) | |
| 335 return luaL_error(L, "File could not be read."); | |
| 336 | |
| 337 // Close and delete the file, if we created it. | |
| 338 // (I.e. if the first parameter is a string). | |
| 339 if (lua_isstring(L, 1)) | |
| 340 file->release(); | |
| 341 | |
| 342 // Push the string. | |
| 343 lua_pushlstring(L, (char *)data->getData(), data->getSize()); | |
| 344 | |
| 345 // Push the size. | |
| 346 lua_pushinteger(L, data->getSize()); | |
| 347 | |
| 348 // Lua has a copy now, so we can free it. | |
| 349 data->release(); | |
| 350 | |
| 351 return 2; | |
| 352 } | |
| 353 | |
| 354 int Filesystem::write(lua_State *L) | |
| 355 { | |
| 356 // The file to write to. The file must either be created | |
| 357 // on-the-fly, or passed as a parameter. | |
| 358 File *file; | |
| 359 | |
| 360 // We know for sure that we need a second parameter, so | |
| 361 // let's check that first. | |
| 362 if (lua_isnoneornil(L, 2)) | |
| 363 return luaL_error(L, "Second argument needed."); | |
| 364 | |
| 365 if (lua_isstring(L, 1)) | |
| 366 { | |
| 367 // Create the file. | |
| 368 file = newFile(lua_tostring(L, 1)); | |
| 369 } | |
| 370 else | |
| 371 return luaL_error(L, "Expected filename."); | |
| 372 | |
| 373 // Get the current mode of the file. | |
| 374 File::Mode mode = file->getMode(); | |
| 375 | |
| 376 if (mode == File::CLOSED) | |
| 377 { | |
| 378 // It should be possible to use append mode, but | |
| 379 // normal File::Mode::Write is the default. | |
| 380 int mode = luaL_optint(L, 4, File::WRITE); | |
| 381 | |
| 382 // Open the file. | |
| 383 if (!file->open((File::Mode)mode)) | |
| 384 return luaL_error(L, "Could not open file."); | |
| 385 } | |
| 386 | |
| 387 size_t length = 0; | |
| 388 const char *input; | |
| 389 if (lua_isstring(L, 2)) | |
| 390 { | |
| 391 input = lua_tolstring(L, 2, &length); | |
| 392 } | |
| 393 else if (luax_istype(L, 2, DATA_T)) | |
| 394 { | |
| 395 love::Data *data = luax_totype<love::Data>(L, 2, "Data", DATA_T); | |
| 396 length = data->getSize(); | |
| 397 input = (char *)data->getData(); | |
| 398 } | |
| 399 else | |
| 400 { | |
| 401 return luaL_error(L, "Expected string or data for argument #2."); | |
| 402 } | |
| 403 | |
| 404 // Get how much we should write. Length of string default. | |
| 405 length = luaL_optint(L, 3, length); | |
| 406 | |
| 407 // Write the data. | |
| 408 bool success = file->write(input, length); | |
| 409 | |
| 410 // Close and delete the file, if we created | |
| 411 // it in this function. | |
| 412 if (lua_isstring(L, 1)) | |
| 413 { | |
| 414 // Kill the file if "we" created it. | |
| 415 file->close(); | |
| 416 file->release(); | |
| 417 } | |
| 418 | |
| 419 if (!success) | |
| 420 return luaL_error(L, "Data could not be written."); | |
| 421 | |
| 422 lua_pushboolean(L, success); | |
| 423 return 1; | |
| 424 } | |
| 425 | |
| 426 int Filesystem::enumerate(lua_State *L) | |
| 427 { | |
| 428 int n = lua_gettop(L); | |
| 429 | |
| 430 if (n != 1) | |
| 431 return luaL_error(L, "Function requires a single parameter."); | |
| 432 | |
| 433 int type = lua_type(L, 1); | |
| 434 | |
| 435 if (type != LUA_TSTRING) | |
| 436 return luaL_error(L, "Function requires parameter of type string."); | |
| 437 | |
| 438 const char *dir = lua_tostring(L, 1); | |
| 439 char **rc = PHYSFS_enumerateFiles(dir); | |
| 440 char **i; | |
| 441 int index = 1; | |
| 442 | |
| 443 lua_newtable(L); | |
| 444 | |
| 445 for (i = rc; *i != 0; i++) | |
| 446 { | |
| 447 lua_pushinteger(L, index); | |
| 448 lua_pushstring(L, *i); | |
| 449 lua_settable(L, -3); | |
| 450 index++; | |
| 451 } | |
| 452 | |
| 453 PHYSFS_freeList(rc); | |
| 454 | |
| 455 return 1; | |
| 456 } | |
| 457 | |
| 458 int Filesystem::lines_i(lua_State *L) | |
| 459 { | |
| 460 const int bufsize = 1024; | |
| 461 char buf[bufsize]; | |
| 462 int linesize = 0; | |
| 463 bool newline = false; | |
| 464 | |
| 465 File *file = luax_checktype<File>(L, lua_upvalueindex(1), "File", FILESYSTEM_FILE_T); | |
| 466 | |
| 467 // Only accept read mode at this point. | |
| 468 if (file->getMode() != File::READ) | |
| 469 return luaL_error(L, "File needs to stay in read mode."); | |
| 470 | |
| 471 int64 pos = file->tell(); | |
| 472 int64 userpos = -1; | |
| 473 | |
| 474 if (lua_isnoneornil(L, lua_upvalueindex(2)) == 0) | |
| 475 { | |
| 476 // User may have changed the file position. | |
| 477 userpos = pos; | |
| 478 pos = (int64) lua_tonumber(L, lua_upvalueindex(2)); | |
| 479 if (userpos != pos) | |
| 480 file->seek(pos); | |
| 481 } | |
| 482 | |
| 483 while (!newline && !file->eof()) | |
| 484 { | |
| 485 // This 64-bit to 32-bit integer cast should be safe as it never exceeds bufsize. | |
| 486 int read = (int) file->read(buf, bufsize); | |
| 487 if (read < 0) | |
| 488 return luaL_error(L, "Could not read from file."); | |
| 489 | |
| 490 linesize += read; | |
| 491 | |
| 492 for (int i = 0; i < read; i++) | |
| 493 { | |
| 494 if (buf[i] == '\n') | |
| 495 { | |
| 496 linesize -= read - i; | |
| 497 newline = true; | |
| 498 break; | |
| 499 } | |
| 500 } | |
| 501 } | |
| 502 | |
| 503 if (newline || (file->eof() && linesize > 0)) | |
| 504 { | |
| 505 if (linesize < bufsize) | |
| 506 { | |
| 507 // We have the line in the buffer on the stack. No 'new' and 'read' needed. | |
| 508 lua_pushlstring(L, buf, linesize > 0 && buf[linesize - 1] == '\r' ? linesize - 1 : linesize); | |
| 509 if (userpos < 0) | |
| 510 file->seek(pos + linesize + 1); | |
| 511 } | |
| 512 else | |
| 513 { | |
| 514 char *str; | |
| 515 try | |
| 516 { | |
| 517 str = new char[linesize + 1]; | |
| 518 } | |
| 519 catch(std::bad_alloc &) | |
| 520 { | |
| 521 return luaL_error(L, "Out of memory"); | |
| 522 } | |
| 523 file->seek(pos); | |
| 524 | |
| 525 // Read the \n anyway and save us a call to seek. | |
| 526 if (file->read(str, linesize + 1) == -1) | |
| 527 { | |
| 528 delete [] str; | |
| 529 return luaL_error(L, "Could not read from file."); | |
| 530 } | |
| 531 | |
| 532 lua_pushlstring(L, str, str[linesize - 1] == '\r' ? linesize - 1 : linesize); | |
| 533 delete [] str; | |
| 534 } | |
| 535 | |
| 536 if (userpos >= 0) | |
| 537 { | |
| 538 // Save new position in upvalue. | |
| 539 lua_pushnumber(L, (lua_Number)(pos + linesize + 1)); | |
| 540 lua_replace(L, lua_upvalueindex(2)); | |
| 541 file->seek(userpos); | |
| 542 } | |
| 543 | |
| 544 return 1; | |
| 545 } | |
| 546 | |
| 547 // EOF reached. | |
| 548 if (userpos >= 0 && luax_toboolean(L, lua_upvalueindex(3))) | |
| 549 file->seek(userpos); | |
| 550 else | |
| 551 file->close(); | |
| 552 | |
| 553 return 0; | |
| 554 } | |
| 555 | |
| 556 int Filesystem::load(lua_State *L) | |
| 557 { | |
| 558 // Need only one arg. | |
| 559 luax_assert_argc(L, 1, 1); | |
| 560 | |
| 561 // Must be string. | |
| 562 if (!lua_isstring(L, -1)) | |
| 563 return luaL_error(L, "The argument must be a string."); | |
| 564 | |
| 565 std::string filename = lua_tostring(L, -1); | |
| 566 | |
| 567 // The file must exist. | |
| 568 if (!exists(filename.c_str())) | |
| 569 return luaL_error(L, "File %s does not exist.", filename.c_str()); | |
| 570 | |
| 571 // Create the file. | |
| 572 File *file = newFile(filename.c_str()); | |
| 573 | |
| 574 // Get the data from the file. | |
| 575 Data *data = file->read(); | |
| 576 | |
| 577 int status = luaL_loadbuffer(L, (const char *)data->getData(), data->getSize(), ("@" + filename).c_str()); | |
| 578 | |
| 579 data->release(); | |
| 580 file->release(); | |
| 581 | |
| 582 // Load the chunk, but don't run it. | |
| 583 switch (status) | |
| 584 { | |
| 585 case LUA_ERRMEM: | |
| 586 return luaL_error(L, "Memory allocation error: %s\n", lua_tostring(L, -1)); | |
| 587 case LUA_ERRSYNTAX: | |
| 588 return luaL_error(L, "Syntax error: %s\n", lua_tostring(L, -1)); | |
| 589 default: // success | |
| 590 return 1; | |
| 591 } | |
| 592 } | |
| 593 | |
| 594 int Filesystem::getLastModified(lua_State *L) | |
| 595 { | |
| 596 const char *filename = luaL_checkstring(L, 1); | |
| 597 PHYSFS_sint64 time = PHYSFS_getLastModTime(filename); | |
| 598 if (time == -1) | |
| 599 { | |
| 600 lua_pushnil(L); | |
| 601 lua_pushstring(L, "Could not determine file modification date."); | |
| 602 return 2; | |
| 603 } | |
| 604 lua_pushnumber(L, static_cast<lua_Number>(time)); | |
| 605 return 1; | |
| 606 } | |
| 607 | |
|
1380
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
608 int64 Filesystem::getSize(const char *filename) |
|
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
609 { |
|
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
610 File f(filename); |
|
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
611 int64 size = f.getSize(); |
|
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
612 return size; |
|
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
613 } |
|
cb7e512d785c
Added love.filesystem.getSize (issue #541)
Alex Szpakowski <slime73@gmail.com>
parents:
1282
diff
changeset
|
614 |
| 1096 | 615 } // physfs |
| 616 } // filesystem | |
| 617 } // love |
