From c686b6e6bc389edb08cef2215b0882b2b0ff4b4b Mon Sep 17 00:00:00 2001 From: Christoph Sommer Date: Sun, 11 May 2008 15:20:03 +0000 Subject: [PATCH] Add-on Manager rewrite - Moved Add-on information to an .nfo file inside the .zip - Add-ons can be enabled/disabled via the menu - Add-ons can be toggled on-the-fly and are MD5-checksummed SVN-Revision: 5458 --- CMakeLists.txt | 2 +- src/{ => addon}/addon.cpp | 70 ++++-- src/{ => addon}/addon.hpp | 33 +-- src/addon/addon_manager.cpp | 438 ++++++++++++++++++++++++++++++++++++++ src/{ => addon}/addon_manager.hpp | 55 ++++- src/addon/md5.cpp | 398 ++++++++++++++++++++++++++++++++++ src/addon/md5.hpp | 94 ++++++++ src/addon_manager.cpp | 297 -------------------------- src/gameconfig.cpp | 10 + src/gui/menu.cpp | 6 + src/gui/menu.hpp | 1 + src/lisp/writer.cpp | 13 ++ src/lisp/writer.hpp | 1 + src/main.cpp | 31 +-- src/title.cpp | 78 +++---- src/title.hpp | 6 +- 16 files changed, 1112 insertions(+), 421 deletions(-) rename src/{ => addon}/addon.cpp (57%) rename src/{ => addon}/addon.hpp (70%) create mode 100644 src/addon/addon_manager.cpp rename src/{ => addon}/addon_manager.hpp (56%) create mode 100644 src/addon/md5.cpp create mode 100644 src/addon/md5.hpp delete mode 100644 src/addon_manager.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f23166a21..8c3a6ca4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,7 +158,7 @@ include_directories (${SUPERTUX_SOURCE_DIR}/src/squirrel/include/) ## Build list of sources for supertux binary -FILE(GLOB SUPERTUX_SOURCES RELATIVE ${SUPERTUX_SOURCE_DIR} src/*.cpp src/audio/*.cpp src/badguy/*.cpp src/binreloc/*.cpp src/control/*.cpp src/gui/*.cpp src/lisp/*.cpp src/math/*.cpp src/object/*.cpp src/physfs/*.cpp src/sprite/*.cpp src/tinygettext/*.cpp src/trigger/*.cpp src/video/*.cpp src/worldmap/*.cpp src/scripting/*.cpp src/obstack/*.c) +FILE(GLOB SUPERTUX_SOURCES RELATIVE ${SUPERTUX_SOURCE_DIR} src/*.cpp src/audio/*.cpp src/badguy/*.cpp src/binreloc/*.cpp src/control/*.cpp src/gui/*.cpp src/lisp/*.cpp src/math/*.cpp src/object/*.cpp src/physfs/*.cpp src/sprite/*.cpp src/tinygettext/*.cpp src/trigger/*.cpp src/video/*.cpp src/worldmap/*.cpp src/scripting/*.cpp src/addon/*.cpp src/obstack/*.c) ## Debug options diff --git a/src/addon.cpp b/src/addon/addon.cpp similarity index 57% rename from src/addon.cpp rename to src/addon/addon.cpp index 1b6dbdc2c..4e8c9f6f6 100644 --- a/src/addon.cpp +++ b/src/addon/addon.cpp @@ -19,25 +19,48 @@ // 02111-1307, USA. // +#include #include #include -#include "addon.hpp" -#include "addon_manager.hpp" +#include +#include "addon/addon.hpp" +#include "addon/addon_manager.hpp" +#include "log.hpp" +#include "addon/md5.hpp" -void -Addon::install() +std::string +Addon::get_md5() const { - AddonManager& adm = AddonManager::get_instance(); - adm.install(*this); -} + if (!installed) { + if (stored_md5 == "") log_warning << "Add-on not installed and no stored MD5 available" << std::endl; + return stored_md5; + } -void -Addon::remove() -{ - AddonManager& adm = AddonManager::get_instance(); - adm.remove(*this); + if (calculated_md5 != "") return calculated_md5; + + if (installed_physfs_filename == "") throw std::runtime_error("Tried to calculate MD5 of Add-on with unknown filename"); + + // TODO: this does not work as expected for some files -- IFileStream seems to not always behave like an ifstream. + //IFileStream ifs(installed_physfs_filename); + //std::string md5 = MD5(ifs).hex_digest(); + + MD5 md5; + PHYSFS_file* file; + file = PHYSFS_openRead(installed_physfs_filename.c_str()); + unsigned char buffer[1024]; + while (true) { + PHYSFS_sint64 len = PHYSFS_read(file, buffer, 1, sizeof(buffer)); + if (len <= 0) break; + md5.update(buffer, len); + } + PHYSFS_close(file); + + calculated_md5 = md5.hex_digest(); + log_debug << "MD5 of " << title << ": " << calculated_md5 << std::endl; + + return calculated_md5; } - + void Addon::parse(const lisp::Lisp& lisp) { @@ -47,8 +70,8 @@ Addon::parse(const lisp::Lisp& lisp) lisp.get("author", author); lisp.get("license", license); lisp.get("http-url", http_url); - lisp.get("file", file); - lisp.get("md5", md5); + lisp.get("file", suggested_filename); + lisp.get("md5", stored_md5); } catch(std::exception& e) { std::stringstream msg; msg << "Problem when parsing addoninfo: " << e.what(); @@ -81,8 +104,8 @@ Addon::write(lisp::Writer& writer) const if (author != "") writer.write_string("author", author); if (license != "") writer.write_string("license", license); if (http_url != "") writer.write_string("http-url", http_url); - if (file != "") writer.write_string("file", file); - if (md5 != "") writer.write_string("md5", md5); + if (suggested_filename != "") writer.write_string("file", suggested_filename); + if (stored_md5 != "") writer.write_string("md5", stored_md5); writer.end_list("supertux-addoninfo"); } @@ -94,9 +117,16 @@ Addon::write(std::string fname) const } bool -Addon::equals(const Addon& addon2) const +Addon::operator==(Addon addon2) const { - if ((this->md5 == "") || (addon2.md5 == "")) return (this->title == addon2.title); - return (this->md5 == addon2.md5); + std::string s1 = this->get_md5(); + std::string s2 = addon2.get_md5(); + + if ((s1 != "") && (s2 != "")) return (s1 == s2); + + if (this->title != addon2.title) return false; + if (this->author != addon2.author) return false; + if (this->kind != addon2.kind) return false; + return true; } diff --git a/src/addon.hpp b/src/addon/addon.hpp similarity index 70% rename from src/addon.hpp rename to src/addon/addon.hpp index b65efe704..a63043581 100644 --- a/src/addon.hpp +++ b/src/addon/addon.hpp @@ -27,6 +27,10 @@ #include "lisp/lisp.hpp" #include "lisp/writer.hpp" +class Addon; + +#include "addon/addon_manager.hpp" + /** * Represents an (available or installed) Add-on, e.g. a level set */ @@ -38,20 +42,17 @@ public: std::string author; std::string license; std::string http_url; - std::string file; - std::string md5; - - bool isInstalled; - - /** - * Download and install Add-on - */ - void install(); + std::string suggested_filename; /**< filename suggested by addon author, e.g. "pak0.zip" */ + std::string installed_physfs_filename; /**< PhysFS filename on disk, e.g. "pak0.zip" */ + std::string installed_absolute_filename; /**< complete path and filename on disk, e.g. "/home/sommer/.supertux2/pak0.zip" */ + std::string stored_md5; + bool installed; + bool loaded; /** - * Physically delete Add-on + * Get MD5, based either on installed file's contents or stored value */ - void remove(); + std::string get_md5() const; /** * Read additional information from given contents of a (supertux-addoninfo ...) block @@ -75,10 +76,16 @@ public: /** * Checks if Add-on is the same as given one. - * If available, checks MD5 sum, else relies on title alone. + * If available, checks MD5 sum, else relies on kind, author and title alone. */ - bool equals(const Addon& addon2) const; + bool operator==(Addon addon2) const; + +protected: + friend class AddonManager; + + mutable std::string calculated_md5; + Addon() {}; }; #endif diff --git a/src/addon/addon_manager.cpp b/src/addon/addon_manager.cpp new file mode 100644 index 000000000..695e181e7 --- /dev/null +++ b/src/addon/addon_manager.cpp @@ -0,0 +1,438 @@ +// $Id$ +// +// SuperTux - Add-on Manager +// Copyright (C) 2007 Christoph Sommer +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + +#include +#include +#include +#include +#include +#include +#include +#include "addon/addon_manager.hpp" +#include "config.h" +#include "log.hpp" +#include "lisp/parser.hpp" +#include "lisp/lisp.hpp" +#include "lisp/list_iterator.hpp" +#include "physfs/physfs_stream.hpp" + +#ifdef HAVE_LIBCURL +#include +#include +#include +#endif + +#ifdef HAVE_LIBCURL +namespace { + + size_t my_curl_string_append(void *ptr, size_t size, size_t nmemb, void *string_ptr) + { + std::string& s = *static_cast(string_ptr); + std::string buf(static_cast(ptr), size * nmemb); + s += buf; + log_debug << "read " << size * nmemb << " bytes of data..." << std::endl; + return size * nmemb; + } + + size_t my_curl_physfs_write(void *ptr, size_t size, size_t nmemb, void *f_p) + { + PHYSFS_file* f = static_cast(f_p); + PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb); + log_debug << "read " << size * nmemb << " bytes of data..." << std::endl; + return size * written; + } + +} +#endif + +AddonManager& +AddonManager::get_instance() +{ + static AddonManager instance; + return instance; +} + +AddonManager::AddonManager() +{ +#ifdef HAVE_LIBCURL + curl_global_init(CURL_GLOBAL_ALL); +#endif +} + +AddonManager::~AddonManager() +{ +#ifdef HAVE_LIBCURL + curl_global_cleanup(); +#endif + + for (std::vector::iterator i = addons.begin(); i != addons.end(); i++) delete *i; +} + +std::vector +AddonManager::get_addons() +{ +/* + for (std::vector::iterator it = installed_addons.begin(); it != installed_addons.end(); ++it) { + Addon& addon = *it; + if (addon.md5 == "") addon.md5 = calculate_md5(addon); + } +*/ + return addons; +} + +void +AddonManager::check_online() +{ +#ifdef HAVE_LIBCURL + char error_buffer[CURL_ERROR_SIZE+1]; + + const char* baseUrl = "http://supertux.berlios.de/addons/index.nfo"; + std::string addoninfos = ""; + + CURL *curl_handle; + curl_handle = curl_easy_init(); + curl_easy_setopt(curl_handle, CURLOPT_URL, baseUrl); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL"); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_string_append); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &addoninfos); + curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer); + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); + CURLcode result = curl_easy_perform(curl_handle); + curl_easy_cleanup(curl_handle); + + if (result != CURLE_OK) { + std::string why = error_buffer[0] ? error_buffer : "unhandled error"; + throw std::runtime_error("Downloading Add-on list failed: " + why); + } + + try { + lisp::Parser parser; + std::stringstream addoninfos_stream(addoninfos); + const lisp::Lisp* root = parser.parse(addoninfos_stream, "supertux-addons"); + + const lisp::Lisp* addons_lisp = root->get_lisp("supertux-addons"); + if(!addons_lisp) throw std::runtime_error("Downloaded file is not an Add-on list"); + + lisp::ListIterator iter(addons_lisp); + while(iter.next()) { + const std::string& token = iter.item(); + if(token != "supertux-addoninfo") { + log_warning << "Unknown token '" << token << "' in Add-on list" << std::endl; + continue; + } + Addon* addon_ptr = new Addon(); + Addon& addon = *addon_ptr; + addon.parse(*(iter.lisp())); + addon.installed = false; + addon.loaded = false; + + // make sure the list of known Add-ons does not already contain this one + bool exists = false; + for (std::vector::const_iterator i = addons.begin(); i != addons.end(); i++) { + if (**i == addon) { + exists = true; + break; + } + } + if (exists) { + delete addon_ptr; + continue; + } + + // make sure the Add-on's file name does not contain weird characters + if (addon.suggested_filename.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { + log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl; + delete addon_ptr; + continue; + } + + addons.push_back(addon_ptr); + } + } catch(std::exception& e) { + std::stringstream msg; + msg << "Problem when reading Add-on list: " << e.what(); + throw std::runtime_error(msg.str()); + } + +#endif +} + + +void +AddonManager::install(Addon* addon) +{ +#ifdef HAVE_LIBCURL + + if (addon->installed) throw std::runtime_error("Tried installing installed Add-on"); + + // make sure the Add-on's file name does not contain weird characters + if (addon->suggested_filename.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { + throw std::runtime_error("Add-on has unsafe file name (\""+addon->suggested_filename+"\")"); + } + + std::string fileName = addon->suggested_filename; + + // make sure its file doesn't already exist + if (PHYSFS_exists(fileName.c_str())) { + fileName = addon->stored_md5 + "_" + addon->suggested_filename; + if (PHYSFS_exists(fileName.c_str())) { + throw std::runtime_error("Add-on of suggested filename already exists (\""+addon->suggested_filename+"\", \""+fileName+"\")"); + } + } + + char error_buffer[CURL_ERROR_SIZE+1]; + + char* url = (char*)malloc(addon->http_url.length() + 1); + strncpy(url, addon->http_url.c_str(), addon->http_url.length() + 1); + + PHYSFS_file* f = PHYSFS_openWrite(fileName.c_str()); + + log_debug << "Downloading \"" << url << "\"" << std::endl; + + CURL *curl_handle; + curl_handle = curl_easy_init(); + curl_easy_setopt(curl_handle, CURLOPT_URL, url); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL"); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_physfs_write); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, f); + curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer); + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1); + CURLcode result = curl_easy_perform(curl_handle); + curl_easy_cleanup(curl_handle); + + PHYSFS_close(f); + + free(url); + + if (result != CURLE_OK) { + PHYSFS_delete(fileName.c_str()); + std::string why = error_buffer[0] ? error_buffer : "unhandled error"; + throw std::runtime_error("Downloading Add-on failed: " + why); + } + + addon->installed = true; + addon->installed_physfs_filename = fileName; + static const std::string writeDir = PHYSFS_getWriteDir(); + static const std::string dirSep = PHYSFS_getDirSeparator(); + addon->installed_absolute_filename = writeDir + dirSep + fileName; + addon->loaded = false; + + if (addon->get_md5() != addon->stored_md5) { + addon->installed = false; + PHYSFS_delete(fileName.c_str()); + std::string why = "MD5 checksums differ"; + throw std::runtime_error("Downloading Add-on failed: " + why); + } + + log_debug << "Finished downloading \"" << addon->installed_absolute_filename << "\". Enabling Add-on." << std::endl; + + enable(addon); + +#else + (void) addon; +#endif + +} + +void +AddonManager::remove(Addon* addon) +{ + if (!addon->installed) throw std::runtime_error("Tried removing non-installed Add-on"); + + //FIXME: more checks + + // make sure the Add-on's file name does not contain weird characters + if (addon->installed_physfs_filename.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { + throw std::runtime_error("Add-on has unsafe file name (\""+addon->installed_physfs_filename+"\")"); + } + + unload(addon); + + log_debug << "deleting file \"" << addon->installed_absolute_filename << "\"" << std::endl; + PHYSFS_delete(addon->installed_absolute_filename.c_str()); + addon->installed = false; + + // FIXME: As we don't know anything more about it (e.g. where to get it), remove it from list of known Add-ons +} + +void +AddonManager::disable(Addon* addon) +{ + unload(addon); + + std::string fileName = addon->installed_physfs_filename; + if (std::find(ignored_addon_filenames.begin(), ignored_addon_filenames.end(), fileName) == ignored_addon_filenames.end()) { + ignored_addon_filenames.push_back(fileName); + } +} + +void +AddonManager::enable(Addon* addon) +{ + load(addon); + + std::string fileName = addon->installed_physfs_filename; + std::vector::iterator i = std::find(ignored_addon_filenames.begin(), ignored_addon_filenames.end(), fileName); + if (i != ignored_addon_filenames.end()) { + ignored_addon_filenames.erase(i); + } +} + +void +AddonManager::unload(Addon* addon) +{ + if (!addon->installed) throw std::runtime_error("Tried unloading non-installed Add-on"); + if (!addon->loaded) return; + + log_debug << "Removing archive \"" << addon->installed_absolute_filename << "\" from search path" << std::endl; + if (PHYSFS_removeFromSearchPath(addon->installed_absolute_filename.c_str()) == 0) { + log_warning << "Could not remove " << addon->installed_absolute_filename << " from search path. Ignoring." << std::endl; + return; + } + + addon->loaded = false; +} + +void +AddonManager::load(Addon* addon) +{ + if (!addon->installed) throw std::runtime_error("Tried loading non-installed Add-on"); + if (addon->loaded) return; + + log_debug << "Adding archive \"" << addon->installed_absolute_filename << "\" to search path" << std::endl; + if (PHYSFS_addToSearchPath(addon->installed_absolute_filename.c_str(), 0) == 0) { + log_warning << "Could not add " << addon->installed_absolute_filename << " to search path. Ignoring." << std::endl; + return; + } + + addon->loaded = true; +} + +void +AddonManager::load_addons() +{ + // unload all Addons and forget about them + for (std::vector::iterator i = addons.begin(); i != addons.end(); i++) { + if ((*i)->installed && (*i)->loaded) unload(*i); + delete *i; + } + addons.clear(); + + // Search for archives and add them to the search path + char** rc = PHYSFS_enumerateFiles("/"); + + for(char** i = rc; *i != 0; ++i) { + + // get filename of potential archive + std::string fileName = *i; + + static const std::string archiveDir = PHYSFS_getRealDir(fileName.c_str()); + static const std::string dirSep = PHYSFS_getDirSeparator(); + std::string fullFilename = archiveDir + dirSep + fileName; + + /* + // make sure it's in the writeDir + static const std::string writeDir = PHYSFS_getWriteDir(); + if (fileName.compare(0, writeDir.length(), writeDir) != 0) continue; + */ + + // make sure it looks like an archive + static const std::string archiveExt = ".zip"; + if (fullFilename.compare(fullFilename.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue; + + // make sure it exists + struct stat stats; + if (stat(fullFilename.c_str(), &stats) != 0) continue; + + // make sure it's an actual file + if (!S_ISREG(stats.st_mode)) continue; + + log_debug << "Found archive \"" << fullFilename << "\"" << std::endl; + + // add archive to search path + PHYSFS_addToSearchPath(fullFilename.c_str(), 0); + + // Search for infoFiles + std::string infoFileName = ""; + char** rc2 = PHYSFS_enumerateFiles("/"); + for(char** i = rc2; *i != 0; ++i) { + + // get filename of potential infoFile + std::string potentialInfoFileName = *i; + + // make sure it looks like an infoFile + static const std::string infoExt = ".nfo"; + if (potentialInfoFileName.compare(potentialInfoFileName.length()-infoExt.length(), infoExt.length(), infoExt) != 0) continue; + + // make sure it's in the current archive + std::string infoFileDir = PHYSFS_getRealDir(potentialInfoFileName.c_str()); + if (infoFileDir != fullFilename) continue; + + // found infoFileName + infoFileName = potentialInfoFileName; + break; + } + PHYSFS_freeList(rc2); + + // if we have an infoFile, it's an Addon + if (infoFileName != "") { + try { + Addon* addon = new Addon(); + addon->parse(infoFileName); + addon->installed = true; + addon->installed_physfs_filename = fileName; + addon->installed_absolute_filename = fullFilename; + addon->loaded = true; + addons.push_back(addon); + + // check if the Addon is disabled + if (std::find(ignored_addon_filenames.begin(), ignored_addon_filenames.end(), fileName) != ignored_addon_filenames.end()) { + unload(addon); + } + + } catch (const std::runtime_error& e) { + log_warning << "Could not load add-on info for " << fullFilename << ", loading as unmanaged:" << e.what() << std::endl; + } + } + + } + + PHYSFS_freeList(rc); +} + + +void +AddonManager::read_config(const lisp::Lisp& lisp) +{ + lisp.get_vector("disabled-addons", ignored_addon_filenames); +} + +void +AddonManager::write_config(lisp::Writer& writer) +{ + writer.write_string_vector("disabled-addons", ignored_addon_filenames); +} + diff --git a/src/addon_manager.hpp b/src/addon/addon_manager.hpp similarity index 56% rename from src/addon_manager.hpp rename to src/addon/addon_manager.hpp index 23b3415c7..26c80f4ef 100644 --- a/src/addon_manager.hpp +++ b/src/addon/addon_manager.hpp @@ -23,7 +23,7 @@ #include #include -#include "addon.hpp" +#include "addon/addon.hpp" /** * Checks for, installs and removes Add-ons @@ -34,26 +34,67 @@ public: /** * returns a list of installed Add-ons */ - std::vector get_installed_addons() const; - + std::vector get_addons(); + /** - * returns a list of available Add-ons + * downloads list of available Add-ons */ - std::vector get_available_addons() const; + void check_online(); /** * Download and install Add-on */ - void install(const Addon& addon); + void install(Addon* addon); /** * Physically delete Add-on */ - void remove(const Addon& addon); + void remove(Addon* addon); + + /** + * Unload Add-on and mark as not to be loaded automatically + */ + void disable(Addon* addon); + + /** + * Load Add-on and mark as to be loaded automatically + */ + void enable(Addon* addon); + + /** + * Remove Add-on from search path + */ + void unload(Addon* addon); + + /** + * Add Add-on to search path + */ + void load(Addon* addon); + /** + * Loads all enabled Add-ons, i.e. adds them to the search path + */ + void load_addons(); + + /** + * Returns the shared AddonManager instance + */ static AddonManager& get_instance(); + /** + * Write AddonManager configuration to Lisp + */ + void write_config(lisp::Writer& writer); + + /** + * Read AddonManager configuration from Lisp + */ + void read_config(const lisp::Lisp& lisp); + protected: + std::vector addons; + std::vector ignored_addon_filenames; + AddonManager(); ~AddonManager(); }; diff --git a/src/addon/md5.cpp b/src/addon/md5.cpp new file mode 100644 index 000000000..30207216e --- /dev/null +++ b/src/addon/md5.cpp @@ -0,0 +1,398 @@ +// +// MD5 message-digest algorithm +// Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. +// +// C++/object oriented translation and modification: +// Copyright (C) 1995 Mordechai T. Abzug +// +// Further adaptations for SuperTux: +// Copyright (C) 2008 Christoph Sommer +// +// This translation/modification is provided "as is," without express or +// implied warranty of any kind. +// +// The translators/modifiers do not claim: +// (1) that MD5 will do what you think it does; +// (2) that this translation/ modification is accurate; or +// (3) that this software is "merchantible." +// +// based on: +// +// MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm +// MDDRIVER.C - test driver for MD2, MD4 and MD5 +// Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. +// +// License to copy and use this software is granted provided that it +// is identified as the "RSA Data Security, Inc. MD5 Message-Digest +// Algorithm" in all material mentioning or referencing this software +// or this function. +// +// License is also granted to make and use derivative works provided +// that such works are identified as "derived from the RSA Data +// Security, Inc. MD5 Message-Digest Algorithm" in all material +// mentioning or referencing the derived work. +// +// RSA Data Security, Inc. makes no representations concerning either +// the merchantability of this software or the suitability of this +// software for any particular purpose. It is provided "as is" +// without express or implied warranty of any kind. +// +// These notices must be retained in any copies of any part of this +// documentation and/or software. +// + +#include "md5.hpp" + +#include +#include +#include +#include + +MD5::MD5() { + init(); +} + +void MD5::update (uint8_t* input, uint32_t input_length) { + + uint32_t input_index, buffer_index; + uint32_t buffer_space; // how much space is left in buffer + + if (finalized) throw std::runtime_error("MD5::update: Can't update a finalized digest!"); + + // Compute number of bytes mod 64 + buffer_index = (unsigned int)((count[0] >> 3) & 0x3F); + + // Update number of bits + if ( (count[0] += ((uint32_t) input_length << 3))<((uint32_t) input_length << 3) ) count[1]++; + + count[1] += ((uint32_t)input_length >> 29); + + + buffer_space = 64 - buffer_index; // how much space is left in buffer + + // Transform as many times as possible. + if (input_length >= buffer_space) { // ie. we have enough to fill the buffer + // fill the rest of the buffer and transform + memcpy (buffer + buffer_index, input, buffer_space); + transform (buffer); + + // now, transform each 64-byte piece of the input, bypassing the buffer + for (input_index = buffer_space; input_index + 63 < input_length; + input_index += 64) + transform (input+input_index); + + buffer_index = 0; // so we can buffer remaining + } else + input_index=0; // so we can buffer the whole input + + + // and here we do the buffering: + memcpy(buffer+buffer_index, input+input_index, input_length-input_index); +} + +void MD5::update(FILE *file) { + uint8_t buffer[1024]; + int len; + + while ((len=fread(buffer, 1, 1024, file))) update(buffer, len); + + fclose (file); +} + +void MD5::update(std::istream& stream) { + uint8_t buffer[1024]; + int len; + + while (stream.good()) { + stream.read((char*)buffer, 1024); // note that return value of read is unusable. + len=stream.gcount(); + update(buffer, len); + } +} + +void MD5::update(std::ifstream& stream) { + uint8_t buffer[1024]; + int len; + + while (stream.good()) { + stream.read((char*)buffer, 1024); // note that return value of read is unusable. + len=stream.gcount(); + update(buffer, len); + } +} + + +MD5::MD5(FILE *file) { + init(); // must be called be all constructors + update(file); + finalize (); +} + +MD5::MD5(std::istream& stream) { + init(); // must called by all constructors + update (stream); + finalize(); +} + +MD5::MD5(std::ifstream& stream) { + init(); // must called by all constructors + update (stream); + finalize(); +} + +uint8_t* MD5::raw_digest() { + uint8_t* s = new uint8_t[16]; + + finalize(); + + memcpy(s, digest, 16); + return s; +} + +std::string MD5::hex_digest() { + int i; + char* s= new char[33]; + + finalize(); + + for (i=0; i<16; i++) sprintf(s+i*2, "%02x", digest[i]); + + s[32]='\0'; + + return s; +} + +std::ostream& operator<<(std::ostream &stream, MD5 context) { + stream << context.hex_digest(); + return stream; +} + + +// PRIVATE METHODS: + +void MD5::init() { + finalized=false; + + // Nothing counted, so count=0 + count[0] = 0; + count[1] = 0; + + // Load magic initialization constants. + state[0] = 0x67452301; + state[1] = 0xefcdab89; + state[2] = 0x98badcfe; + state[3] = 0x10325476; +} + +void MD5::finalize() { + if (finalized) return; + + uint8_t bits[8]; + unsigned int index, padLen; + static uint8_t PADDING[64]={ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + // Save number of bits + encode (bits, count, 8); + + // Pad out to 56 mod 64. + index = (uint32_t) ((count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + update (PADDING, padLen); + + // Append length (before padding) + update (bits, 8); + + // Store state in digest + encode (digest, state, 16); + + // Zeroize sensitive information + memset (buffer, 0, sizeof(*buffer)); + + finalized=true; +} + +// Constants for MD5Transform routine. +// Although we could use C++ style constants, defines are actually better, +// since they let us easily evade scope clashes. + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +void MD5::transform (uint8_t block[64]) { + uint32_t a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + decode (x, block, 64); + + assert(!finalized); // not just a user error, since the method is private + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x02441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x04881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + // Zeroize sensitive information. + memset ( (uint8_t* ) x, 0, sizeof(x)); +} + +void MD5::encode (uint8_t* output, uint32_t* input, uint32_t len) { + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (uint8_t) (input[i] & 0xff); + output[j+1] = (uint8_t) ((input[i] >> 8) & 0xff); + output[j+2] = (uint8_t) ((input[i] >> 16) & 0xff); + output[j+3] = (uint8_t) ((input[i] >> 24) & 0xff); + } +} + +void MD5::decode (uint32_t* output, uint8_t* input, uint32_t len) { + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[i] = ((uint32_t)input[j]) | (((uint32_t)input[j+1]) << 8) | (((uint32_t)input[j+2]) << 16) | (((uint32_t)input[j+3]) << 24); + } +} + +// Note: Replace "for loop" with standard memcpy if possible. +void MD5::memcpy (uint8_t* output, uint8_t* input, uint32_t len) { + unsigned int i; + + for (i = 0; i < len; i++) output[i] = input[i]; +} + +// Note: Replace "for loop" with standard memset if possible. +void MD5::memset (uint8_t* output, uint8_t value, uint32_t len) { + unsigned int i; + + for (i = 0; i < len; i++) output[i] = value; +} + +inline unsigned int MD5::rotate_left(uint32_t x, uint32_t n) { + return (x << n) | (x >> (32-n)); +} + +inline unsigned int MD5::F(uint32_t x, uint32_t y, uint32_t z) { + return (x & y) | (~x & z); +} + +inline unsigned int MD5::G(uint32_t x, uint32_t y, uint32_t z) { + return (x & z) | (y & ~z); +} + +inline unsigned int MD5::H(uint32_t x, uint32_t y, uint32_t z) { + return x ^ y ^ z; +} + +inline unsigned int MD5::I(uint32_t x, uint32_t y, uint32_t z) { + return y ^ (x | ~z); +} + +inline void MD5::FF(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac) { + a += F(b, c, d) + x + ac; + a = rotate_left (a, s) +b; +} + +inline void MD5::GG(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac) { + a += G(b, c, d) + x + ac; + a = rotate_left (a, s) +b; +} + +inline void MD5::HH(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac) { + a += H(b, c, d) + x + ac; + a = rotate_left (a, s) +b; +} + +inline void MD5::II(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac) { + a += I(b, c, d) + x + ac; + a = rotate_left (a, s) +b; +} diff --git a/src/addon/md5.hpp b/src/addon/md5.hpp new file mode 100644 index 000000000..6d1e9216f --- /dev/null +++ b/src/addon/md5.hpp @@ -0,0 +1,94 @@ +// +// MD5 message-digest algorithm +// Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. +// +// C++/object oriented translation and modification: +// Copyright (C) 1995 Mordechai T. Abzug +// +// Further adaptations for SuperTux: +// Copyright (C) 2008 Christoph Sommer +// +// This translation/modification is provided "as is," without express or +// implied warranty of any kind. +// +// The translators/modifiers do not claim: +// (1) that MD5 will do what you think it does; +// (2) that this translation/ modification is accurate; or +// (3) that this software is "merchantible." +// +// based on: +// +// MD5.H - header file for MD5C.C +// MDDRIVER.C - test driver for MD2, MD4 and MD5 +// Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. +// +// License to copy and use this software is granted provided that it +// is identified as the "RSA Data Security, Inc. MD5 Message-Digest +// Algorithm" in all material mentioning or referencing this software +// or this function. +// +// License is also granted to make and use derivative works provided +// that such works are identified as "derived from the RSA Data +// Security, Inc. MD5 Message-Digest Algorithm" in all material +// mentioning or referencing the derived work. +// +// RSA Data Security, Inc. makes no representations concerning either +// the merchantability of this software or the suitability of this +// software for any particular purpose. It is provided "as is" +// without express or implied warranty of any kind. +// +// These notices must be retained in any copies of any part of this +// documentation and/or software. +// + +#include +#include +#include +#include +#include + +class MD5 { + + public: + MD5(); + MD5(uint8_t* string); /**< digest string, finalize */ + MD5(std::istream& stream); /**< digest stream, finalize */ + MD5(FILE *file); /**< digest file, close, finalize */ + MD5(std::ifstream& stream); /**< digest stream, close, finalize */ + + void update(uint8_t* input, unsigned int input_length); /**< MD5 block update operation. Continues an MD5 message-digest operation, processing another message block, and updating the context. */ + void update(std::istream& stream); + void update(FILE *file); + void update(std::ifstream& stream); + + uint8_t* raw_digest(); /**< digest as a 16-byte binary array */ + std::string hex_digest(); /**< digest as a 33-byte ascii-hex string */ + friend std::ostream& operator<<(std::ostream&, MD5 context); + + private: + uint32_t state[4]; + uint32_t count[2]; /**< number of _bits_, mod 2^64 */ + uint8_t buffer[64]; /**< input buffer */ + uint8_t digest[16]; + bool finalized; + + void init(); /**< called by all constructors */ + void finalize(); /**< MD5 finalization. Ends an MD5 message-digest operation, writing the the message digest and zeroizing the context. */ + void transform(uint8_t* buffer); /**< MD5 basic transformation. Transforms state based on block. Does the real update work. Note that length is implied to be 64. */ + + static void encode(uint8_t* dest, uint32_t* src, uint32_t length); /**< Encodes input (uint32_t) into output (uint8_t). Assumes len is a multiple of 4. */ + static void decode(uint32_t* dest, uint8_t* src, uint32_t length); /**< Decodes input (uint8_t) into output (uint32_t). Assumes len is a multiple of 4. */ + static void memcpy(uint8_t* dest, uint8_t* src, uint32_t length); + static void memset(uint8_t* start, uint8_t val, uint32_t length); + + static inline uint32_t rotate_left(uint32_t x, uint32_t n); + static inline uint32_t F(uint32_t x, uint32_t y, uint32_t z); //*< F, G, H and I are basic MD5 functions. */ + static inline uint32_t G(uint32_t x, uint32_t y, uint32_t z); + static inline uint32_t H(uint32_t x, uint32_t y, uint32_t z); + static inline uint32_t I(uint32_t x, uint32_t y, uint32_t z); + static inline void FF(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac); /**< FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. Rotation is separate from addition to prevent recomputation. */ + static inline void GG(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac); + static inline void HH(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac); + static inline void II(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, uint32_t ac); + +}; diff --git a/src/addon_manager.cpp b/src/addon_manager.cpp deleted file mode 100644 index b59dde094..000000000 --- a/src/addon_manager.cpp +++ /dev/null @@ -1,297 +0,0 @@ -// $Id$ -// -// SuperTux - Add-on Manager -// Copyright (C) 2007 Christoph Sommer -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -// 02111-1307, USA. -// - -#include -#include -#include -#include -#include -#include -#include -#include "addon_manager.hpp" -#include "config.h" -#include "log.hpp" -#include "lisp/parser.hpp" -#include "lisp/lisp.hpp" -#include "lisp/list_iterator.hpp" - -#ifdef HAVE_LIBCURL -#include -#include -#include -#endif - -#ifdef HAVE_LIBCURL -namespace { - - size_t my_curl_string_append(void *ptr, size_t size, size_t nmemb, void *string_ptr) - { - std::string& s = *static_cast(string_ptr); - std::string buf(static_cast(ptr), size * nmemb); - s += buf; - log_debug << "read " << size * nmemb << " bytes of data..." << std::endl; - return size * nmemb; - } - - size_t my_curl_physfs_write(void *ptr, size_t size, size_t nmemb, void *f_p) - { - PHYSFS_file* f = static_cast(f_p); - PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb); - log_debug << "read " << size * nmemb << " bytes of data..." << std::endl; - return size * written; - } - -} -#endif - -AddonManager& -AddonManager::get_instance() -{ - static AddonManager instance; - return instance; -} - -AddonManager::AddonManager() -{ -#ifdef HAVE_LIBCURL - curl_global_init(CURL_GLOBAL_ALL); -#endif -} - -AddonManager::~AddonManager() -{ -#ifdef HAVE_LIBCURL - curl_global_cleanup(); -#endif -} - -std::vector -AddonManager::get_installed_addons() const -{ - std::vector addons; - - // iterate over complete search path (i.e. directories and archives) - char **i = PHYSFS_getSearchPath(); - if (!i) throw std::runtime_error("Could not query physfs search path"); - for (; *i != NULL; i++) { - - // get filename of potential archive - std::string fileName = *i; - - // make sure it's in the writeDir - static const std::string writeDir = PHYSFS_getWriteDir(); - if (fileName.compare(0, writeDir.length(), writeDir) != 0) continue; - - // make sure it looks like an archive - static const std::string archiveExt = ".zip"; - if (fileName.compare(fileName.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue; - - // make sure it exists - struct stat stats; - if (stat(fileName.c_str(), &stats) != 0) continue; - - // make sure it's an actual file - if (!S_ISREG(stats.st_mode)) continue; - - Addon addon; - - // extract nice title as fallback for when the Add-on has no addoninfo file - static const char* dirSep = PHYSFS_getDirSeparator(); - std::string::size_type n = fileName.rfind(dirSep) + 1; - if (n == std::string::npos) n = 0; - addon.title = fileName.substr(n, fileName.length() - n - archiveExt.length()); - std::string shortFileName = fileName.substr(n, fileName.length() - n); - addon.file = shortFileName; - - // read an accompaining .nfo file, if it exists - static const std::string infoExt = ".nfo"; - std::string infoFileName = fileName.substr(n, fileName.length() - n - archiveExt.length()) + infoExt; - if (PHYSFS_exists(infoFileName.c_str())) { - addon.parse(infoFileName); - if (addon.file != shortFileName) { - log_warning << "Add-on \"" << addon.title << "\", contained in file \"" << shortFileName << "\" is accompained by an addoninfo file that specifies \"" << addon.file << "\" as the Add-on's file name. Skipping." << std::endl; - } - } - - // make sure the Add-on's file name does not contain weird characters - if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { - log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl; - continue; - } - - addon.isInstalled = true; - addons.push_back(addon); - } - - return addons; -} - -std::vector -AddonManager::get_available_addons() const -{ - std::vector addons; - -#ifdef HAVE_LIBCURL - - char error_buffer[CURL_ERROR_SIZE+1]; - - const char* baseUrl = "http://supertux.berlios.de/addons/index.nfo"; - std::string addoninfos = ""; - - CURL *curl_handle; - curl_handle = curl_easy_init(); - curl_easy_setopt(curl_handle, CURLOPT_URL, baseUrl); - curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL"); - curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_string_append); - curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &addoninfos); - curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer); - curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1); - curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1); - curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); - CURLcode result = curl_easy_perform(curl_handle); - curl_easy_cleanup(curl_handle); - - if (result != CURLE_OK) { - std::string why = error_buffer[0] ? error_buffer : "unhandled error"; - throw std::runtime_error("Downloading Add-on list failed: " + why); - } - - try { - lisp::Parser parser; - std::stringstream addoninfos_stream(addoninfos); - const lisp::Lisp* root = parser.parse(addoninfos_stream, "supertux-addons"); - - const lisp::Lisp* addons_lisp = root->get_lisp("supertux-addons"); - if(!addons_lisp) throw std::runtime_error("Downloaded file is not an Add-on list"); - - lisp::ListIterator iter(addons_lisp); - while(iter.next()) { - const std::string& token = iter.item(); - if(token == "supertux-addoninfo") { - Addon addon; - addon.parse(*(iter.lisp())); - - // make sure the Add-on's file name does not contain weird characters - if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { - log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl; - continue; - } - - addon.isInstalled = false; - addons.push_back(addon); - } else { - log_warning << "Unknown token '" << token << "' in Add-on list" << std::endl; - } - } - } catch(std::exception& e) { - std::stringstream msg; - msg << "Problem when reading Add-on list: " << e.what(); - throw std::runtime_error(msg.str()); - } - -#endif - - return addons; -} - - -void -AddonManager::install(const Addon& addon) -{ - // make sure the Add-on's file name does not contain weird characters - if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { - throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")"); - } - -#ifdef HAVE_LIBCURL - - char error_buffer[CURL_ERROR_SIZE+1]; - - char* url = (char*)malloc(addon.http_url.length() + 1); - strncpy(url, addon.http_url.c_str(), addon.http_url.length() + 1); - - PHYSFS_file* f = PHYSFS_openWrite(addon.file.c_str()); - - log_debug << "Downloading \"" << url << "\"" << std::endl; - - CURL *curl_handle; - curl_handle = curl_easy_init(); - curl_easy_setopt(curl_handle, CURLOPT_URL, url); - curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL"); - curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_physfs_write); - curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, f); - curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer); - curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1); - curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1); - CURLcode result = curl_easy_perform(curl_handle); - curl_easy_cleanup(curl_handle); - - PHYSFS_close(f); - - free(url); - - if (result != CURLE_OK) { - PHYSFS_delete(addon.file.c_str()); - std::string why = error_buffer[0] ? error_buffer : "unhandled error"; - throw std::runtime_error("Downloading Add-on failed: " + why); - } - - // write an accompaining .nfo file - static const std::string archiveExt = ".zip"; - static const std::string infoExt = ".nfo"; - std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt; - addon.write(infoFileName); - - static const std::string writeDir = PHYSFS_getWriteDir(); - static const std::string dirSep = PHYSFS_getDirSeparator(); - std::string fullFilename = writeDir + dirSep + addon.file; - log_debug << "Finished downloading \"" << fullFilename << "\"" << std::endl; - PHYSFS_addToSearchPath(fullFilename.c_str(), 1); -#else - (void) addon; -#endif - -} - -void -AddonManager::remove(const Addon& addon) -{ - // make sure the Add-on's file name does not contain weird characters - if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { - throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")"); - } - - log_debug << "deleting file \"" << addon.file << "\"" << std::endl; - PHYSFS_removeFromSearchPath(addon.file.c_str()); - PHYSFS_delete(addon.file.c_str()); - - // remove an accompaining .nfo file - static const std::string archiveExt = ".zip"; - static const std::string infoExt = ".nfo"; - std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt; - if (PHYSFS_exists(infoFileName.c_str())) { - log_debug << "deleting file \"" << infoFileName << "\"" << std::endl; - PHYSFS_delete(infoFileName.c_str()); - } -} - diff --git a/src/gameconfig.cpp b/src/gameconfig.cpp index 6a92e35ac..c717af56b 100644 --- a/src/gameconfig.cpp +++ b/src/gameconfig.cpp @@ -30,6 +30,7 @@ #include "control/joystickkeyboardcontroller.hpp" #include "resources.hpp" #include "main.hpp" +#include "addon/addon_manager.hpp" Config* config = 0; @@ -97,6 +98,11 @@ Config::load() if(config_control_lisp && main_controller) { main_controller->read(*config_control_lisp); } + + const lisp::Lisp* config_addons_lisp = config_lisp->get_lisp("addons"); + if(config_addons_lisp) { + AddonManager::get_instance().read_config(*config_addons_lisp); + } } void @@ -131,5 +137,9 @@ Config::save() writer.end_list("control"); } + writer.start_list("addons"); + AddonManager::get_instance().write_config(writer); + writer.end_list("addons"); + writer.end_list("supertux-config"); } diff --git a/src/gui/menu.cpp b/src/gui/menu.cpp index 255835964..dda904861 100644 --- a/src/gui/menu.cpp +++ b/src/gui/menu.cpp @@ -893,6 +893,12 @@ Menu::is_toggled(int id) const return get_item_by_id(id).toggled; } +void +Menu::set_toggled(int id, bool toggled) +{ + get_item_by_id(id).toggled = toggled; +} + Menu* Menu::get_parent() const { diff --git a/src/gui/menu.hpp b/src/gui/menu.hpp index fc7dca486..7c6605cb9 100644 --- a/src/gui/menu.hpp +++ b/src/gui/menu.hpp @@ -189,6 +189,7 @@ public: void event(const SDL_Event& event); bool is_toggled(int id) const; + void set_toggled(int id, bool toggled); Menu* get_parent() const; diff --git a/src/lisp/writer.cpp b/src/lisp/writer.cpp index d2c7b1c5e..99ff42ba8 100644 --- a/src/lisp/writer.cpp +++ b/src/lisp/writer.cpp @@ -164,6 +164,19 @@ Writer::write_float_vector(const std::string& name, } void +Writer::write_string_vector(const std::string& name, + const std::vector& value) +{ + indent(); + *out << '(' << name; + for(std::vector::const_iterator i = value.begin(); i != value.end(); ++i) { + *out << " "; + write_escaped_string(*i); + } + *out << ")\n"; +} + +void Writer::write_escaped_string(const std::string& str) { *out << '"'; diff --git a/src/lisp/writer.hpp b/src/lisp/writer.hpp index ba5d327ab..c2dad8d4d 100644 --- a/src/lisp/writer.hpp +++ b/src/lisp/writer.hpp @@ -46,6 +46,7 @@ namespace lisp void write_int_vector(const std::string& name, const std::vector& value); void write_int_vector(const std::string& name, const std::vector& value); void write_float_vector(const std::string& name, const std::vector& value); + void write_string_vector(const std::string& name, const std::vector& value); // add more write-functions when needed... void end_list(const std::string& listname); diff --git a/src/main.cpp b/src/main.cpp index 15c06a708..500688253 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,6 +59,7 @@ namespace supertux_apple { #include "physfs/physfs_sdl.hpp" #include "random_generator.hpp" #include "worldmap/worldmap.hpp" +#include "addon/addon_manager.hpp" #include "binreloc/binreloc.h" namespace { DrawingContext *context_pointer; } @@ -98,11 +99,13 @@ static void init_physfs(const char* argv0) throw std::runtime_error(msg.str()); } + // allow symbolic links + PHYSFS_permitSymbolicLinks(1); + // Initialize physfs (this is a slightly modified version of // PHYSFS_setSaneConfig const char* application = "supertux2"; //instead of PACKAGE_NAME so we can coexist with MS1 const char* userdir = PHYSFS_getUserDir(); - const char* dirsep = PHYSFS_getDirSeparator(); char* writedir = new char[strlen(userdir) + strlen(application) + 2]; // Set configuration directory @@ -132,27 +135,6 @@ static void init_physfs(const char* argv0) PHYSFS_addToSearchPath(writedir, 0); delete[] writedir; - // Search for archives and add them to the search path - const char* archiveExt = "zip"; - char** rc = PHYSFS_enumerateFiles("/"); - size_t extlen = strlen(archiveExt); - - for(char** i = rc; *i != 0; ++i) { - size_t l = strlen(*i); - if((l > extlen) && ((*i)[l - extlen - 1] == '.')) { - const char* ext = (*i) + (l - extlen); - if(strcasecmp(ext, archiveExt) == 0) { - const char* d = PHYSFS_getRealDir(*i); - char* str = new char[strlen(d) + strlen(dirsep) + l + 1]; - sprintf(str, "%s%s%s", d, dirsep, *i); - PHYSFS_addToSearchPath(str, 1); - delete[] str; - } - } - } - - PHYSFS_freeList(rc); - // when started from source dir... std::string dir = PHYSFS_getBaseDir(); dir += "/data"; @@ -224,9 +206,6 @@ static void init_physfs(const char* argv0) #endif } - // allow symbolic links - PHYSFS_permitSymbolicLinks(1); - //show search Path char** searchpath = PHYSFS_getSearchPath(); for(char** i = searchpath; *i != NULL; i++) @@ -541,6 +520,8 @@ int main(int argc, char** argv) main_controller = new JoystickKeyboardController(); timelog("config"); init_config(); + timelog("addons"); + AddonManager::get_instance().load_addons(); timelog("tinygettext"); init_tinygettext(); timelog("commandline"); diff --git a/src/title.cpp b/src/title.cpp index 612766380..bf5776a1f 100644 --- a/src/title.cpp +++ b/src/title.cpp @@ -66,7 +66,7 @@ #include "options_menu.hpp" #include "console.hpp" #include "random_generator.hpp" -#include "addon_manager.hpp" +#include "addon/addon_manager.hpp" enum MainMenuIDs { MNID_STARTGAME, @@ -202,9 +202,9 @@ TitleScreen::check_contrib_world_menu() } namespace { - bool generate_addons_menu_sorter(const Addon& a1, const Addon& a2) + bool generate_addons_menu_sorter(const Addon* a1, const Addon* a2) { - return a1.title < a2.title; + return a1->title < a2->title; } const int ADDON_LIST_START_ID = 10; @@ -215,43 +215,12 @@ TitleScreen::generate_addons_menu() { AddonManager& adm = AddonManager::get_instance(); - // refresh list of installed addons - installed_addons = adm.get_installed_addons(); + // refresh list of addons + addons = adm.get_addons(); - // build new Add-on list - addons.clear(); - - // add installed addons to list - addons.insert(addons.end(), installed_addons.begin(), installed_addons.end()); - - // add available addons to list - addons.insert(addons.end(), available_addons.begin(), available_addons.end()); - // sort list std::sort(addons.begin(), addons.end(), generate_addons_menu_sorter); - // remove available addons that are already installed - std::vector::iterator it2 = addons.begin(); - while (it2 != addons.end()) { - Addon addon = *it2; - if (addon.isInstalled) { - bool restart = false; - for (std::vector::iterator it = addons.begin(); it != addons.end(); ++it) { - Addon addon2 = *it; - if ((addon2.equals(addon)) && (!addon2.isInstalled)) { - addons.erase(it); - restart = true; - break; - } - } - if (restart) { - it2 = addons.begin(); - continue; - } - } - it2++; - } - // (re)generate menu free_addons_menu(); addons_menu.reset(new Menu()); @@ -268,12 +237,12 @@ TitleScreen::generate_addons_menu() //addons_menu->add_hl(); for (unsigned int i = 0; i < addons.size(); i++) { - Addon addon = addons[i]; + const Addon& addon = *addons[i]; std::string text = ""; if (addon.kind != "") text += addon.kind + " "; text += std::string("\"") + addon.title + "\""; if (addon.author != "") text += " by \"" + addon.author + "\""; - addons_menu->add_toggle(ADDON_LIST_START_ID + i, text, addon.isInstalled); + addons_menu->add_toggle(ADDON_LIST_START_ID + i, text, addon.loaded); } addons_menu->add_hl(); @@ -289,7 +258,7 @@ TitleScreen::check_addons_menu() // check if "Check Online" was chosen if (index == 0) { try { - available_addons = AddonManager::get_instance().get_available_addons(); + AddonManager::get_instance().check_online(); generate_addons_menu(); Menu::set_current(addons_menu.get()); addons_menu->set_active_item(index); @@ -302,32 +271,33 @@ TitleScreen::check_addons_menu() // if one of the Addons listed was chosen, take appropriate action if ((index >= ADDON_LIST_START_ID) && (index < ADDON_LIST_START_ID) + addons.size()) { - Addon addon = addons[index - ADDON_LIST_START_ID]; - if (!addon.isInstalled) { + Addon& addon = *addons[index - ADDON_LIST_START_ID]; + if (!addon.installed) { try { - addon.install(); - //generate_addons_menu(); - //Menu::set_current(addons_menu.get()); - //addons_menu->set_active_item(index); - Menu::set_current(0); + AddonManager::get_instance().install(&addon); } catch (std::runtime_error e) { - log_warning << "Installation of Add-on failed: " << e.what() << std::endl; + log_warning << "Installing Add-on failed: " << e.what() << std::endl; } + addons_menu->set_toggled(index, addon.loaded); + } else if (!addon.loaded) { + try { + AddonManager::get_instance().enable(&addon); + } + catch (std::runtime_error e) { + log_warning << "Enabling Add-on failed: " << e.what() << std::endl; + } + addons_menu->set_toggled(index, addon.loaded); } else { try { - addon.remove(); - //generate_addons_menu(); - //Menu::set_current(addons_menu.get()); - //addons_menu->set_active_item(index); - Menu::set_current(0); + AddonManager::get_instance().disable(&addon); } catch (std::runtime_error e) { - log_warning << "Removal of Add-on failed: " << e.what() << std::endl; + log_warning << "Disabling Add-on failed: " << e.what() << std::endl; } + addons_menu->set_toggled(index, addon.loaded); } } - } void diff --git a/src/title.hpp b/src/title.hpp index 25d68821c..9a267ad22 100644 --- a/src/title.hpp +++ b/src/title.hpp @@ -25,7 +25,7 @@ #include #include "screen.hpp" #include "game_session.hpp" -#include "addon.hpp" +#include "addon/addon.hpp" class Menu; class World; @@ -67,9 +67,7 @@ private: std::auto_ptr main_world; std::vector contrib_worlds; std::auto_ptr addons_menu; - std::vector addons; /**< shown list of Add-ons */ - std::vector available_addons; /**< list of downloadable Add-ons */ - std::vector installed_addons; /**< list of currently installed Add-ons */ + std::vector addons; /**< shown list of Add-ons */ World* current_world; std::auto_ptr frame; -- 2.11.0