diff --git a/src/common/b64.cpp b/src/common/b64.cpp index aa2f6f5df..9ba0b0a61 100644 --- a/src/common/b64.cpp +++ b/src/common/b64.cpp @@ -19,12 +19,98 @@ **/ #include "b64.h" +#include "Exception.h" + +#include +#include namespace love { +// Translation table as described in RFC1113 +static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// Translation table to decode (created by Bob Trower) static const char cd64[]="|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq"; +/** + * encode 3 8-bit binary bytes as 4 '6-bit' characters + **/ +static void b64_encode_block(char in[3], char out[4], int len) +{ + out[0] = (char) cb64[(int)(in[0] >> 2)]; + out[1] = (char) cb64[(int)(((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4))]; + out[2] = (char) (len > 1 ? cb64[(int)(((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6))] : '='); + out[3] = (char) (len > 2 ? cb64[(int)(in[2] & 0x3f)] : '='); +} + +char *b64_encode(const char *src, size_t srclen, size_t linelen, size_t &dstlen) +{ + if (linelen == 0) + linelen = std::numeric_limits::max(); + + size_t blocksout = 0; + size_t srcpos = 0; + + size_t adjustment = (srclen % 3) ? (3 - (srclen % 3)) : 0; + size_t paddedlen = ((srclen + adjustment) / 3) * 4; + + dstlen = paddedlen + paddedlen / linelen; + + if (dstlen == 0) + return nullptr; + + char *dst = nullptr; + try + { + dst = new char[dstlen + 1]; + } + catch (std::exception &) + { + throw love::Exception("Out of memory."); + } + + size_t dstpos = 0; + + while (srcpos < srclen) + { + char in[3] = {0}; + char out[4] = {0}; + + int len = 0; + + for (int i = 0; i < 3; i++) + { + if (srcpos >= srclen) + break; + + in[i] = src[srcpos++]; + len++; + } + + if (len > 0) + { + b64_encode_block(in, out, len); + + for (int i = 0; i < 4 && dstpos < dstlen; i++, dstpos++) + dst[dstpos] = out[i]; + + blocksout++; + } + + if (blocksout >= linelen / 4 || srcpos >= srclen) + { + if (blocksout > 0 && dstpos < dstlen) + dst[dstpos++] = '\n'; + + blocksout = 0; + } + } + + dst[dstpos] = '\0'; + return dst; +} + static void b64_decode_block(char in[4], char out[3]) { out[0] = (char)(in[0] << 2 | in[1] >> 4); @@ -32,34 +118,44 @@ static void b64_decode_block(char in[4], char out[3]) out[2] = (char)(((in[2] << 6) & 0xc0) | in[3]); } -char *b64_decode(const char *src, int slen, int &size) +char *b64_decode(const char *src, size_t srclen, size_t &size) { - size = (slen / 4) * 3; + size_t paddedsize = (srclen / 4) * 3; + + char *dst = nullptr; + try + { + dst = new char[paddedsize]; + } + catch (std::exception &) + { + throw love::Exception("Out of memory."); + } - char *dst = new char[size]; char *d = dst; - char in[4] = {0}, out[3], v; - int i, len, pos = 0; + char in[4] = {0}; + char out[3] = {0}; + size_t i, len, srcpos = 0; - while (pos <= slen) + while (srcpos <= srclen) { - for (len = 0, i = 0; i < 4 && pos <= slen; i++) + for (len = 0, i = 0; i < 4 && srcpos <= srclen; i++) { - v = 0; + char v = 0; - while (pos <= slen && v == 0) + while (srcpos <= srclen && v == 0) { - v = src[pos++]; + v = src[srcpos++]; v = (char)((v < 43 || v > 122) ? 0 : cd64[v - 43]); - if (v) + if (v != 0) v = (char)((v == '$') ? 0 : v - 61); } - if (pos <= slen) + if (srcpos <= srclen) { len++; - if (v) + if (v != 0) in[i] = (char)(v - 1); } else @@ -74,6 +170,7 @@ char *b64_decode(const char *src, int slen, int &size) } } + size = (size_t)(ptrdiff_t) (d - dst); return dst; } diff --git a/src/common/b64.h b/src/common/b64.h index ad586a6f2..f808865f2 100644 --- a/src/common/b64.h +++ b/src/common/b64.h @@ -20,21 +20,35 @@ #include "config.h" +#include + #ifndef LOVE_B64_H #define LOVE_B64_H namespace love { +/** + * Base64-encode data. + * + * @param src The data to encode. + * @param srclen The size in bytes of the data. + * @param linelen The maximum length of each line in the encoded string. + * 0 indicates no maximum length. + * @param dstlen The length of the encoded string is stored here. + * @return A string containing the base64-encoded data (allocated with new[]). + */ +char *b64_encode(const char *src, size_t srclen, size_t linelen, size_t &dstlen); + /** * Decode base64 encoded data. * * @param src The string containing the base64 data. - * @param slen The length of the string. - * @param size The size of the binary data is stored here. + * @param srclen The length of the string. + * @param dstlen The size of the binary data is stored here. * @return A chunk of memory containing the binary data (allocated with new[]). */ -char *b64_decode(const char *src, int slen, int &size); +char *b64_decode(const char *src, size_t srclen, size_t &dstlen); } // love diff --git a/src/modules/filesystem/FileData.cpp b/src/modules/filesystem/FileData.cpp index 4e0f3972d..9ce9d94d3 100644 --- a/src/modules/filesystem/FileData.cpp +++ b/src/modules/filesystem/FileData.cpp @@ -73,23 +73,5 @@ const std::string &FileData::getExtension() const return extension; } -bool FileData::getConstant(const char *in, Decoder &out) -{ - return decoders.find(in, out); -} - -bool FileData::getConstant(Decoder in, const char *&out) -{ - return decoders.find(in, out); -} - -StringMap::Entry FileData::decoderEntries[] = -{ - {"file", FileData::FILE}, - {"base64", FileData::BASE64}, -}; - -StringMap FileData::decoders(FileData::decoderEntries, sizeof(FileData::decoderEntries)); - } // filesystem } // love diff --git a/src/modules/filesystem/FileData.h b/src/modules/filesystem/FileData.h index 7106cdb7d..2e4e9abcd 100644 --- a/src/modules/filesystem/FileData.h +++ b/src/modules/filesystem/FileData.h @@ -22,10 +22,11 @@ #define LOVE_FILESYSTEM_FILE_DATA_H // LOVE -#include #include "common/Data.h" -#include "common/StringMap.h" #include "common/int.h" +#include "common/Exception.h" + +#include namespace love { @@ -36,13 +37,6 @@ class FileData : public Data { public: - enum Decoder - { - FILE, - BASE64, - DECODE_MAX_ENUM - }; // Decoder - FileData(uint64 size, const std::string &filename); virtual ~FileData(); @@ -54,9 +48,6 @@ public: const std::string &getFilename() const; const std::string &getExtension() const; - static bool getConstant(const char *in, Decoder &out); - static bool getConstant(Decoder in, const char *&out); - private: // The actual data. @@ -71,9 +62,6 @@ private: // The extension (without dot). Used to identify file type. std::string extension; - static StringMap::Entry decoderEntries[]; - static StringMap decoders; - }; // FileData } // filesystem diff --git a/src/modules/filesystem/Filesystem.cpp b/src/modules/filesystem/Filesystem.cpp index 571a505f7..611d1eed5 100644 --- a/src/modules/filesystem/Filesystem.cpp +++ b/src/modules/filesystem/Filesystem.cpp @@ -60,6 +60,13 @@ bool Filesystem::isAndroidSaveExternal() const return useExternal; } +FileData *Filesystem::newFileData(const void *data, size_t size, const char *filename) const +{ + FileData *fd = new FileData(size, std::string(filename)); + memcpy(fd->getData(), data, size); + return fd; +} + bool Filesystem::isRealDirectory(const std::string &path) const { #ifdef LOVE_WINDOWS diff --git a/src/modules/filesystem/Filesystem.h b/src/modules/filesystem/Filesystem.h index dbc12b58a..17748f65a 100644 --- a/src/modules/filesystem/Filesystem.h +++ b/src/modules/filesystem/Filesystem.h @@ -128,13 +128,7 @@ public: * @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; + virtual FileData *newFileData(const void *data, size_t size, const char *filename) const; /** * Gets the current working directory. diff --git a/src/modules/filesystem/physfs/Filesystem.cpp b/src/modules/filesystem/physfs/Filesystem.cpp index c466a6d6b..66328e3cd 100644 --- a/src/modules/filesystem/physfs/Filesystem.cpp +++ b/src/modules/filesystem/physfs/Filesystem.cpp @@ -457,30 +457,6 @@ love::filesystem::File *Filesystem::newFile(const char *filename) const return new File(filename); } -FileData *Filesystem::newFileData(void *data, unsigned int size, const char *filename) const -{ - FileData *fd = new FileData(size, std::string(filename)); - - // Copy the data into FileData. - memcpy(fd->getData(), data, size); - - return fd; -} - -FileData *Filesystem::newFileData(const char *b64, const char *filename) const -{ - int size = (int) strlen(b64); - int outsize = 0; - char *dst = b64_decode(b64, size, outsize); - FileData *fd = new FileData(outsize, std::string(filename)); - - // Copy the data into FileData. - memcpy(fd->getData(), dst, outsize); - delete [] dst; - - return fd; -} - const char *Filesystem::getWorkingDirectory() { if (cwd.empty()) diff --git a/src/modules/filesystem/physfs/Filesystem.h b/src/modules/filesystem/physfs/Filesystem.h index dd42b1fd8..36d37741b 100644 --- a/src/modules/filesystem/physfs/Filesystem.h +++ b/src/modules/filesystem/physfs/Filesystem.h @@ -64,9 +64,6 @@ public: File *newFile(const char *filename) const; - FileData *newFileData(void *data, unsigned int size, const char *filename) const; - FileData *newFileData(const char *b64, const char *filename) const; - const char *getWorkingDirectory(); std::string getUserDirectory(); std::string getAppdataDirectory(); diff --git a/src/modules/filesystem/wrap_Filesystem.cpp b/src/modules/filesystem/wrap_Filesystem.cpp index ed964b448..a289c7066 100644 --- a/src/modules/filesystem/wrap_Filesystem.cpp +++ b/src/modules/filesystem/wrap_Filesystem.cpp @@ -242,26 +242,9 @@ int w_newFileData(lua_State *L) size_t length = 0; const char *str = luaL_checklstring(L, 1, &length); const char *filename = luaL_checkstring(L, 2); - const char *decstr = lua_isstring(L, 3) ? lua_tostring(L, 3) : 0; - FileData::Decoder decoder = FileData::FILE; - - if (decstr && !FileData::getConstant(decstr, decoder)) - return luaL_error(L, "Invalid FileData decoder: %s", decstr); - - FileData *t = 0; - - switch (decoder) - { - case FileData::FILE: - t = instance()->newFileData((void *)str, (int)length, filename); - break; - case FileData::BASE64: - t = instance()->newFileData(str, filename); - break; - default: - return luaL_error(L, "Invalid FileData decoder: %s", decstr); - } + FileData *t = nullptr; + luax_catchexcept(L, [&](){ t = instance()->newFileData(str, length, filename); }); luax_pushtype(L, FILESYSTEM_FILE_DATA_ID, t); t->release(); diff --git a/src/modules/image/ImageData.h b/src/modules/image/ImageData.h index fde48c5a2..393f45cc6 100644 --- a/src/modules/image/ImageData.h +++ b/src/modules/image/ImageData.h @@ -23,6 +23,7 @@ // LOVE #include "common/Data.h" +#include "common/StringMap.h" #include "filesystem/FileData.h" #include "thread/threads.h" diff --git a/src/modules/math/MathModule.cpp b/src/modules/math/MathModule.cpp index 2493630de..a6a22b0a2 100644 --- a/src/modules/math/MathModule.cpp +++ b/src/modules/math/MathModule.cpp @@ -21,6 +21,8 @@ // LOVE #include "MathModule.h" #include "common/Vector.h" +#include "common/b64.h" +#include "common/int.h" #include "BezierCurve.h" // STL @@ -34,49 +36,129 @@ using love::Vertex; namespace { - // check if an angle is oriented counter clockwise - inline bool is_oriented_ccw(const Vertex &a, const Vertex &b, const Vertex &c) + +static const char hexchars[] = "0123456789abcdef"; + +char *bytesToHex(const love::uint8 *src, size_t srclen, size_t &dstlen) +{ + dstlen = srclen * 2; + + if (dstlen == 0) + return nullptr; + + char *dst = nullptr; + try { - // return det(b-a, c-a) >= 0 - return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) >= 0; + dst = new char[dstlen + 1]; + } + catch (std::exception &) + { + throw love::Exception("Out of memory."); } - // check if a and b are on the same side of the line c->d - bool on_same_side(const Vertex &a, const Vertex &b, const Vertex &c, const Vertex &d) + for (size_t i = 0; i < srclen; i++) { - float px = d.x - c.x, py = d.y - c.y; - // return det(p, a-c) * det(p, b-c) >= 0 - float l = px * (a.y - c.y) - py * (a.x - c.x); - float m = px * (b.y - c.y) - py * (b.x - c.x); - return l * m >= 0; + love::uint8 b = src[i]; + dst[i * 2 + 0] = hexchars[b >> 4]; + dst[i * 2 + 1] = hexchars[b & 0xF]; } - // checks is p is contained in the triangle abc - inline bool point_in_triangle(const Vertex &p, const Vertex &a, const Vertex &b, const Vertex &c) - { - return on_same_side(p,a, b,c) && on_same_side(p,b, a,c) && on_same_side(p,c, a,b); - } - - // checks if any vertex in `vertices' is in the triangle abc. - bool any_point_in_triangle(const list &vertices, const Vertex &a, const Vertex &b, const Vertex &c) - { - list::const_iterator it, end = vertices.end(); - for (it = vertices.begin(); it != end; ++it) - { - const Vertex *p = *it; - if ((p != &a) && (p != &b) && (p != &c) && point_in_triangle(*p, a,b,c)) // oh god... - return true; - } - - return false; - } - - inline bool is_ear(const Vertex &a, const Vertex &b, const Vertex &c, const list &vertices) - { - return is_oriented_ccw(a,b,c) && !any_point_in_triangle(vertices, a,b,c); - } + dst[dstlen] = '\0'; + return dst; } +love::uint8 nibble(char c) +{ + if (c >= '0' && c <= '9') + return (love::uint8) (c - '0'); + + if (c >= 'A' && c <= 'F') + return (love::uint8) (c - 'A' + 0x0a); + + if (c >= 'a' && c <= 'f') + return (love::uint8) (c - 'a' + 0x0a); + + return 0; +} + +love::uint8 *hexToBytes(const char *src, size_t srclen, size_t &dstlen) +{ + if (srclen >= 2 && src[0] == '0' && (src[1] == 'x' || src[1] == 'X')) + { + src += 2; + srclen -= 2; + } + + dstlen = (srclen + 1) / 2; + + if (dstlen == 0) + return nullptr; + + love::uint8 *dst = nullptr; + try + { + dst = new love::uint8[dstlen]; + } + catch (std::exception &) + { + throw love::Exception("Out of memory."); + } + + for (size_t i = 0; i < dstlen; i++) + { + dst[i] = nibble(src[i * 2]) << 4; + + if (i * 2 + 1 < srclen) + dst[i] |= nibble(src[i * 2 + 1]); + } + + return dst; +} + +// check if an angle is oriented counter clockwise +inline bool is_oriented_ccw(const Vertex &a, const Vertex &b, const Vertex &c) +{ + // return det(b-a, c-a) >= 0 + return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) >= 0; +} + +// check if a and b are on the same side of the line c->d +bool on_same_side(const Vertex &a, const Vertex &b, const Vertex &c, const Vertex &d) +{ + float px = d.x - c.x, py = d.y - c.y; + // return det(p, a-c) * det(p, b-c) >= 0 + float l = px * (a.y - c.y) - py * (a.x - c.x); + float m = px * (b.y - c.y) - py * (b.x - c.x); + return l * m >= 0; +} + +// checks is p is contained in the triangle abc +inline bool point_in_triangle(const Vertex &p, const Vertex &a, const Vertex &b, const Vertex &c) +{ + return on_same_side(p,a, b,c) && on_same_side(p,b, a,c) && on_same_side(p,c, a,b); +} + +// checks if any vertex in `vertices' is in the triangle abc. +bool any_point_in_triangle(const list &vertices, const Vertex &a, const Vertex &b, const Vertex &c) +{ + list::const_iterator it, end = vertices.end(); + for (it = vertices.begin(); it != end; ++it) + { + const Vertex *p = *it; + if ((p != &a) && (p != &b) && (p != &c) && point_in_triangle(*p, a,b,c)) // oh god... + return true; + } + + return false; +} + +inline bool is_ear(const Vertex &a, const Vertex &b, const Vertex &c, const list &vertices) +{ + return is_oriented_ccw(a,b,c) && !any_point_in_triangle(vertices, a,b,c); +} + +} // anonymous namespace + namespace love { namespace math @@ -270,5 +352,47 @@ char *Math::decompress(Compressor::Format format, const char *cbytes, size_t com return compressor->decompress(format, cbytes, compressedsize, rawsize); } +char *Math::encode(EncodeFormat format, const char *src, size_t srclen, size_t &dstlen, size_t linelen) +{ + switch (format) + { + case ENCODE_BASE64: + default: + return b64_encode(src, srclen, linelen, dstlen); + case ENCODE_HEX: + return bytesToHex((const uint8 *) src, srclen, dstlen); + } +} + +char *Math::decode(EncodeFormat format, const char *src, size_t srclen, size_t &dstlen) +{ + switch (format) + { + case ENCODE_BASE64: + default: + return b64_decode(src, srclen, dstlen); + case ENCODE_HEX: + return (char *) hexToBytes(src, srclen, dstlen); + } +} + +bool Math::getConstant(const char *in, EncodeFormat &out) +{ + return encoders.find(in, out); +} + +bool Math::getConstant(EncodeFormat in, const char *&out) +{ + return encoders.find(in, out); +} + +StringMap::Entry Math::encoderEntries[] = +{ + { "base64", ENCODE_BASE64 }, + { "hex", ENCODE_HEX }, +}; + +StringMap Math::encoders(Math::encoderEntries, sizeof(Math::encoderEntries)); + } // math } // love diff --git a/src/modules/math/MathModule.h b/src/modules/math/MathModule.h index 66b4100ba..8217e193b 100644 --- a/src/modules/math/MathModule.h +++ b/src/modules/math/MathModule.h @@ -30,6 +30,7 @@ #include "common/math.h" #include "common/Vector.h" #include "common/int.h" +#include "common/StringMap.h" // Noise #include "libraries/noise1234/noise1234.h" @@ -53,6 +54,13 @@ private: public: + enum EncodeFormat + { + ENCODE_BASE64, + ENCODE_HEX, + ENCODE_MAX_ENUM + }; + virtual ~Math(); RandomGenerator *getRandomGenerator() @@ -152,12 +160,21 @@ public: **/ char *decompress(Compressor::Format format, const char *cbytes, size_t compressedsize, size_t &rawsize); + char *encode(EncodeFormat format, const char *src, size_t srclen, size_t &dstlen, size_t linelen = 0); + char *decode(EncodeFormat format, const char *src, size_t srclen, size_t &dstlen); + + static bool getConstant(const char *in, EncodeFormat &out); + static bool getConstant(EncodeFormat in, const char *&out); + static Math instance; private: Math(); + static StringMap::Entry encoderEntries[]; + static StringMap encoders; + }; // Math inline float Math::noise(float x) const diff --git a/src/modules/math/wrap_Math.cpp b/src/modules/math/wrap_Math.cpp index ac1d152c8..a9bbf8cae 100644 --- a/src/modules/math/wrap_Math.cpp +++ b/src/modules/math/wrap_Math.cpp @@ -24,6 +24,7 @@ #include "wrap_CompressedData.h" #include "MathModule.h" #include "BezierCurve.h" +#include "common/b64.h" #include #include @@ -385,6 +386,72 @@ int w_decompress(lua_State *L) return 1; } +int w_encode(lua_State *L) +{ + const char *formatstr = luaL_checkstring(L, 1); + Math::EncodeFormat format; + if (!Math::getConstant(formatstr, format)) + return luaL_error(L, "Invalid encode format: %s", formatstr); + + size_t srclen = 0; + const char *src = nullptr; + + if (luax_istype(L, 2, DATA_ID)) + { + Data *data = luax_totype(L, 2, DATA_ID); + src = (const char *) data->getData(); + srclen = data->getSize(); + } + else + src = luaL_checklstring(L, 2, &srclen); + + size_t linelen = (size_t) luaL_optinteger(L, 3, 0); + + size_t dstlen = 0; + char *dst = nullptr; + luax_catchexcept(L, [&](){ dst = Math::instance.encode(format, src, srclen, dstlen, linelen); }); + + if (dst != nullptr) + lua_pushlstring(L, dst, dstlen); + else + lua_pushstring(L, ""); + + delete[] dst; + return 1; +} + +int w_decode(lua_State *L) +{ + const char *formatstr = luaL_checkstring(L, 1); + Math::EncodeFormat format; + if (!Math::getConstant(formatstr, format)) + return luaL_error(L, "Invalid decode format: %s", formatstr); + + size_t srclen = 0; + const char *src = nullptr; + + if (luax_istype(L, 2, DATA_ID)) + { + Data *data = luax_totype(L, 2, DATA_ID); + src = (const char *) data->getData(); + srclen = data->getSize(); + } + else + src = luaL_checklstring(L, 2, &srclen); + + size_t dstlen = 0; + char *dst = nullptr; + luax_catchexcept(L, [&](){ dst = Math::instance.decode(format, src, srclen, dstlen); }); + + if (dst != nullptr) + lua_pushlstring(L, dst, dstlen); + else + lua_pushstring(L, ""); + + delete[] dst; + return 1; +} + // C functions in a struct, necessary for the FFI versions of math functions. struct FFI_Math { @@ -441,6 +508,8 @@ static const luaL_Reg functions[] = { "noise", w_noise }, { "compress", w_compress }, { "decompress", w_decompress }, + { "encode", w_encode }, + { "decode", w_decode }, { 0, 0 } }; diff --git a/src/scripts/nogame.lua b/src/scripts/nogame.lua index 892088a9e..0c92a9474 100644 --- a/src/scripts/nogame.lua +++ b/src/scripts/nogame.lua @@ -936,7 +936,8 @@ function love.nogame() love.graphics.setBackgroundColor(136/255, 193/255, 206/255) local function load_image(file, name) - return love.graphics.newImage(love.filesystem.newFileData(file, name:gsub("_", "."), "base64")) + local decoded = love.math.decode("base64", file) + return love.graphics.newImage(love.filesystem.newFileData(decoded, name:gsub("_", "."))) end g_images = {} diff --git a/src/scripts/nogame.lua.h b/src/scripts/nogame.lua.h index 3fe0e1029..e551cf36f 100644 --- a/src/scripts/nogame.lua.h +++ b/src/scripts/nogame.lua.h @@ -3572,12 +3572,15 @@ const unsigned char nogame_lua[] = 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x66, 0x69, 0x6c, 0x65, 0x2c, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x29, 0x0a, + 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x3d, + 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x28, + 0x22, 0x62, 0x61, 0x73, 0x65, 0x36, 0x34, 0x22, 0x2c, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x6e, 0x65, 0x77, 0x46, 0x69, - 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x28, 0x66, 0x69, 0x6c, 0x65, 0x2c, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3a, - 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5f, 0x22, 0x2c, 0x20, 0x22, 0x2e, 0x22, 0x29, 0x2c, 0x20, 0x22, 0x62, - 0x61, 0x73, 0x65, 0x36, 0x34, 0x22, 0x29, 0x29, 0x0a, + 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x28, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x6e, 0x61, + 0x6d, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5f, 0x22, 0x2c, 0x20, 0x22, 0x2e, 0x22, 0x29, 0x29, + 0x29, 0x0a, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x09, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x0a, 0x09, 0x09, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x61, 0x73, 0x74, 0x20, 0x3d,