From 578ba4e15e521263c91907faec0880304dfa5f5e Mon Sep 17 00:00:00 2001 From: Lygaen Date: Mon, 19 Jun 2023 22:28:17 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=84=20Added=20icon=20loading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/net/packets/status/serverlist.cpp | 11 +- src/server.cpp | 14 +++ src/server.h | 9 ++ src/utils/config.cpp | 24 ++++- src/utils/config.h | 13 ++- src/utils/file.cpp | 98 ++++++++++++++++++ src/utils/file.h | 144 ++++++++++++++++++++++++++ 7 files changed, 306 insertions(+), 7 deletions(-) create mode 100644 src/utils/file.cpp create mode 100644 src/utils/file.h diff --git a/src/net/packets/status/serverlist.cpp b/src/net/packets/status/serverlist.cpp index 93a32b3b..76906332 100644 --- a/src/net/packets/status/serverlist.cpp +++ b/src/net/packets/status/serverlist.cpp @@ -28,10 +28,13 @@ void ServerListPacket::write(IStream *stream) Config::inst()->MOTD.getValue().save(description, alloc); document.AddMember("description", description, alloc); - // TODO Add favicon in base64 with prepending "data:image/png;base64," - // rapidjson::Value favicon(rapidjson::kStringType); - // --- load the favicon from config --- - // document.AddMember("favicon", favicon, alloc); + rapidjson::Value favicon(rapidjson::kStringType); + + const std::string &data = Config::inst()->ICON_FILE.getValue().getBase64String(); + std::string f = std::string("data:image/png;base64,") + data; + favicon.SetString(f.c_str(), f.size()); + + document.AddMember("favicon", favicon, alloc); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); diff --git a/src/server.cpp b/src/server.cpp index 5a17e779..be43864a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -28,6 +28,17 @@ Server::~Server() } } +void Server::checks() +{ + const PNGFile &icon = Config::inst()->ICON_FILE.getValue(); + if (icon.getHeight() != icon.getWidth() != 64) + { + // Notchian clients only render 64x64 images + logger::warn("Invalid image ! Check it's resolution (must be 64x64) or just if it's there !"); + Config::inst()->ICON_FILE.setValue(PNGFile()); // Frees memory and deletes config entry + } +} + void Server::start() { std::string addr = Config::inst()->ADDRESS.getValue(); @@ -39,6 +50,9 @@ void Server::start() exit(EXIT_FAILURE); } sock.start(Config::inst()->BACKLOG.getValue()); + + checks(); + logger::info("Server started on %s:%d !", addr.c_str(), port); isRunning = true; diff --git a/src/server.h b/src/server.h index c1d2919e..bff39d87 100644 --- a/src/server.h +++ b/src/server.h @@ -28,6 +28,15 @@ class Server ServerSocket sock; bool isRunning{}; + /** + * @brief Internal Checks + * + * Internal checks that will not + * prevent the integrity of the + * server. + */ + void checks(); + public: /** * @brief Construct a new Server object diff --git a/src/utils/config.cpp b/src/utils/config.cpp index 51b0f4b5..b0daf76c 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -18,7 +18,7 @@ inline rapidjson::Document::ConstMemberIterator Field::canSafelyRead(const ra auto loc = document.FindMember(section); if (loc == document.MemberEnd()) return document.MemberEnd(); - return loc->value.IsObject() ? document.MemberEnd() : loc->value.FindMember(key); + return !loc->value.IsObject() ? document.MemberEnd() : loc->value.FindMember(key); } template @@ -117,6 +117,28 @@ void Field::save(rapidjson::Document &document) writeSafely(document, v); } +template <> +void Field::load(const rapidjson::Document &document) +{ + auto loc = canSafelyRead(document); + if (loc == document.MemberEnd()) + return; + + if (!loc->value.IsString()) + return; + + std::string s = std::string(std::string(loc->value.GetString(), loc->value.GetStringLength())); + if (!s.empty()) + value = PNGFile(s); +} +template <> +void Field::save(rapidjson::Document &document) +{ + rapidjson::Value v; + v.SetString(value.getPath().c_str(), value.getPath().length(), document.GetAllocator()); + writeSafely(document, v); +} + template <> void Field::load(const rapidjson::Document &document) { diff --git a/src/utils/config.h b/src/utils/config.h index 6a7e9e10..e2241eec 100644 --- a/src/utils/config.h +++ b/src/utils/config.h @@ -15,6 +15,7 @@ #include #include #include +#include /** * @brief The Field Object for the ::Config @@ -193,6 +194,13 @@ class Config * should use. */ Field LOGLEVEL = Field("other", "loglevel", std::string("ALL")); + /** + * @brief The Icon File + * + * The icon file that is sent to the server + * while pinging. + */ + Field ICON_FILE = Field("display", "icon_file", PNGFile()); /** * @brief List of all the config fields @@ -202,7 +210,7 @@ class Config * on all of the fields by defining the UF(x) macro. */ #define CONFIG_FIELDS UF(PORT) UF(MOTD) UF(LOGLEVEL) UF(COMPRESSION_LVL) UF(ONLINE_MODE) UF(ADDRESS) \ - UF(BACKLOG) UF(MAX_PLAYERS) + UF(BACKLOG) UF(MAX_PLAYERS) UF(ICON_FILE) /** * @brief The Version Number @@ -226,7 +234,8 @@ class Config * the singleton class-like. * @return ::Config* The instance of the config */ - static Config *inst() + static Config * + inst() { return INSTANCE; } diff --git a/src/utils/file.cpp b/src/utils/file.cpp new file mode 100644 index 00000000..0d7c52ec --- /dev/null +++ b/src/utils/file.cpp @@ -0,0 +1,98 @@ +#include "file.h" +#include +#include +#include +#include + +File::File() : path("") +{ +} + +File::File(std::string path) : path(path) +{ + load(); +} + +File::~File() +{ +} + +bool File::load() +{ + std::ifstream file(path, std::ios::binary | std::ios::ate); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + data.resize(size); + file.read(data.data(), size); + + return file.good(); +} + +void File::setPath(std::string path) +{ + this->path = path; +} + +const std::string &File::getPath() const +{ + return path; +} + +const char *File::getPointer() const +{ + return data.data(); +} + +int File::getSize() const +{ + return data.size(); +} + +PNGFile::PNGFile() : File() +{ +} + +PNGFile::PNGFile(std::string path) : File(path), width(0), height(0) +{ + MemoryStream m; + m.write(reinterpret_cast(const_cast(getPointer())), 1, 15 + 8); + + char buff[3]; + m.read(reinterpret_cast(buff), 0, 3); + + if (buff[0] != 'P' || buff[1] != 'N' || buff[2] != 'G') + return; + + // Crafty way to just skip 12 bytes + m.readLong(); + m.readInt(); + + int32_t temp = m.readInt(); + width = *reinterpret_cast(&temp); + temp = m.readInt(); + height = *reinterpret_cast(&temp); + + char encoded[((getSize() + 2) / 3) * 4]; + EVP_EncodeBlock(reinterpret_cast(encoded), reinterpret_cast(const_cast(getPointer())), getSize()); + base64String = std::string(encoded, ((getSize() + 2) / 3) * 4); +} + +PNGFile::~PNGFile() +{ +} + +unsigned int PNGFile::getWidth() const +{ + return width; +} + +unsigned int PNGFile::getHeight() const +{ + return height; +} + +const std::string &PNGFile::getBase64String() const +{ + return base64String; +} diff --git a/src/utils/file.h b/src/utils/file.h new file mode 100644 index 00000000..1bcfee4a --- /dev/null +++ b/src/utils/file.h @@ -0,0 +1,144 @@ +/** + * @file file.h + * @author Mathieu Cayeux + * @brief The file containing file (lol) loading logic + * @version 0.1 + * @date 2023-06-19 + * + * @copyright Copyright (c) 2023 + * + */ + +#ifndef MINESERVER_FILE_H +#define MINESERVER_FILE_H + +#include +#include + +/** + * @brief File Loader Wrapper + * + */ +class File +{ +private: + std::vector data; + std::string path; + +public: + /** + * @brief Constructs a new File object + * + * Constructs an empty file object, really + * just used for the config or placeholder + * objects. + */ + File(); + /** + * @brief Construct a new File object + * + * Internally calls File::load() + * @param path The Path of the file + */ + File(std::string path); + /** + * @brief Destroy the File object + * + */ + ~File(); + + /** + * @brief Loads the data of the file into ram + * + * @return true file was loaded correctly + * @return false file was not loaded correctly + */ + bool load(); + + /** + * @brief Set the Path of the file + * + * You should call File::load() to load + * the file data afterwards. + * @param path the path of the targeted file + */ + void setPath(std::string path); + /** + * @brief Get the Path object + * + * @warning The path does not assure that the data + * stored in this object contains the data + * of the file at the path. + * @return const std::string& the path of the file + */ + const std::string &getPath() const; + + /** + * @brief Get the pointer to the data + * + * @return const char* the pointer to the file data + */ + const char *getPointer() const; + /** + * @brief Get the size of the file stored + * + * @return int the size of the file stored + */ + int getSize() const; +}; + +/** + * @brief Wrapper around ::File for PNG files + * + */ +class PNGFile : public File +{ +private: + unsigned int width, height; + std::string base64String; + +public: + /** + * @brief Construct a new PNGFile object + * + * Constructs an empty file object, really + * just used for the config or placeholder + * objects. + */ + PNGFile(); + /** + * @brief Construct a new PNGFile object + * + * Internally calls File::load(), then + * does few calculations for loading + * data used in the server. + * @param path The Path of the file + */ + PNGFile(std::string path); + /** + * @brief Destroy the PNGFile object + * + */ + ~PNGFile(); + + /** + * @brief Get the Width of the file + * + * @return unsigned int the width + */ + unsigned int getWidth() const; + /** + * @brief Get the Height of the file + * + * @return unsigned int the height + */ + unsigned int getHeight() const; + /** + * @brief Get the Base64 representation of the file + * + * @return const std::string& the base64 representation + */ + const std::string &getBase64String() const; +}; + +#endif // MINESERVER_FILE_H \ No newline at end of file