diff --git a/CMakeLists.txt b/CMakeLists.txt index 7485832d9..3364876d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,10 +186,16 @@ source_group("modules\\event\\sdl" FILES ${LOVE_SRC_MODULE_EVENT_SDL}) # set(LOVE_SRC_MODULE_FILESYSTEM_ROOT + src/modules/filesystem/DroppedFile.cpp + src/modules/filesystem/DroppedFile.h src/modules/filesystem/File.cpp src/modules/filesystem/File.h src/modules/filesystem/FileData.cpp src/modules/filesystem/FileData.h + src/modules/filesystem/Filesystem.cpp + src/modules/filesystem/Filesystem.h + src/modules/filesystem/wrap_DroppedFile.cpp + src/modules/filesystem/wrap_DroppedFile.h src/modules/filesystem/wrap_File.cpp src/modules/filesystem/wrap_File.h src/modules/filesystem/wrap_FileData.cpp diff --git a/platform/macosx/love-Info.plist b/platform/macosx/love-Info.plist index 3dbad5b89..3be04e47f 100644 --- a/platform/macosx/love-Info.plist +++ b/platform/macosx/love-Info.plist @@ -32,6 +32,18 @@ LSHandlerRank None + + CFBundleTypeName + Document + CFBundleTypeRole + Editor + CFBundleTypeOSTypes + + **** + + CFBundleTypeIconFile + Document + CFBundleExecutable love diff --git a/platform/macosx/love-framework.xcodeproj/project.pbxproj b/platform/macosx/love-framework.xcodeproj/project.pbxproj index d40cffc25..3fadf03fa 100644 --- a/platform/macosx/love-framework.xcodeproj/project.pbxproj +++ b/platform/macosx/love-framework.xcodeproj/project.pbxproj @@ -272,7 +272,13 @@ FA636D8B171B70920065623F /* RandomGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = FA636D89171B70920065623F /* RandomGenerator.h */; }; FA636D8E171B72A70065623F /* wrap_RandomGenerator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA636D8C171B72A70065623F /* wrap_RandomGenerator.cpp */; }; FA636D8F171B72A70065623F /* wrap_RandomGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = FA636D8D171B72A70065623F /* wrap_RandomGenerator.h */; }; + FA70573C194A3FC600670B30 /* DroppedFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA70573A194A3FC600670B30 /* DroppedFile.cpp */; }; + FA70573D194A3FC600670B30 /* DroppedFile.h in Headers */ = {isa = PBXBuildFile; fileRef = FA70573B194A3FC600670B30 /* DroppedFile.h */; }; + FA705740194A55CB00670B30 /* wrap_DroppedFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA70573E194A55CB00670B30 /* wrap_DroppedFile.cpp */; }; + FA705741194A55CB00670B30 /* wrap_DroppedFile.h in Headers */ = {isa = PBXBuildFile; fileRef = FA70573F194A55CB00670B30 /* wrap_DroppedFile.h */; }; FA7175AA178E8418001FE7FE /* lua.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7175A9178E8418001FE7FE /* lua.h */; }; + FA782D0119F2F945006A3E93 /* Filesystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA782CFF19F2F945006A3E93 /* Filesystem.cpp */; }; + FA782D0219F2F945006A3E93 /* Filesystem.h in Headers */ = {isa = PBXBuildFile; fileRef = FA782D0019F2F945006A3E93 /* Filesystem.h */; }; FA7AA59217F6AC1F00704BE2 /* wrap_Mesh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7AA59017F6AC1F00704BE2 /* wrap_Mesh.cpp */; }; FA7AA59317F6AC1F00704BE2 /* wrap_Mesh.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7AA59117F6AC1F00704BE2 /* wrap_Mesh.h */; }; FA7C937A16DCC6C2006F2BEE /* wrap_Math.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7C937516DCC6C2006F2BEE /* wrap_Math.cpp */; }; @@ -843,7 +849,13 @@ FA636D89171B70920065623F /* RandomGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RandomGenerator.h; sourceTree = ""; }; FA636D8C171B72A70065623F /* wrap_RandomGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_RandomGenerator.cpp; sourceTree = ""; }; FA636D8D171B72A70065623F /* wrap_RandomGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_RandomGenerator.h; sourceTree = ""; }; + FA70573A194A3FC600670B30 /* DroppedFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DroppedFile.cpp; sourceTree = ""; }; + FA70573B194A3FC600670B30 /* DroppedFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DroppedFile.h; sourceTree = ""; }; + FA70573E194A55CB00670B30 /* wrap_DroppedFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_DroppedFile.cpp; sourceTree = ""; }; + FA70573F194A55CB00670B30 /* wrap_DroppedFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_DroppedFile.h; sourceTree = ""; }; FA7175A9178E8418001FE7FE /* lua.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lua.h; sourceTree = ""; }; + FA782CFF19F2F945006A3E93 /* Filesystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Filesystem.cpp; sourceTree = ""; }; + FA782D0019F2F945006A3E93 /* Filesystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Filesystem.h; sourceTree = ""; }; FA7AA59017F6AC1F00704BE2 /* wrap_Mesh.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Mesh.cpp; sourceTree = ""; }; FA7AA59117F6AC1F00704BE2 /* wrap_Mesh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Mesh.h; sourceTree = ""; }; FA7C937516DCC6C2006F2BEE /* wrap_Math.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Math.cpp; sourceTree = ""; }; @@ -1757,11 +1769,17 @@ 7B0734182055607468261A5E /* filesystem */ = { isa = PBXGroup; children = ( + FA70573A194A3FC600670B30 /* DroppedFile.cpp */, + FA70573B194A3FC600670B30 /* DroppedFile.h */, 11D141087979064B441B787D /* File.cpp */, 31A444CF0B4E6DA450120730 /* File.h */, 62370A494F9D6E2D570065EB /* FileData.cpp */, 54A13C2209F945671BC27974 /* FileData.h */, + FA782CFF19F2F945006A3E93 /* Filesystem.cpp */, + FA782D0019F2F945006A3E93 /* Filesystem.h */, 64DD03B45BF6265723662DAF /* physfs */, + FA70573E194A55CB00670B30 /* wrap_DroppedFile.cpp */, + FA70573F194A55CB00670B30 /* wrap_DroppedFile.h */, 6C367AE309C453C412D91363 /* wrap_File.cpp */, 52E15B702C40593D3BF431DF /* wrap_File.h */, 597478A255B82B56488B4717 /* wrap_FileData.cpp */, @@ -2044,6 +2062,7 @@ FAE010DD170DDE99006F29D0 /* ddsparse.h in Headers */, FAE010E1170DE25E006F29D0 /* ddsHandler.h in Headers */, FAE010E5170DF75C006F29D0 /* wrap_CompressedData.h in Headers */, + FA705741194A55CB00670B30 /* wrap_DroppedFile.h in Headers */, FAF6704E18184FF800DBDEEA /* wuff_config.h in Headers */, FA0CDE3D1710F9A50056E8D7 /* FormatHandler.h in Headers */, FA7AA59317F6AC1F00704BE2 /* wrap_Mesh.h in Headers */, @@ -2088,8 +2107,10 @@ FA34E7CE174B513F00E04D3F /* System.h in Headers */, FA34E7D3174B515D00E04D3F /* System.h in Headers */, FAF670571818501300DBDEEA /* WaveDecoder.h in Headers */, + FA70573D194A3FC600670B30 /* DroppedFile.h in Headers */, FA1EF7C51799FEB200FF380C /* wrap_System.h in Headers */, FA435EA417B36E9C004C3F22 /* Polyline.h in Headers */, + FA782D0219F2F945006A3E93 /* Filesystem.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2283,6 +2304,7 @@ FA08F62C16C7541400F007B5 /* wrap_Shader.cpp in Sources */, FA0A4BFA182E26F500E1E4D2 /* wrap_MotorJoint.cpp in Sources */, FA08F62D16C7541400F007B5 /* wrap_SpriteBatch.cpp in Sources */, + FA782D0119F2F945006A3E93 /* Filesystem.cpp in Sources */, FA08F62E16C7542600F007B5 /* ImageData.cpp in Sources */, FA08F62F16C7542600F007B5 /* Image.cpp in Sources */, FA08F63116C7542600F007B5 /* wrap_Image.cpp in Sources */, @@ -2351,10 +2373,12 @@ FA08F66D16C7549200F007B5 /* wrap_Sound.cpp in Sources */, FA08F66E16C7549200F007B5 /* wrap_SoundData.cpp in Sources */, FA08F66F16C754A100F007B5 /* Decoder.cpp in Sources */, + FA70573C194A3FC600670B30 /* DroppedFile.cpp in Sources */, FA08F67016C754A100F007B5 /* FLACDecoder.cpp in Sources */, FA08F67116C754A100F007B5 /* GmeDecoder.cpp in Sources */, FA08F67216C754A100F007B5 /* ModPlugDecoder.cpp in Sources */, FA08F67316C754A100F007B5 /* Mpg123Decoder.cpp in Sources */, + FA705740194A55CB00670B30 /* wrap_DroppedFile.cpp in Sources */, FA08F67416C754A100F007B5 /* Sound.cpp in Sources */, FA08F67516C754A100F007B5 /* VorbisDecoder.cpp in Sources */, FA08F67716C754A900F007B5 /* threads.cpp in Sources */, diff --git a/src/common/runtime.cpp b/src/common/runtime.cpp index f152e4df4..e26c4d1e2 100644 --- a/src/common/runtime.cpp +++ b/src/common/runtime.cpp @@ -634,6 +634,7 @@ StringMap::Entry typeEntries[] = // Filesystem {"File", FILESYSTEM_FILE_ID}, + {"DroppedFile", FILESYSTEM_DROPPED_FILE_ID}, {"FileData", FILESYSTEM_FILE_DATA_ID}, // Font diff --git a/src/common/types.h b/src/common/types.h index e9319ea58..016cfa41f 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -37,6 +37,7 @@ enum Type // Filesystem. FILESYSTEM_FILE_ID, + FILESYSTEM_DROPPED_FILE_ID, FILESYSTEM_FILE_DATA_ID, // Font @@ -123,6 +124,7 @@ const bits MODULE_T = (bits(1) << MODULE_ID) | OBJECT_T; // Filesystem. const bits FILESYSTEM_FILE_T = (bits(1) << FILESYSTEM_FILE_ID) | OBJECT_T; +const bits FILESYSTEM_DROPPED_FILE_T = (bits(1) << FILESYSTEM_DROPPED_FILE_ID) | FILESYSTEM_FILE_T; const bits FILESYSTEM_FILE_DATA_T = (bits(1) << FILESYSTEM_FILE_DATA_ID) | DATA_T; const bits FONT_GLYPH_DATA_T = (bits(1) << FONT_GLYPH_DATA_ID) | DATA_T; diff --git a/src/modules/event/sdl/Event.cpp b/src/modules/event/sdl/Event.cpp index bae71cfc7..5d98924f4 100644 --- a/src/modules/event/sdl/Event.cpp +++ b/src/modules/event/sdl/Event.cpp @@ -20,6 +20,8 @@ #include "Event.h" +#include "filesystem/DroppedFile.h" +#include "filesystem/Filesystem.h" #include "keyboard/Keyboard.h" #include "mouse/Mouse.h" #include "joystick/JoystickModule.h" @@ -108,7 +110,8 @@ Message *Event::convert(const SDL_Event &e) const { Message *msg = NULL; - love::keyboard::Keyboard *kb = 0; + love::keyboard::Keyboard *kb = nullptr; + love::filesystem::Filesystem *filesystem = nullptr; love::keyboard::Keyboard::Key key; love::mouse::Mouse::Button button; @@ -210,6 +213,29 @@ Message *Event::convert(const SDL_Event &e) const msg = convertWindowEvent(e); break; case SDL_DROPFILE: + filesystem = Module::getInstance(Module::M_FILESYSTEM); + if (filesystem != nullptr) + { + // Allow mounting any dropped path, so zips or dirs can be mounted. + filesystem->allowMountingForPath(e.drop.file); + + if (filesystem->isRealDirectory(e.drop.file)) + { + arg1 = new Variant(e.drop.file, strlen(e.drop.file)); + msg = new Message("directorydropped", arg1); + arg1->release(); + } + else + { + Proxy proxy; + proxy.data = new love::filesystem::DroppedFile(e.drop.file); + proxy.flags = FILESYSTEM_DROPPED_FILE_T; + arg1 = new Variant(FILESYSTEM_DROPPED_FILE_ID, &proxy); + msg = new Message("filedropped", arg1); + arg1->release(); + ((Object *) proxy.data)->release(); + } + } SDL_free(e.drop.file); break; case SDL_QUIT: diff --git a/src/modules/filesystem/DroppedFile.cpp b/src/modules/filesystem/DroppedFile.cpp new file mode 100644 index 000000000..f83df867d --- /dev/null +++ b/src/modules/filesystem/DroppedFile.cpp @@ -0,0 +1,252 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +// LOVE +#include "DroppedFile.h" +#include "common/utf8.h" + +// Assume POSIX or Visual Studio. +#include +#include + +#ifdef LOVE_WINDOWS +#include +#else +#include // POSIX. +#endif + +namespace love +{ +namespace filesystem +{ + +DroppedFile::DroppedFile(const std::string &filename) + : filename(filename) + , file(nullptr) + , mode(MODE_CLOSED) + , bufferMode(BUFFER_NONE) + , bufferSize(0) +{ +} + +DroppedFile::~DroppedFile() +{ + if (mode != MODE_CLOSED) + close(); +} + +bool DroppedFile::open(Mode newmode) +{ + if (newmode == MODE_CLOSED) + return true; + + // File already open? + if (file != nullptr) + return false; + +#ifdef LOVE_WINDOWS + // make sure non-ASCII filenames work. + std::wstring modestr = to_widestr(getModeString(newmode)); + file = _wfopen(to_widestr(filename).c_str(), modestr.c_str()); +#else + file = fopen(filename.c_str(), getModeString(newmode)); +#endif + + if (newmode == MODE_READ && file == nullptr) + throw love::Exception("Could not open file %s. Does not exist.", filename.c_str()); + + mode = newmode; + + if (file != nullptr && !setBuffer(bufferMode, bufferSize)) + { + // Revert to buffer defaults if we don't successfully set the buffer. + bufferMode = BUFFER_NONE; + bufferSize = 0; + } + + return file != nullptr; +} + +bool DroppedFile::close() +{ + if (file == nullptr || fclose(file) != 0) + return false; + + mode = MODE_CLOSED; + file = nullptr; + + return true; +} + +bool DroppedFile::isOpen() const +{ + return mode != MODE_CLOSED && file != nullptr; +} + +int64 DroppedFile::getSize() +{ +#ifdef LOVE_WINDOWS + + // make sure non-ASCII filenames work. + struct _stat buf; + if (_wstat(to_widestr(filename).c_str(), &buf) != 0) + return -1; + + return (int64) buf.st_size; + +#else + + // Assume POSIX support... + struct stat buf; + if (stat(filename.c_str(), &buf) != 0) + return -1; + + return (int64) buf.st_size; + +#endif +} + +int64 DroppedFile::read(void *dst, int64 size) +{ + if (!file || mode != MODE_READ) + throw love::Exception("File is not opened for reading."); + + if (size < 0) + throw love::Exception("Invalid read size."); + + size_t read = fread(dst, 1, (size_t) size, file); + + return (int64) read; +} + +bool DroppedFile::write(const void *data, int64 size) +{ + if (!file || (mode != MODE_WRITE && mode != MODE_APPEND)) + throw love::Exception("File is not opened for writing."); + + if (size < 0) + throw love::Exception("Invalid write size."); + + int64 written = (int64) fwrite(data, 1, (size_t) size, file); + + return written == size; +} + +bool DroppedFile::flush() +{ + if (!file || (mode != MODE_WRITE && mode != MODE_APPEND)) + throw love::Exception("File is not opened for writing."); + + return fflush(file) == 0; +} + +bool DroppedFile::eof() +{ + return file == nullptr || feof(file) != 0; +} + +int64 DroppedFile::tell() +{ + if (file == nullptr) + return -1; + + return (int64) ftell(file); +} + +bool DroppedFile::seek(uint64 pos) +{ + return file != nullptr && fseek(file, (long) pos, SEEK_SET) == 0; +} + +bool DroppedFile::setBuffer(BufferMode bufmode, int64 size) +{ + if (size < 0) + return false; + + if (bufmode == BUFFER_NONE) + size = 0; + + // If the file isn't open, we'll make sure the buffer values are set in + // DroppedFile::open. + if (!isOpen()) + { + bufferMode = bufmode; + bufferSize = size; + return true; + } + + int vbufmode; + switch (bufmode) + { + case File::BUFFER_NONE: + default: + vbufmode = _IONBF; + break; + case File::BUFFER_LINE: + vbufmode = _IOLBF; + break; + case File::BUFFER_FULL: + vbufmode = _IOFBF; + break; + } + + if (setvbuf(file, nullptr, vbufmode, (size_t) size) != 0) + return false; + + bufferMode = bufmode; + bufferSize = size; + + return true; +} + +File::BufferMode DroppedFile::getBuffer(int64 &size) const +{ + size = bufferSize; + return bufferMode; +} + +const std::string &DroppedFile::getFilename() const +{ + return filename; +} + +File::Mode DroppedFile::getMode() const +{ + return mode; +} + +const char *DroppedFile::getModeString(Mode mode) +{ + switch (mode) + { + case File::MODE_CLOSED: + default: + return "c"; + case File::MODE_READ: + return "rb"; + case File::MODE_WRITE: + return "wb"; + case File::MODE_APPEND: + return "ab"; + } +} + +} // filesystem +} // love diff --git a/src/modules/filesystem/DroppedFile.h b/src/modules/filesystem/DroppedFile.h new file mode 100644 index 000000000..50cacda83 --- /dev/null +++ b/src/modules/filesystem/DroppedFile.h @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#ifndef LOVE_FILESYSTEM_DROPPED_FILE_H +#define LOVE_FILESYSTEM_DROPPED_FILE_H + +// LOVE +#include "common/config.h" +#include "File.h" + +// C +#include + +namespace love +{ +namespace filesystem +{ + +/** + * File which is created when a user drags and drops an actual file onto the + * LOVE game. Uses C's stdio. Filenames are system-dependent full paths. + **/ +class DroppedFile : public File +{ +public: + + DroppedFile(const std::string &filename); + virtual ~DroppedFile(); + + // Implements File. + using File::read; + using File::write; + bool open(Mode mode); + bool close(); + bool isOpen() const; + int64 getSize(); + int64 read(void *dst, int64 size); + bool write(const void *data, int64 size); + bool flush(); + bool eof(); + int64 tell(); + bool seek(uint64 pos); + bool setBuffer(BufferMode bufmode, int64 size); + BufferMode getBuffer(int64 &size) const; + Mode getMode() const; + const std::string &getFilename() const; + +private: + + static const char *getModeString(Mode mode); + + std::string filename; + + FILE *file; + + Mode mode; + + BufferMode bufferMode; + int64 bufferSize; + +}; // DroppedFile + +} // filesystem +} // love + +#endif // LOVE_FILESYSTEM_DROPPED_FILE_H diff --git a/src/modules/filesystem/File.cpp b/src/modules/filesystem/File.cpp index ef20621d7..105e52f95 100644 --- a/src/modules/filesystem/File.cpp +++ b/src/modules/filesystem/File.cpp @@ -29,6 +29,68 @@ File::~File() { } +FileData *File::read(int64 size) +{ + bool isopen = isOpen(); + + if (!isopen && !open(MODE_READ)) + throw love::Exception("Could not read file %s.", getFilename().c_str()); + + int64 max = getSize(); + int64 cur = tell(); + size = (size == ALL) ? max : size; + + if (size < 0) + throw love::Exception("Invalid read size."); + + // Clamping because the file offset may be in a weird position. + if (cur < 0) + cur = 0; + else if (cur > max) + cur = max; + + if (cur + size > max) + size = max - cur; + + FileData *fileData = new FileData(size, getFilename()); + int64 bytesRead = read(fileData->getData(), size); + + if (bytesRead < 0 || (bytesRead == 0 && bytesRead != size)) + { + delete fileData; + throw love::Exception("Could not read from file."); + } + + if (bytesRead < size) + { + FileData *tmpFileData = new FileData(bytesRead, getFilename()); + memcpy(tmpFileData->getData(), fileData->getData(), (size_t) bytesRead); + fileData->release(); + fileData = tmpFileData; + } + + if (!isopen) + close(); + + return fileData; +} + +bool File::write(const Data *data, int64 size) +{ + return write(data->getData(), (size == ALL) ? data->getSize() : size); +} + +std::string File::getExtension() const +{ + const std::string &filename = getFilename(); + std::string::size_type idx = filename.rfind('.'); + + if (idx != std::string::npos) + return filename.substr(idx+1); + else + return std::string(); +} + bool File::getConstant(const char *in, Mode &out) { return modes.find(in, out); @@ -51,10 +113,10 @@ bool File::getConstant(BufferMode in, const char *&out) StringMap::Entry File::modeEntries[] = { - {"c", File::CLOSED}, - {"r", File::READ}, - {"w", File::WRITE}, - {"a", File::APPEND}, + {"c", File::MODE_CLOSED}, + {"r", File::MODE_READ}, + {"w", File::MODE_WRITE}, + {"a", File::MODE_APPEND}, }; StringMap File::modes(File::modeEntries, sizeof(File::modeEntries)); diff --git a/src/modules/filesystem/File.h b/src/modules/filesystem/File.h index e0be859e4..d46e13727 100644 --- a/src/modules/filesystem/File.h +++ b/src/modules/filesystem/File.h @@ -49,10 +49,10 @@ public: **/ enum Mode { - CLOSED, - READ, - WRITE, - APPEND, + MODE_CLOSED, + MODE_READ, + MODE_WRITE, + MODE_APPEND, MODE_MAX_ENUM }; @@ -77,7 +77,7 @@ public: /** * Opens the file in a certain mode. * - * @param mode READ, WRITE, APPEND. + * @param mode MODE_READ, MODE_WRITE, MODE_APPEND. * @return True if successful, false otherwise. **/ virtual bool open(Mode mode) = 0; @@ -107,7 +107,7 @@ public: * @param size The number of bytes to attempt reading, or -1 for EOF. * @return A newly allocated Data object. **/ - virtual FileData *read(int64 size = ALL) = 0; + virtual FileData *read(int64 size = ALL); /** * Reads data into the destination buffer. @@ -134,7 +134,7 @@ public: * @param size The number of bytes to attempt writing, or -1 for everything. * @return True of success, false otherwise. **/ - virtual bool write(const Data *data, int64 size = ALL) = 0; + virtual bool write(const Data *data, int64 size = ALL); /** * Flushes the currently buffered file data to disk. Only applicable in @@ -192,13 +192,13 @@ public: * Gets the filename for this File, or empty string if none. * @return The filename for this File. **/ - virtual std::string getFilename() const = 0; + virtual const std::string &getFilename() const = 0; /** * Gets the file extension for this File, or empty string if none. * @return The file extension for this File (without the dot). **/ - virtual std::string getExtension() const = 0; + virtual std::string getExtension() const; static bool getConstant(const char *in, Mode &out); static bool getConstant(Mode in, const char *&out); diff --git a/src/modules/filesystem/Filesystem.cpp b/src/modules/filesystem/Filesystem.cpp new file mode 100644 index 000000000..4e5a538c1 --- /dev/null +++ b/src/modules/filesystem/Filesystem.cpp @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +// LOVE +#include "Filesystem.h" +#include "common/utf8.h" + +// Assume POSIX or Visual Studio. +#include +#include + +namespace love +{ +namespace filesystem +{ + +Filesystem::Filesystem() +{ +} + +Filesystem::~Filesystem() +{ +} + +bool Filesystem::isRealDirectory(const std::string &path) const +{ +#ifdef LOVE_WINDOWS + // make sure non-ASCII paths work. + struct _stat buf; + if (_wstat(to_widestr(path).c_str(), &buf) != 0) + return false; + + return (buf.st_mode & _S_IFDIR) == _S_IFDIR; +#else + // Assume POSIX support... + struct stat buf; + if (stat(path.c_str(), &buf) != 0) + return false; + + return S_ISDIR(buf.st_mode) != 0; +#endif +} + +} // filesystem +} // love diff --git a/src/modules/filesystem/Filesystem.h b/src/modules/filesystem/Filesystem.h new file mode 100644 index 000000000..bba774472 --- /dev/null +++ b/src/modules/filesystem/Filesystem.h @@ -0,0 +1,261 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#ifndef LOVE_FILESYSTEM_FILESYSTEM_H +#define LOVE_FILESYSTEM_FILESYSTEM_H + +// LOVE +#include "common/config.h" +#include "common/runtime.h" +#include "common/Module.h" +#include "common/int.h" +#include "FileData.h" +#include "File.h" + +// C++ +#include +#include + +// In Windows, we would like to use "LOVE" as the +// application folder, but in Linux, we like .love. +#define LOVE_APPDATA_PREFIX "" +#ifdef LOVE_WINDOWS +# define LOVE_APPDATA_FOLDER "LOVE" +# define LOVE_PATH_SEPARATOR "/" +# define LOVE_MAX_PATH _MAX_PATH +#else +# ifdef LOVE_MACOSX +# define LOVE_APPDATA_FOLDER "LOVE" +# elif defined(LOVE_LINUX) +# define LOVE_APPDATA_FOLDER "love" +# else +# define LOVE_APPDATA_PREFIX "." +# define LOVE_APPDATA_FOLDER "love" +# endif +# define LOVE_PATH_SEPARATOR "/" +# define LOVE_MAX_PATH MAXPATHLEN +#endif + +namespace love +{ +namespace filesystem +{ + +class Filesystem : public Module +{ +public: + + Filesystem(); + virtual ~Filesystem(); + + // Implements Module. + virtual ModuleType getModuleType() const { return M_FILESYSTEM; } + + virtual void init(const char *arg0) = 0; + + virtual void setFused(bool fused) = 0; + virtual bool isFused() const = 0; + + /** + * This sets up the save directory. If the + * it is already set up, nothing happens. + * @return True on success, false otherwise. + **/ + virtual bool setupWriteDirectory() = 0; + + /** + * Sets the name of the save folder. + * @param ident The name of the game. Will be used to + * to create the folder in the LOVE data folder. + **/ + virtual bool setIdentity(const char *ident, bool appendToPath = false) = 0; + virtual const char *getIdentity() const = 0; + + /** + * Sets the path to the game source. + * This can only be set once. + * @param source Path to a directory or a .love-file. + **/ + virtual bool setSource(const char *source) = 0; + + /** + * Gets the path to the game source. + * Returns a 0-length string if the source has not been set. + **/ + virtual const char *getSource() const = 0; + + virtual bool mount(const char *archive, const char *mountpoint, bool appendToPath = false) = 0; + virtual bool unmount(const char *archive) = 0; + + /** + * Creates a new file. + **/ + virtual File *newFile(const char *filename) const = 0; + + /** + * Creates a new FileData object. Data will be copied. + * @param data Pointer to the data. + * @param size The size of the data. + * @param filename The full filename used to file type identification. + **/ + virtual FileData *newFileData(void *data, unsigned int size, const char *filename) const = 0; + + /** + * Creates a new FileData object from base64 data. + * @param b64 The base64 data. + **/ + virtual FileData *newFileData(const char *b64, const char *filename) const = 0; + + /** + * Gets the current working directory. + **/ + virtual const char *getWorkingDirectory() = 0; + + /** + * Gets the user home directory. + **/ + virtual std::string getUserDirectory() = 0; + + /** + * Gets the APPDATA directory. On Windows, this is the folder + * in the %APPDATA% enviroment variable. On Linux, this is the + * user home folder. + **/ + virtual std::string getAppdataDirectory() = 0; + + /** + * Gets the full path of the save folder. + **/ + virtual const char *getSaveDirectory() = 0; + + /** + * Gets the full path to the directory containing the game source. + * For example if the game source is C:\Games\mygame.love, this will return + * C:\Games. + **/ + virtual std::string getSourceBaseDirectory() const = 0; + + /** + * Gets the real directory path containing the file. + **/ + virtual std::string getRealDirectory(const char *filename) const = 0; + + /** + * Checks if a path is a directory. + * @param dir The directory name to check. + **/ + virtual bool isDirectory(const char *dir) const = 0; + + /** + * Checks if a filename exists. + * @param file The filename to check. + **/ + virtual bool isFile(const char *file) const = 0; + + /** + * Creates a directory. Write dir must be set. + * @param dir The directory to create. + **/ + virtual bool createDirectory(const char *dir) = 0; + + /** + * Removes a file (or directory). + * @param file The file or directory to remove. + **/ + virtual bool remove(const char *file) = 0; + + /** + * Reads data from a file. + * @param filename The name of the file to read from. + * @param size The size in bytes of the data to read. + **/ + virtual FileData *read(const char *filename, int64 size = File::ALL) const = 0; + + /** + * Write data to a file. + * @param filename The name of the file to write to. + * @param data The data to write. + * @param size The size in bytes of the data to write. + **/ + virtual void write(const char *filename, const void *data, int64 size) const = 0; + + /** + * Append data to a file, creating it if it doesn't exist. + * @param filename The name of the file to write to. + * @param data The data to append. + * @param size The size in bytes of the data to append. + **/ + virtual void append(const char *filename, const void *data, int64 size) const = 0; + + /** + * This "native" method returns a table of all + * files in a given directory. + **/ + virtual int getDirectoryItems(lua_State *L) = 0; + + /** + * Gets the last modification time of a file, in seconds + * since the Unix epoch. + * @param filename The name of the file. + **/ + virtual int64 getLastModified(const char *filename) const = 0; + + /** + * Gets the size of a file in bytes. + * @param filename The name of the file. + **/ + virtual int64 getSize(const char *filename) const = 0; + + /** + * Enable or disable symbolic link support in love.filesystem. + **/ + virtual void setSymlinksEnabled(bool enable) = 0; + + /** + * Gets whether symbolic link support is enabled. + **/ + virtual bool areSymlinksEnabled() const = 0; + + /** + * Gets whether a filepath is actually a symlink. + * Always returns false if symlinks are not enabled. + **/ + virtual bool isSymlink(const char *filename) const = 0; + + // Require path accessors + // Not const because it's R/W + virtual std::vector &getRequirePath() = 0; + + /** + * Allows a full (OS-dependent) path to be used with Filesystem::mount. + **/ + virtual void allowMountingForPath(const std::string &path) = 0; + + /** + * Gets whether the given full (OS-dependent) path is a directory. + **/ + virtual bool isRealDirectory(const std::string &path) const; + +}; // Filesystem + +} // filesystem +} // love + +#endif // LOVE_FILESYSTEM_FILESYSTEM_H diff --git a/src/modules/filesystem/physfs/File.cpp b/src/modules/filesystem/physfs/File.cpp index be137c4b0..dbec8c6eb 100644 --- a/src/modules/filesystem/physfs/File.cpp +++ b/src/modules/filesystem/physfs/File.cpp @@ -39,8 +39,8 @@ namespace physfs File::File(const std::string &filename) : filename(filename) - , file(0) - , mode(CLOSED) + , file(nullptr) + , mode(MODE_CLOSED) , bufferMode(BUFFER_NONE) , bufferSize(0) { @@ -48,25 +48,25 @@ File::File(const std::string &filename) File::~File() { - if (mode != CLOSED) + if (mode != MODE_CLOSED) close(); } bool File::open(Mode mode) { - if (mode == CLOSED) + if (mode == MODE_CLOSED) return true; // File must exist if read mode. - if ((mode == READ) && !PHYSFS_exists(filename.c_str())) + if ((mode == MODE_READ) && !PHYSFS_exists(filename.c_str())) throw love::Exception("Could not open file %s. Does not exist.", filename.c_str()); // Check whether the write directory is set. - if ((mode == APPEND || mode == WRITE) && (PHYSFS_getWriteDir() == 0) && !hack_setupWriteDirectory()) + if ((mode == MODE_APPEND || mode == MODE_WRITE) && (PHYSFS_getWriteDir() == 0) && !hack_setupWriteDirectory()) throw love::Exception("Could not set write directory."); // File already open? - if (file != 0) + if (file != nullptr) return false; PHYSFS_getLastError(); // Clear the error buffer. @@ -74,13 +74,13 @@ bool File::open(Mode mode) switch (mode) { - case READ: + case MODE_READ: handle = PHYSFS_openRead(filename.c_str()); break; - case APPEND: + case MODE_APPEND: handle = PHYSFS_openAppend(filename.c_str()); break; - case WRITE: + case MODE_WRITE: handle = PHYSFS_openWrite(filename.c_str()); break; default: @@ -99,94 +99,50 @@ bool File::open(Mode mode) this->mode = mode; - if (file != 0 && !setBuffer(bufferMode, bufferSize)) + if (file != nullptr && !setBuffer(bufferMode, bufferSize)) { // Revert to buffer defaults if we don't successfully set the buffer. bufferMode = BUFFER_NONE; bufferSize = 0; } - return (file != 0); + return (file != nullptr); } bool File::close() { - if (!PHYSFS_close(file)) + if (file == nullptr || !PHYSFS_close(file)) return false; - mode = CLOSED; - file = 0; + + mode = MODE_CLOSED; + file = nullptr; + return true; } bool File::isOpen() const { - return mode != CLOSED && file != 0; + return mode != MODE_CLOSED && file != nullptr; } int64 File::getSize() { // If the file is closed, open it to // check the size. - if (file == 0) + if (file == nullptr) { - open(READ); - int64 size = (int64)PHYSFS_fileLength(file); + open(MODE_READ); + int64 size = (int64) PHYSFS_fileLength(file); close(); return size; } - return (int64)PHYSFS_fileLength(file); -} - - -FileData *File::read(int64 size) -{ - bool isOpen = (file != 0); - - if (!isOpen && !open(READ)) - throw love::Exception("Could not read file %s.", filename.c_str()); - - int64 max = getSize(); - int64 cur = tell(); - size = (size == ALL) ? max : size; - - if (size < 0) - throw love::Exception("Invalid read size."); - - // Clamping because the file offset may be in a weird position. - if (cur < 0) - cur = 0; - else if (cur > max) - cur = max; - - if (cur + size > max) - size = max - cur; - - FileData *fileData = new FileData(size, getFilename()); - int64 bytesRead = read(fileData->getData(), size); - - if (bytesRead < 0 || (bytesRead == 0 && bytesRead != size)) - { - delete fileData; - throw love::Exception("Could not read from file."); - } - if (bytesRead < size) - { - FileData *tmpFileData = new FileData(bytesRead, getFilename()); - memcpy(tmpFileData->getData(), fileData->getData(), (size_t) bytesRead); - delete fileData; - fileData = tmpFileData; - } - - if (!isOpen) - close(); - - return fileData; + return (int64) PHYSFS_fileLength(file); } int64 File::read(void *dst, int64 size) { - if (!file || mode != READ) + if (!file || mode != MODE_READ) throw love::Exception("File is not opened for reading."); int64 max = (int64)PHYSFS_fileLength(file); @@ -205,7 +161,7 @@ int64 File::read(void *dst, int64 size) bool File::write(const void *data, int64 size) { - if (!file || (mode != WRITE && mode != APPEND)) + if (!file || (mode != MODE_WRITE && mode != MODE_APPEND)) throw love::Exception("File is not opened for writing."); // Another clamp, for the time being. @@ -215,7 +171,7 @@ bool File::write(const void *data, int64 size) throw love::Exception("Invalid write size."); // Try to write. - int64 written = static_cast(PHYSFS_write(file, data, 1, (PHYSFS_uint32) size)); + int64 written = (int64) PHYSFS_write(file, data, 1, (PHYSFS_uint32) size); // Check that correct amount of data was written. if (written != size) @@ -231,14 +187,9 @@ bool File::write(const void *data, int64 size) return true; } -bool File::write(const Data *data, int64 size) -{ - return write(data->getData(), (size == ALL) ? data->getSize() : size); -} - bool File::flush() { - if (!file || (mode != WRITE && mode != APPEND)) + if (!file || (mode != MODE_WRITE && mode != MODE_APPEND)) throw love::Exception("File is not opened for writing."); return PHYSFS_flush(file) != 0; @@ -263,14 +214,12 @@ inline bool test_eof(File *, PHYSFS_File *file) bool File::eof() { - if (file == 0 || test_eof(this, file)) - return true; - return false; + return file == nullptr || test_eof(this, file); } int64 File::tell() { - if (file == 0) + if (file == nullptr) return -1; return (int64) PHYSFS_tell(file); @@ -278,23 +227,17 @@ int64 File::tell() bool File::seek(uint64 pos) { - if (file == 0) - return false; - - if (!PHYSFS_seek(file, (PHYSFS_uint64) pos)) - return false; - return true; + return file != nullptr && PHYSFS_seek(file, (PHYSFS_uint64) pos) != 0; } bool File::setBuffer(BufferMode bufmode, int64 size) { - // No negativity allowed! if (size < 0) return false; // If the file isn't open, we'll make sure the buffer values are set in // File::open. - if (file == 0 || mode == CLOSED) + if (!isOpen()) { bufferMode = bufmode; bufferSize = size; @@ -331,21 +274,11 @@ File::BufferMode File::getBuffer(int64 &size) const return bufferMode; } -std::string File::getFilename() const +const std::string &File::getFilename() const { return filename; } -std::string File::getExtension() const -{ - std::string::size_type idx = filename.rfind('.'); - - if (idx != std::string::npos) - return filename.substr(idx+1); - else - return std::string(); -} - filesystem::File::Mode File::getMode() const { return mode; diff --git a/src/modules/filesystem/physfs/File.h b/src/modules/filesystem/physfs/File.h index a3fe33792..13cd26925 100644 --- a/src/modules/filesystem/physfs/File.h +++ b/src/modules/filesystem/physfs/File.h @@ -54,14 +54,14 @@ public: virtual ~File(); // Implements love::filesystem::File. + using love::filesystem::File::read; + using love::filesystem::File::write; bool open(Mode mode); bool close(); bool isOpen() const; int64 getSize(); - FileData *read(int64 size = ALL); - int64 read(void *dst, int64 size); + virtual int64 read(void *dst, int64 size); bool write(const void *data, int64 size); - bool write(const Data *data, int64 size = ALL); bool flush(); bool eof(); int64 tell(); @@ -69,8 +69,7 @@ public: bool setBuffer(BufferMode bufmode, int64 size); BufferMode getBuffer(int64 &size) const; Mode getMode() const; - std::string getFilename() const; - std::string getExtension() const; + const std::string &getFilename() const; private: diff --git a/src/modules/filesystem/physfs/Filesystem.cpp b/src/modules/filesystem/physfs/Filesystem.cpp index cf395aec7..f51807d33 100644 --- a/src/modules/filesystem/physfs/Filesystem.cpp +++ b/src/modules/filesystem/physfs/Filesystem.cpp @@ -18,8 +18,6 @@ * 3. This notice may not be removed or altered from any source distribution. **/ -#include "common/config.h" - #include #include @@ -27,6 +25,25 @@ #include "common/b64.h" #include "Filesystem.h" +#include "File.h" + +// PhysFS +#ifdef LOVE_MACOSX_USE_FRAMEWORKS +#include +#else +#include +#endif + +// For great CWD. (Current Working Directory) +// Using this instead of boost::filesystem which totally +// cramped our style. +#ifdef LOVE_WINDOWS +# include +# include +#else +# include +# include +#endif namespace { @@ -73,8 +90,7 @@ namespace physfs { Filesystem::Filesystem() - : initialized(false) - , fused(false) + : fused(false) , fusedSet(false) { requirePath = {"?.lua", "?/init.lua"}; @@ -82,7 +98,7 @@ Filesystem::Filesystem() Filesystem::~Filesystem() { - if (initialized) + if (PHYSFS_isInit()) PHYSFS_deinit(); } @@ -95,7 +111,6 @@ void Filesystem::init(const char *arg0) { if (!PHYSFS_init(arg0)) throw Exception(PHYSFS_getLastError()); - initialized = true; } void Filesystem::setFused(bool fused) @@ -115,7 +130,7 @@ bool Filesystem::isFused() const bool Filesystem::setIdentity(const char *ident, bool appendToPath) { - if (!initialized) + if (!PHYSFS_isInit()) return false; std::string old_save_path = save_path_full; @@ -164,7 +179,7 @@ const char *Filesystem::getIdentity() const bool Filesystem::setSource(const char *source) { - if (!initialized) + if (!PHYSFS_isInit()) return false; // Check whether directory is already set. @@ -188,7 +203,7 @@ const char *Filesystem::getSource() const bool Filesystem::setupWriteDirectory() { - if (!initialized) + if (!PHYSFS_isInit()) return false; // These must all be set. @@ -244,13 +259,20 @@ bool Filesystem::setupWriteDirectory() bool Filesystem::mount(const char *archive, const char *mountpoint, bool appendToPath) { - if (!initialized || !archive) + if (!PHYSFS_isInit() || !archive) return false; std::string realPath; std::string sourceBase = getSourceBaseDirectory(); - if (isFused() && sourceBase.compare(archive) == 0) + // Check whether the given archive path is in the list of allowed full paths. + auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive); + + if (it != allowedMountPaths.end()) + { + realPath = *it; + } + else if (isFused() && sourceBase.compare(archive) == 0) { // Special case: if the game is fused and the archive is the source's // base directory, mount it even though it's outside of the save dir. @@ -285,7 +307,7 @@ bool Filesystem::mount(const char *archive, const char *mountpoint, bool appendT bool Filesystem::unmount(const char *archive) { - if (!initialized || !archive) + if (!PHYSFS_isInit() || !archive) return false; std::string realPath; @@ -319,7 +341,7 @@ bool Filesystem::unmount(const char *archive) return PHYSFS_removeFromSearchPath(realPath.c_str()); } -File *Filesystem::newFile(const char *filename) const +love::filesystem::File *Filesystem::newFile(const char *filename) const { return new File(filename); } @@ -478,7 +500,7 @@ FileData *Filesystem::read(const char *filename, int64 size) const { File file(filename); - file.open(File::READ); + file.open(File::MODE_READ); // close() is called in the File destructor. return file.read(size); @@ -488,7 +510,7 @@ void Filesystem::write(const char *filename, const void *data, int64 size) const { File file(filename); - file.open(File::WRITE); + file.open(File::MODE_WRITE); // close() is called in the File destructor. if (!file.write(data, size)) @@ -499,7 +521,7 @@ void Filesystem::append(const char *filename, const void *data, int64 size) cons { File file(filename); - file.open(File::APPEND); + file.open(File::MODE_APPEND); // close() is called in the File destructor. if (!file.write(data, size)) @@ -538,108 +560,6 @@ int Filesystem::getDirectoryItems(lua_State *L) return 1; } -int Filesystem::lines_i(lua_State *L) -{ - const int bufsize = 1024; - char buf[bufsize]; - int linesize = 0; - bool newline = false; - - File *file = luax_checktype(L, lua_upvalueindex(1), "File", FILESYSTEM_FILE_T); - - // Only accept read mode at this point. - if (file->getMode() != File::READ) - return luaL_error(L, "File needs to stay in read mode."); - - int64 pos = file->tell(); - int64 userpos = -1; - - if (lua_isnoneornil(L, lua_upvalueindex(2)) == 0) - { - // User may have changed the file position. - userpos = pos; - pos = (int64) lua_tonumber(L, lua_upvalueindex(2)); - if (userpos != pos) - file->seek(pos); - } - - while (!newline && !file->eof()) - { - // This 64-bit to 32-bit integer cast should be safe as it never exceeds bufsize. - int read = (int) file->read(buf, bufsize); - if (read < 0) - return luaL_error(L, "Could not read from file."); - - linesize += read; - - for (int i = 0; i < read; i++) - { - if (buf[i] == '\n') - { - linesize -= read - i; - newline = true; - break; - } - } - } - - if (newline || (file->eof() && linesize > 0)) - { - if (linesize < bufsize) - { - // We have the line in the buffer on the stack. No 'new' and 'read' needed. - lua_pushlstring(L, buf, linesize > 0 && buf[linesize - 1] == '\r' ? linesize - 1 : linesize); - if (userpos < 0) - file->seek(pos + linesize + 1); - } - else - { - char *str = 0; - try - { - str = new char[linesize + 1]; - } - catch(std::bad_alloc &) - { - // Can't lua_error (longjmp) in exception handlers. - } - - if (!str) - return luaL_error(L, "Out of memory."); - - file->seek(pos); - - // Read the \n anyway and save us a call to seek. - if (file->read(str, linesize + 1) == -1) - { - delete [] str; - return luaL_error(L, "Could not read from file."); - } - - lua_pushlstring(L, str, str[linesize - 1] == '\r' ? linesize - 1 : linesize); - delete [] str; - } - - if (userpos >= 0) - { - // Save new position in upvalue. - lua_pushnumber(L, (lua_Number)(pos + linesize + 1)); - lua_replace(L, lua_upvalueindex(2)); - file->seek(userpos); - } - - return 1; - } - - // EOF reached. - if (userpos >= 0 && luax_toboolean(L, lua_upvalueindex(3))) - file->seek(userpos); - else - file->close(); - - return 0; -} - int64 Filesystem::getLastModified(const char *filename) const { PHYSFS_sint64 time = PHYSFS_getLastModTime(filename); @@ -677,6 +597,12 @@ std::vector &Filesystem::getRequirePath() return requirePath; } +void Filesystem::allowMountingForPath(const std::string &path) +{ + if (std::find(allowedMountPaths.begin(), allowedMountPaths.end(), path) == allowedMountPaths.end()) + allowedMountPaths.push_back(path); +} + } // physfs } // filesystem } // love diff --git a/src/modules/filesystem/physfs/Filesystem.h b/src/modules/filesystem/physfs/Filesystem.h index 647f15fe2..82ff6b4f4 100644 --- a/src/modules/filesystem/physfs/Filesystem.h +++ b/src/modules/filesystem/physfs/Filesystem.h @@ -24,47 +24,9 @@ // STD #include #include -#include -#include // LOVE -#include "common/Module.h" -#include "common/config.h" -#include "common/int.h" -#include "common/runtime.h" -#include "filesystem/FileData.h" -#include "File.h" - -// For great CWD. (Current Working Directory) -// Using this instead of boost::filesystem which totally -// cramped our style. -#ifdef LOVE_WINDOWS -# include -# include -#else -# include -# include -#endif - -// In Windows, we would like to use "LOVE" as the -// application folder, but in Linux, we like .love. -#define LOVE_APPDATA_PREFIX "" -#ifdef LOVE_WINDOWS -# define LOVE_APPDATA_FOLDER "LOVE" -# define LOVE_PATH_SEPARATOR "/" -# define LOVE_MAX_PATH _MAX_PATH -#else -# ifdef LOVE_MACOSX -# define LOVE_APPDATA_FOLDER "LOVE" -# elif defined(LOVE_LINUX) -# define LOVE_APPDATA_FOLDER "love" -# else -# define LOVE_APPDATA_PREFIX "." -# define LOVE_APPDATA_FOLDER "love" -# endif -# define LOVE_PATH_SEPARATOR "/" -# define LOVE_MAX_PATH MAXPATHLEN -#endif +#include "filesystem/Filesystem.h" namespace love { @@ -73,7 +35,7 @@ namespace filesystem namespace physfs { -class Filesystem : public Module +class Filesystem : public love::filesystem::Filesystem { public: @@ -81,7 +43,6 @@ public: virtual ~Filesystem(); // Implements Module. - virtual ModuleType getModuleType() const { return M_FILESYSTEM; } const char *getName() const; void init(const char *arg0); @@ -89,183 +50,55 @@ public: void setFused(bool fused); bool isFused() const; - /** - * This sets up the save directory. If the - * it is already set up, nothing happens. - * @return True on success, false otherwise. - **/ bool setupWriteDirectory(); - /** - * Sets the name of the save folder. - * @param ident The name of the game. Will be used to - * to create the folder in the LOVE data folder. - **/ bool setIdentity(const char *ident, bool appendToPath = false); const char *getIdentity() const; - /** - * Sets the path to the game source. - * This can only be set once. - * @param source Path to a directory or a .love-file. - **/ bool setSource(const char *source); - /** - * Gets the path to the game source. - * Returns a 0-length string if the source has not been set. - **/ const char *getSource() const; bool mount(const char *archive, const char *mountpoint, bool appendToPath = false); bool unmount(const char *archive); - /** - * Creates a new file. - **/ File *newFile(const char *filename) const; - /** - * Creates a new FileData object. Data will be copied. - * @param data Pointer to the data. - * @param size The size of the data. - * @param filename The full filename used to file type identification. - **/ FileData *newFileData(void *data, unsigned int size, const char *filename) const; - - /** - * Creates a new FileData object from base64 data. - * @param b64 The base64 data. - **/ FileData *newFileData(const char *b64, const char *filename) const; - /** - * Gets the current working directory. - **/ const char *getWorkingDirectory(); - - /** - * Gets the user home directory. - **/ std::string getUserDirectory(); - - /** - * Gets the APPDATA directory. On Windows, this is the folder - * in the %APPDATA% enviroment variable. On Linux, this is the - * user home folder. - **/ std::string getAppdataDirectory(); - - /** - * Gets the full path of the save folder. - **/ const char *getSaveDirectory(); - - /** - * Gets the full path to the directory containing the game source. - * For example if the game source is C:\Games\mygame.love, this will return - * C:\Games. - **/ std::string getSourceBaseDirectory() const; - /** - * Gets the real directory path containing the file. - **/ std::string getRealDirectory(const char *filename) const; - /** - * Checks if a path is a directory. - * @param dir The directory name to check. - **/ bool isDirectory(const char *dir) const; - - /** - * Checks if a filename exists. - * @param file The filename to check. - **/ bool isFile(const char *file) const; - /** - * Creates a directory. Write dir must be set. - * @param dir The directory to create. - **/ bool createDirectory(const char *dir); - /** - * Removes a file (or directory). - * @param file The file or directory to remove. - **/ bool remove(const char *file); - /** - * Reads data from a file. - * @param filename The name of the file to read from. - * @param size The size in bytes of the data to read. - **/ FileData *read(const char *filename, int64 size = File::ALL) const; - - /** - * Write data to a file. - * @param filename The name of the file to write to. - * @param data The data to write. - * @param size The size in bytes of the data to write. - **/ void write(const char *filename, const void *data, int64 size) const; - - /** - * Append data to a file, creating it if it doesn't exist. - * @param filename The name of the file to write to. - * @param data The data to append. - * @param size The size in bytes of the data to append. - **/ void append(const char *filename, const void *data, int64 size) const; - /** - * This "native" method returns a table of all - * files in a given directory. - **/ int getDirectoryItems(lua_State *L); - /** - * Gets the last modification time of a file, in seconds - * since the Unix epoch. - * @param filename The name of the file. - **/ int64 getLastModified(const char *filename) const; - - /** - * Gets the size of a file in bytes. - * @param filename The name of the file. - **/ int64 getSize(const char *filename) const; - /** - * Enable or disable symbolic link support in love.filesystem. - **/ void setSymlinksEnabled(bool enable); - - /** - * Gets whether symbolic link support is enabled. - **/ bool areSymlinksEnabled() const; - - /** - * Gets whether a filepath is actually a symlink. - * Always returns false if symlinks are not enabled. - **/ bool isSymlink(const char *filename) const; - /** - * Text file line-reading iterator function used and - * pushed on the Lua stack by love.filesystem.lines - * and File:lines. - **/ - static int lines_i(lua_State *L); - - // Require path accessors - // Not const because it's R/W std::vector &getRequirePath(); + void allowMountingForPath(const std::string &path); + private: // Contains the current working directory (UTF8). @@ -286,9 +119,6 @@ private: // The full path to the source of the game. std::string game_source; - // Workaround for machines without PhysFS 2.0 - bool initialized; - // Allow saving outside of the LOVE_APPDATA_FOLDER // for release 'builds' bool fused; @@ -297,6 +127,8 @@ private: // Search path for require std::vector requirePath; + std::vector allowedMountPaths; + }; // Filesystem } // physfs diff --git a/src/modules/filesystem/wrap_DroppedFile.cpp b/src/modules/filesystem/wrap_DroppedFile.cpp new file mode 100644 index 000000000..9be0312e2 --- /dev/null +++ b/src/modules/filesystem/wrap_DroppedFile.cpp @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#include "wrap_DroppedFile.h" +#include "wrap_File.h" + +namespace love +{ +namespace filesystem +{ + +DroppedFile *luax_checkdroppedfile(lua_State *L, int idx) +{ + return luax_checktype(L, idx, "DroppedFile", FILESYSTEM_DROPPED_FILE_T); +} + +static const luaL_Reg functions[] = +{ + // Inherits from File. + { "getSize", w_File_getSize }, + { "open", w_File_open }, + { "close", w_File_close }, + { "isOpen", w_File_isOpen }, + { "read", w_File_read }, + { "write", w_File_write }, + { "flush", w_File_flush }, + { "eof", w_File_eof }, + { "tell", w_File_tell }, + { "seek", w_File_seek }, + { "lines", w_File_lines }, + { "setBuffer", w_File_setBuffer }, + { "getBuffer", w_File_getBuffer }, + { "getMode", w_File_getMode }, + { "getFilename", w_File_getFilename }, + { "getExtension", w_File_getExtension }, + { 0, 0 } +}; + +extern "C" int luaopen_droppedfile(lua_State *L) +{ + return luax_register_type(L, "DroppedFile", functions); +} + +} // filesystem +} // love diff --git a/src/modules/filesystem/wrap_DroppedFile.h b/src/modules/filesystem/wrap_DroppedFile.h new file mode 100644 index 000000000..8a6516fb1 --- /dev/null +++ b/src/modules/filesystem/wrap_DroppedFile.h @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#ifndef LOVE_FILESYSTEM_WRAP_DROPPED_FILE_H +#define LOVE_FILESYSTEM_WRAP_DROPPED_FILE_H + +// LOVE +#include "common/runtime.h" +#include "DroppedFile.h" + +namespace love +{ +namespace filesystem +{ + +DroppedFile *luax_checkdroppedfile(lua_State *L, int idx); +extern "C" int luaopen_droppedfile(lua_State *L); + +} // filesystem +} // love + +#endif // LOVE_FILESYSTEM_WRAP_DROPPED_FILE_H diff --git a/src/modules/filesystem/wrap_File.cpp b/src/modules/filesystem/wrap_File.cpp index dda6da74a..a467625f5 100644 --- a/src/modules/filesystem/wrap_File.cpp +++ b/src/modules/filesystem/wrap_File.cpp @@ -20,8 +20,6 @@ #include "wrap_File.h" -#include "physfs/Filesystem.h" - #include "common/Data.h" #include "common/Exception.h" #include "common/int.h" @@ -223,26 +221,128 @@ int w_File_seek(lua_State *L) return 1; } +int w_File_lines_i(lua_State *L) +{ + const int bufsize = 1024; + char buf[bufsize]; + int linesize = 0; + bool newline = false; + + File *file = luax_checktype(L, lua_upvalueindex(1), "File", FILESYSTEM_FILE_T); + + // Only accept read mode at this point. + if (file->getMode() != File::MODE_READ) + return luaL_error(L, "File needs to stay in read mode."); + + int64 pos = file->tell(); + int64 userpos = -1; + + if (lua_isnoneornil(L, lua_upvalueindex(2)) == 0) + { + // User may have changed the file position. + userpos = pos; + pos = (int64) lua_tonumber(L, lua_upvalueindex(2)); + if (userpos != pos) + file->seek(pos); + } + + while (!newline && !file->eof()) + { + // This 64-bit to 32-bit integer cast should be safe as it never exceeds bufsize. + int read = (int) file->read(buf, bufsize); + if (read < 0) + return luaL_error(L, "Could not read from file."); + + linesize += read; + + for (int i = 0; i < read; i++) + { + if (buf[i] == '\n') + { + linesize -= read - i; + newline = true; + break; + } + } + } + + if (newline || (file->eof() && linesize > 0)) + { + if (linesize < bufsize) + { + // We have the line in the buffer on the stack. No 'new' and 'read' needed. + lua_pushlstring(L, buf, linesize > 0 && buf[linesize - 1] == '\r' ? linesize - 1 : linesize); + if (userpos < 0) + file->seek(pos + linesize + 1); + } + else + { + char *str = 0; + try + { + str = new char[linesize + 1]; + } + catch(std::bad_alloc &) + { + // Can't lua_error (longjmp) in exception handlers. + } + + if (!str) + return luaL_error(L, "Out of memory."); + + file->seek(pos); + + // Read the \n anyway and save us a call to seek. + if (file->read(str, linesize + 1) == -1) + { + delete [] str; + return luaL_error(L, "Could not read from file."); + } + + lua_pushlstring(L, str, str[linesize - 1] == '\r' ? linesize - 1 : linesize); + delete [] str; + } + + if (userpos >= 0) + { + // Save new position in upvalue. + lua_pushnumber(L, (lua_Number)(pos + linesize + 1)); + lua_replace(L, lua_upvalueindex(2)); + file->seek(userpos); + } + + return 1; + } + + // EOF reached. + if (userpos >= 0 && luax_toboolean(L, lua_upvalueindex(3))) + file->seek(userpos); + else + file->close(); + + return 0; +} + int w_File_lines(lua_State *L) { File *file = luax_checkfile(L, 1); lua_pushnumber(L, 0); // File position. - luax_pushboolean(L, file->getMode() != File::CLOSED); // Save current file mode. + luax_pushboolean(L, file->getMode() != File::MODE_CLOSED); // Save current file mode. - if (file->getMode() != File::READ) + if (file->getMode() != File::MODE_READ) { - if (file->getMode() != File::CLOSED) + if (file->getMode() != File::MODE_CLOSED) file->close(); bool success = false; - luax_catchexcept(L, [&](){ success = file->open(File::READ); }); + luax_catchexcept(L, [&](){ success = file->open(File::MODE_READ); }); if (!success) return luaL_error(L, "Could not open file."); } - lua_pushcclosure(L, physfs::Filesystem::lines_i, 3); + lua_pushcclosure(L, w_File_lines_i, 3); return 1; } @@ -299,6 +399,20 @@ int w_File_getMode(lua_State *L) return 1; } +int w_File_getFilename(lua_State *L) +{ + File *file = luax_checkfile(L, 1); + luax_pushstring(L, file->getFilename()); + return 1; +} + +int w_File_getExtension(lua_State *L) +{ + File *file = luax_checkfile(L, 1); + luax_pushstring(L, file->getExtension()); + return 1; +} + static const luaL_Reg functions[] = { { "getSize", w_File_getSize }, @@ -315,6 +429,8 @@ static const luaL_Reg functions[] = { "setBuffer", w_File_setBuffer }, { "getBuffer", w_File_getBuffer }, { "getMode", w_File_getMode }, + { "getFilename", w_File_getFilename }, + { "getExtension", w_File_getExtension }, { 0, 0 } }; diff --git a/src/modules/filesystem/wrap_File.h b/src/modules/filesystem/wrap_File.h index d4bd65d74..1248083e4 100644 --- a/src/modules/filesystem/wrap_File.h +++ b/src/modules/filesystem/wrap_File.h @@ -44,10 +44,13 @@ int w_File_flush(lua_State *L); int w_File_eof(lua_State *L); int w_File_tell(lua_State *L); int w_File_seek(lua_State *L); +int w_File_lines_i(lua_State *L); int w_File_lines(lua_State *L); int w_File_setBuffer(lua_State *L); int w_File_getBuffer(lua_State *L); int w_File_getMode(lua_State *L); +int w_File_getFilename(lua_State *L); +int w_File_getExtension(lua_State *L); extern "C" int luaopen_file(lua_State *L); } // filesystem diff --git a/src/modules/filesystem/wrap_Filesystem.cpp b/src/modules/filesystem/wrap_Filesystem.cpp index ce241e11b..cc37dda75 100644 --- a/src/modules/filesystem/wrap_Filesystem.cpp +++ b/src/modules/filesystem/wrap_Filesystem.cpp @@ -21,6 +21,7 @@ // LOVE #include "wrap_Filesystem.h" #include "wrap_File.h" +#include "wrap_DroppedFile.h" #include "wrap_FileData.h" #include "physfs/Filesystem.h" @@ -38,7 +39,7 @@ namespace love namespace filesystem { -#define instance() (Module::getInstance(Module::M_FILESYSTEM)) +#define instance() (Module::getInstance(Module::M_FILESYSTEM)) bool hack_setupWriteDirectory() { @@ -124,7 +125,7 @@ int w_newFile(lua_State *L) const char *filename = luaL_checkstring(L, 1); const char *str = 0; - File::Mode mode = File::CLOSED; + File::Mode mode = File::MODE_CLOSED; if (lua_isstring(L, 2)) { @@ -135,7 +136,7 @@ int w_newFile(lua_State *L) File *t = instance()->newFile(filename); - if (mode != File::CLOSED) + if (mode != File::MODE_CLOSED) { try { @@ -363,7 +364,7 @@ static int w_write_or_append(lua_State *L, File::Mode mode) try { - if (mode == File::APPEND) + if (mode == File::MODE_APPEND) instance()->append(filename, (const void *) input, len); else instance()->write(filename, (const void *) input, len); @@ -379,12 +380,12 @@ static int w_write_or_append(lua_State *L, File::Mode mode) int w_write(lua_State *L) { - return w_write_or_append(L, File::WRITE); + return w_write_or_append(L, File::MODE_WRITE); } int w_append(lua_State *L) { - return w_write_or_append(L, File::APPEND); + return w_write_or_append(L, File::MODE_APPEND); } int w_getDirectoryItems(lua_State *L) @@ -401,7 +402,7 @@ int w_lines(lua_State *L) file = instance()->newFile(lua_tostring(L, 1)); bool success = false; - luax_catchexcept(L, [&](){ success = file->open(File::READ); }); + luax_catchexcept(L, [&](){ success = file->open(File::MODE_READ); }); if (!success) { @@ -415,7 +416,7 @@ int w_lines(lua_State *L) else return luaL_argerror(L, 1, "expected filename."); - lua_pushcclosure(L, physfs::Filesystem::lines_i, 1); + lua_pushcclosure(L, w_File_lines_i, 1); return 1; } @@ -690,13 +691,14 @@ static const luaL_Reg functions[] = static const lua_CFunction types[] = { luaopen_file, + luaopen_droppedfile, luaopen_filedata, 0 }; extern "C" int luaopen_love_filesystem(lua_State *L) { - physfs::Filesystem *instance = instance(); + Filesystem *instance = instance(); if (instance == nullptr) { luax_catchexcept(L, [&](){ instance = new physfs::Filesystem(); }); diff --git a/src/modules/font/BMFontRasterizer.cpp b/src/modules/font/BMFontRasterizer.cpp index 4cfd1f96b..84403d78e 100644 --- a/src/modules/font/BMFontRasterizer.cpp +++ b/src/modules/font/BMFontRasterizer.cpp @@ -20,7 +20,7 @@ // LOVE #include "BMFontRasterizer.h" -#include "filesystem/physfs/Filesystem.h" +#include "filesystem/Filesystem.h" #include "image/Image.h" // C++ @@ -188,7 +188,7 @@ void BMFontRasterizer::parseConfig(const std::string &configtext) // Load the page file from disk into an ImageData, if necessary. if (images[pageindex].get() == nullptr) { - using namespace love::filesystem::physfs; + using namespace love::filesystem; Filesystem *filesystem = Module::getInstance(Module::M_FILESYSTEM); image::Image *imagemodule = Module::getInstance(Module::M_IMAGE); diff --git a/src/modules/image/magpie/ImageData.cpp b/src/modules/image/magpie/ImageData.cpp index 617595ea9..06cafb38a 100644 --- a/src/modules/image/magpie/ImageData.cpp +++ b/src/modules/image/magpie/ImageData.cpp @@ -176,7 +176,7 @@ void ImageData::encode(love::filesystem::File *f, ImageData::Format format) try { - f->open(love::filesystem::File::WRITE); + f->open(love::filesystem::File::MODE_WRITE); f->write(encodedimage.data, encodedimage.size); f->close(); } diff --git a/src/scripts/boot.lua b/src/scripts/boot.lua index bd0c942dc..ad140378e 100644 --- a/src/scripts/boot.lua +++ b/src/scripts/boot.lua @@ -220,6 +220,12 @@ function love.createhandlers() resize = function(w, h) if love.resize then return love.resize(w, h) end end, + filedropped = function (f) + if love.filedropped then return love.filedropped(f) end + end, + directorydropped = function (dir) + if love.directorydropped then return love.directorydropped(dir) end + end, }, { __index = function(self, name) error("Unknown event: " .. name) diff --git a/src/scripts/boot.lua.h b/src/scripts/boot.lua.h index 547d968ed..630bcc242 100644 --- a/src/scripts/boot.lua.h +++ b/src/scripts/boot.lua.h @@ -405,6 +405,20 @@ const unsigned char boot_lua[] = 0x74, 0x68, 0x65, 0x6e, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x77, 0x2c, 0x20, 0x68, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x2c, 0x0a, + 0x09, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, + 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x66, 0x29, 0x0a, + 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x72, 0x6f, + 0x70, 0x70, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6c, + 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x28, 0x66, 0x29, + 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x09, 0x65, 0x6e, 0x64, 0x2c, 0x0a, + 0x09, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, + 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x64, 0x69, 0x72, 0x29, 0x0a, + 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x79, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x72, 0x65, 0x74, + 0x75, 0x72, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, + 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x28, 0x64, 0x69, 0x72, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x09, 0x65, 0x6e, 0x64, 0x2c, 0x0a, 0x09, 0x7d, 0x2c, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x5f, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2c, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x29, 0x0a,