Add-on Manager rewrite
authorChristoph Sommer <mail@christoph-sommer.de>
Sun, 11 May 2008 15:20:03 +0000 (15:20 +0000)
committerChristoph Sommer <mail@christoph-sommer.de>
Sun, 11 May 2008 15:20:03 +0000 (15:20 +0000)
 - 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

19 files changed:
CMakeLists.txt
src/addon.cpp [deleted file]
src/addon.hpp [deleted file]
src/addon/addon.cpp [new file with mode: 0644]
src/addon/addon.hpp [new file with mode: 0644]
src/addon/addon_manager.cpp [new file with mode: 0644]
src/addon/addon_manager.hpp [new file with mode: 0644]
src/addon/md5.cpp [new file with mode: 0644]
src/addon/md5.hpp [new file with mode: 0644]
src/addon_manager.cpp [deleted file]
src/addon_manager.hpp [deleted file]
src/gameconfig.cpp
src/gui/menu.cpp
src/gui/menu.hpp
src/lisp/writer.cpp
src/lisp/writer.hpp
src/main.cpp
src/title.cpp
src/title.hpp

index f23166a..8c3a6ca 100644 (file)
@@ -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.cpp
deleted file mode 100644 (file)
index 1b6dbdc..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-//  $Id$
-//
-//  SuperTux - Add-on
-//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
-//
-//  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 <sstream>
-#include <stdexcept>
-#include "addon.hpp"
-#include "addon_manager.hpp"
-
-void
-Addon::install()
-{
-  AddonManager& adm = AddonManager::get_instance();
-  adm.install(*this);
-}
-
-void
-Addon::remove()
-{
-  AddonManager& adm = AddonManager::get_instance();
-  adm.remove(*this);
-}
-  
-void
-Addon::parse(const lisp::Lisp& lisp)
-{
-  try {
-    lisp.get("kind", kind);  
-    lisp.get("title", title);
-    lisp.get("author", author);
-    lisp.get("license", license);
-    lisp.get("http-url", http_url);
-    lisp.get("file", file);
-    lisp.get("md5", md5);
-  } catch(std::exception& e) {
-    std::stringstream msg;
-    msg << "Problem when parsing addoninfo: " << e.what();
-    throw std::runtime_error(msg.str());
-  }
-}
-
-void
-Addon::parse(std::string fname)
-{
-  try {
-    lisp::Parser parser;
-    const lisp::Lisp* root = parser.parse(fname);
-    const lisp::Lisp* addon = root->get_lisp("supertux-addoninfo");
-    if(!addon) throw std::runtime_error("file is not a supertux-addoninfo file.");
-    parse(*addon);
-  } catch(std::exception& e) {
-    std::stringstream msg;
-    msg << "Problem when reading addoninfo '" << fname << "': " << e.what();
-    throw std::runtime_error(msg.str());
-  }
-}
-
-void
-Addon::write(lisp::Writer& writer) const
-{
-  writer.start_list("supertux-addoninfo");
-  if (kind != "") writer.write_string("kind", kind);  
-  if (title != "") writer.write_string("title", title);
-  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);
-  writer.end_list("supertux-addoninfo");
-}
-
-void 
-Addon::write(std::string fname) const
-{
-  lisp::Writer writer(fname);
-  write(writer);
-}
-
-bool 
-Addon::equals(const Addon& addon2) const
-{
-  if ((this->md5 == "") || (addon2.md5 == "")) return (this->title == addon2.title);
-  return (this->md5 == addon2.md5);
-}
-
diff --git a/src/addon.hpp b/src/addon.hpp
deleted file mode 100644 (file)
index b65efe7..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-//  $Id$
-//
-//  SuperTux - Add-on
-//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
-//
-//  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.
-//
-#ifndef ADDON_H
-#define ADDON_H
-
-#include <string>
-#include <vector>
-#include "lisp/parser.hpp"
-#include "lisp/lisp.hpp"
-#include "lisp/writer.hpp"
-
-/**
- * Represents an (available or installed) Add-on, e.g. a level set
- */
-class Addon
-{
-public:
-  std::string kind;
-  std::string title;
-  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();
-
-  /**
-   * Physically delete Add-on
-   */
-  void remove();
-
-  /**
-   * Read additional information from given contents of a (supertux-addoninfo ...) block
-   */
-  void parse(const lisp::Lisp& lisp);
-
-  /**
-   * Read additional information from given file
-   */
-  void parse(std::string fname);
-
-  /**
-   * Writes out Add-on metainformation to a Lisp Writer
-   */
-  void write(lisp::Writer& writer) const;
-
-  /**
-   * Writes out Add-on metainformation to a file
-   */
-  void write(std::string fname) const;
-
-  /**
-   * Checks if Add-on is the same as given one. 
-   * If available, checks MD5 sum, else relies on title alone.
-   */
-  bool equals(const Addon& addon2) const;
-
-};
-
-#endif
diff --git a/src/addon/addon.cpp b/src/addon/addon.cpp
new file mode 100644 (file)
index 0000000..4e8c9f6
--- /dev/null
@@ -0,0 +1,132 @@
+//  $Id$
+//
+//  SuperTux - Add-on
+//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+//  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 <string>
+#include <sstream>
+#include <stdexcept>
+#include <physfs.h>
+#include "addon/addon.hpp"
+#include "addon/addon_manager.hpp"
+#include "log.hpp"
+#include "addon/md5.hpp"
+
+std::string
+Addon::get_md5() const
+{
+  if (!installed) {
+    if (stored_md5 == "") log_warning << "Add-on not installed and no stored MD5 available" << std::endl;
+    return stored_md5;
+  }
+
+  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)
+{
+  try {
+    lisp.get("kind", kind);  
+    lisp.get("title", title);
+    lisp.get("author", author);
+    lisp.get("license", license);
+    lisp.get("http-url", http_url);
+    lisp.get("file", suggested_filename);
+    lisp.get("md5", stored_md5);
+  } catch(std::exception& e) {
+    std::stringstream msg;
+    msg << "Problem when parsing addoninfo: " << e.what();
+    throw std::runtime_error(msg.str());
+  }
+}
+
+void
+Addon::parse(std::string fname)
+{
+  try {
+    lisp::Parser parser;
+    const lisp::Lisp* root = parser.parse(fname);
+    const lisp::Lisp* addon = root->get_lisp("supertux-addoninfo");
+    if(!addon) throw std::runtime_error("file is not a supertux-addoninfo file.");
+    parse(*addon);
+  } catch(std::exception& e) {
+    std::stringstream msg;
+    msg << "Problem when reading addoninfo '" << fname << "': " << e.what();
+    throw std::runtime_error(msg.str());
+  }
+}
+
+void
+Addon::write(lisp::Writer& writer) const
+{
+  writer.start_list("supertux-addoninfo");
+  if (kind != "") writer.write_string("kind", kind);  
+  if (title != "") writer.write_string("title", title);
+  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 (suggested_filename != "") writer.write_string("file", suggested_filename);
+  if (stored_md5 != "") writer.write_string("md5", stored_md5);
+  writer.end_list("supertux-addoninfo");
+}
+
+void 
+Addon::write(std::string fname) const
+{
+  lisp::Writer writer(fname);
+  write(writer);
+}
+
+bool 
+Addon::operator==(Addon addon2) const
+{
+  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/addon.hpp b/src/addon/addon.hpp
new file mode 100644 (file)
index 0000000..a630435
--- /dev/null
@@ -0,0 +1,91 @@
+//  $Id$
+//
+//  SuperTux - Add-on
+//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+//  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.
+//
+#ifndef ADDON_H
+#define ADDON_H
+
+#include <string>
+#include <vector>
+#include "lisp/parser.hpp"
+#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
+ */
+class Addon
+{
+public:
+  std::string kind;
+  std::string title;
+  std::string author;
+  std::string license;
+  std::string http_url;
+  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;
+
+  /**
+   * Get MD5, based either on installed file's contents or stored value
+   */
+  std::string get_md5() const;
+
+  /**
+   * Read additional information from given contents of a (supertux-addoninfo ...) block
+   */
+  void parse(const lisp::Lisp& lisp);
+
+  /**
+   * Read additional information from given file
+   */
+  void parse(std::string fname);
+
+  /**
+   * Writes out Add-on metainformation to a Lisp Writer
+   */
+  void write(lisp::Writer& writer) const;
+
+  /**
+   * Writes out Add-on metainformation to a file
+   */
+  void write(std::string fname) const;
+
+  /**
+   * Checks if Add-on is the same as given one. 
+   * If available, checks MD5 sum, else relies on kind, author and title alone.
+   */
+  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 (file)
index 0000000..695e181
--- /dev/null
@@ -0,0 +1,438 @@
+//  $Id$
+//
+//  SuperTux - Add-on Manager
+//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+//  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 <sstream>
+#include <stdexcept>
+#include <cstdlib>
+#include <list>
+#include <physfs.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#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 <curl/curl.h>
+#include <curl/types.h>
+#include <curl/easy.h>
+#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<std::string*>(string_ptr);
+    std::string buf(static_cast<char*>(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<PHYSFS_file*>(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<Addon*>::iterator i = addons.begin(); i != addons.end(); i++) delete *i;
+}
+
+std::vector<Addon*>
+AddonManager::get_addons()
+{
+/*
+  for (std::vector<Addon>::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<Addon*>::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<std::string>::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<Addon*>::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/addon_manager.hpp b/src/addon/addon_manager.hpp
new file mode 100644 (file)
index 0000000..26c80f4
--- /dev/null
@@ -0,0 +1,102 @@
+//  $Id$
+//
+//  SuperTux - Add-on Manager
+//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+//  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.
+//
+#ifndef ADDON_MANAGER_H
+#define ADDON_MANAGER_H
+
+#include <string>
+#include <vector>
+#include "addon/addon.hpp"
+
+/**
+ * Checks for, installs and removes Add-ons
+ */
+class AddonManager
+{
+public:
+  /**
+   * returns a list of installed Add-ons
+   */
+  std::vector<Addon*> get_addons();
+
+  /**
+   * downloads list of available Add-ons
+   */
+  void check_online();
+
+  /**
+   * Download and install Add-on
+   */
+  void install(Addon* addon);
+
+  /**
+   * Physically delete Add-on
+   */
+  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<Addon*> addons;
+  std::vector<std::string> ignored_addon_filenames;
+
+  AddonManager();
+  ~AddonManager();
+};
+
+#endif
diff --git a/src/addon/md5.cpp b/src/addon/md5.cpp
new file mode 100644 (file)
index 0000000..3020721
--- /dev/null
@@ -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 <christoph.sommer@2008.expires.deltadevelopment.de>
+//
+// 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 <assert.h>
+#include <strings.h>
+#include <iostream>
+#include <stdexcept>
+
+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 (file)
index 0000000..6d1e921
--- /dev/null
@@ -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 <christoph.sommer@2008.expires.deltadevelopment.de>
+//
+// 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 <stdio.h>
+#include <fstream>
+#include <iostream>
+#include <stdint.h>
+#include <string>
+
+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 (file)
index b59dde0..0000000
+++ /dev/null
@@ -1,297 +0,0 @@
-//  $Id$
-//
-//  SuperTux - Add-on Manager
-//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
-//
-//  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 <sstream>
-#include <stdexcept>
-#include <cstdlib>
-#include <list>
-#include <physfs.h>
-#include <sys/stat.h>
-#include <stdio.h>
-#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 <curl/curl.h>
-#include <curl/types.h>
-#include <curl/easy.h>
-#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<std::string*>(string_ptr);
-    std::string buf(static_cast<char*>(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<PHYSFS_file*>(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<Addon>
-AddonManager::get_installed_addons() const
-{
-  std::vector<Addon> 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<Addon>
-AddonManager::get_available_addons() const
-{
-  std::vector<Addon> 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/addon_manager.hpp b/src/addon_manager.hpp
deleted file mode 100644 (file)
index 23b3415..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-//  $Id$
-//
-//  SuperTux - Add-on Manager
-//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
-//
-//  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.
-//
-#ifndef ADDON_MANAGER_H
-#define ADDON_MANAGER_H
-
-#include <string>
-#include <vector>
-#include "addon.hpp"
-
-/**
- * Checks for, installs and removes Add-ons
- */
-class AddonManager
-{
-public:
-  /**
-   * returns a list of installed Add-ons
-   */
-  std::vector<Addon> get_installed_addons() const;
-  
-  /**
-   * returns a list of available Add-ons
-   */
-  std::vector<Addon> get_available_addons() const;
-
-  /**
-   * Download and install Add-on
-   */
-  void install(const Addon& addon);
-
-  /**
-   * Physically delete Add-on
-   */
-  void remove(const Addon& addon);
-
-  static AddonManager& get_instance();
-
-protected:
-  AddonManager();
-  ~AddonManager();
-};
-
-#endif
index 6a92e35..c717af5 100644 (file)
@@ -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");
 }
index 2558359..dda9048 100644 (file)
@@ -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
 {
index fc7dca4..7c6605c 100644 (file)
@@ -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;
 
index d2c7b1c..99ff42b 100644 (file)
@@ -164,6 +164,19 @@ Writer::write_float_vector(const std::string& name,
 }
 
 void
+Writer::write_string_vector(const std::string& name,
+                           const std::vector<std::string>& value)
+{
+  indent();
+  *out << '(' << name;
+  for(std::vector<std::string>::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 << '"';
index ba5d327..c2dad8d 100644 (file)
@@ -46,6 +46,7 @@ namespace lisp
     void write_int_vector(const std::string& name, const std::vector<int>& value);
     void write_int_vector(const std::string& name, const std::vector<unsigned int>& value);
     void write_float_vector(const std::string& name, const std::vector<float>& value);
+    void write_string_vector(const std::string& name, const std::vector<std::string>& value);
     // add more write-functions when needed...
 
     void end_list(const std::string& listname);
index 15c06a7..5006882 100644 (file)
@@ -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");
index 6127663..bf5776a 100644 (file)
@@ -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<Addon>::iterator it2 = addons.begin();
-  while (it2 != addons.end()) {
-    Addon addon = *it2;
-    if (addon.isInstalled) {
-      bool restart = false;
-      for (std::vector<Addon>::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
index 25d6882..9a267ad 100644 (file)
@@ -25,7 +25,7 @@
 #include <vector>
 #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<World> main_world;
   std::vector<World*> contrib_worlds;
   std::auto_ptr<Menu> addons_menu;
-  std::vector<Addon> addons; /**< shown list of Add-ons */
-  std::vector<Addon> available_addons; /**< list of downloadable Add-ons */
-  std::vector<Addon> installed_addons; /**< list of currently installed Add-ons */
+  std::vector<Addon*> addons; /**< shown list of Add-ons */
   World* current_world;
 
   std::auto_ptr<Surface> frame;