improved handling and displaying dropped level set zip files into window
[rocksndiamonds.git] / src / libgame / setup.c
index 626bb1f9e2d2475074a7e2042127dea37cdcbf9a..c64c95c8ee84c6d5fdb17d6b110eaded193a01a7 100644 (file)
@@ -28,6 +28,7 @@
 #include "text.h"
 #include "misc.h"
 #include "hash.h"
+#include "zip/miniunz.h"
 
 
 #define ENABLE_UNUSED_CODE     FALSE   // for currently unused functions
@@ -177,7 +178,7 @@ static char *getNetworkDir(void)
   return network_dir;
 }
 
-static char *getLevelDirFromTreeInfo(TreeInfo *node)
+char *getLevelDirFromTreeInfo(TreeInfo *node)
 {
   static char *level_dir = NULL;
 
@@ -339,7 +340,7 @@ static char *getClassicArtworkDir(int type)
          getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
 }
 
-static char *getUserGraphicsDir(void)
+char *getUserGraphicsDir(void)
 {
   static char *usergraphics_dir = NULL;
 
@@ -349,7 +350,7 @@ static char *getUserGraphicsDir(void)
   return usergraphics_dir;
 }
 
-static char *getUserSoundsDir(void)
+char *getUserSoundsDir(void)
 {
   static char *usersounds_dir = NULL;
 
@@ -359,7 +360,7 @@ static char *getUserSoundsDir(void)
   return usersounds_dir;
 }
 
-static char *getUserMusicDir(void)
+char *getUserMusicDir(void)
 {
   static char *usermusic_dir = NULL;
 
@@ -2981,6 +2982,223 @@ static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
 // functions for loading level info and custom artwork info
 // ----------------------------------------------------------------------------
 
+int GetZipFileTreeType(char *zip_filename)
+{
+  static char *top_dir_path = NULL;
+  static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
+  static char *conf_basename[NUM_BASE_TREE_TYPES] =
+  {
+    GRAPHICSINFO_FILENAME,
+    SOUNDSINFO_FILENAME,
+    MUSICINFO_FILENAME,
+    LEVELINFO_FILENAME
+  };
+  int j;
+
+  checked_free(top_dir_path);
+  top_dir_path = NULL;
+
+  for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
+  {
+    checked_free(top_dir_conf_filename[j]);
+    top_dir_conf_filename[j] = NULL;
+  }
+
+  char **zip_entries = zip_list(zip_filename);
+
+  // check if zip file successfully opened
+  if (zip_entries == NULL || zip_entries[0] == NULL)
+    return TREE_TYPE_UNDEFINED;
+
+  // first zip file entry is expected to be top level directory
+  char *top_dir = zip_entries[0];
+
+  // check if valid top level directory found in zip file
+  if (!strSuffix(top_dir, "/"))
+    return TREE_TYPE_UNDEFINED;
+
+  // get filenames of valid configuration files in top level directory
+  for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
+    top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
+
+  int tree_type = TREE_TYPE_UNDEFINED;
+  int e = 0;
+
+  while (zip_entries[e] != NULL)
+  {
+    // check if every zip file entry is below top level directory
+    if (!strPrefix(zip_entries[e], top_dir))
+      return TREE_TYPE_UNDEFINED;
+
+    // check if this zip file entry is a valid configuration filename
+    for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
+    {
+      if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
+      {
+       // only exactly one valid configuration file allowed
+       if (tree_type != TREE_TYPE_UNDEFINED)
+         return TREE_TYPE_UNDEFINED;
+
+       tree_type = j;
+      }
+    }
+
+    e++;
+  }
+
+  return tree_type;
+}
+
+static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
+                                       int tree_type)
+{
+  static char *top_dir_path = NULL;
+  static char *top_dir_conf_filename = NULL;
+
+  checked_free(top_dir_path);
+  checked_free(top_dir_conf_filename);
+
+  top_dir_path = NULL;
+  top_dir_conf_filename = NULL;
+
+  char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
+                        ARTWORKINFO_FILENAME(tree_type));
+
+  // check if valid configuration filename determined
+  if (conf_basename == NULL || strEqual(conf_basename, ""))
+    return FALSE;
+
+  char **zip_entries = zip_list(zip_filename);
+
+  // check if zip file successfully opened
+  if (zip_entries == NULL || zip_entries[0] == NULL)
+    return FALSE;
+
+  // first zip file entry is expected to be top level directory
+  char *top_dir = zip_entries[0];
+
+  // check if valid top level directory found in zip file
+  if (!strSuffix(top_dir, "/"))
+    return FALSE;
+
+  // get path of extracted top level directory
+  top_dir_path = getPath2(directory, top_dir);
+
+  // remove trailing directory separator from top level directory path
+  // (required to be able to check for file and directory in next step)
+  top_dir_path[strlen(top_dir_path) - 1] = '\0';
+
+  // check if zip file's top level directory already exists in target directory
+  if (fileExists(top_dir_path))                // (checks for file and directory)
+    return FALSE;
+
+  // get filename of configuration file in top level directory
+  top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
+
+  boolean found_top_dir_conf_filename = FALSE;
+  int i = 0;
+
+  while (zip_entries[i] != NULL)
+  {
+    // check if every zip file entry is below top level directory
+    if (!strPrefix(zip_entries[i], top_dir))
+      return FALSE;
+
+    // check if this zip file entry is the configuration filename
+    if (strEqual(zip_entries[i], top_dir_conf_filename))
+      found_top_dir_conf_filename = TRUE;
+
+    i++;
+  }
+
+  // check if valid configuration filename was found in zip file
+  if (!found_top_dir_conf_filename)
+    return FALSE;
+
+  return TRUE;
+}
+
+char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
+                                 int tree_type)
+{
+  boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
+                                                   tree_type);
+
+  if (!zip_file_valid)
+  {
+    Error(ERR_WARN, "zip file '%s' rejected!", zip_filename);
+
+    return NULL;
+  }
+
+  char **zip_entries = zip_extract(zip_filename, directory);
+
+  if (zip_entries == NULL)
+  {
+    Error(ERR_WARN, "zip file '%s' could not be extracted!", zip_filename);
+
+    return NULL;
+  }
+
+  Error(ERR_INFO, "zip file '%s' successfully extracted!", zip_filename);
+
+  // first zip file entry contains top level directory
+  char *top_dir = zip_entries[0];
+
+  // remove trailing directory separator from top level directory
+  top_dir[strlen(top_dir) - 1] = '\0';
+
+  return top_dir;
+}
+
+static void ProcessZipFilesInDirectory(char *directory, int tree_type)
+{
+  Directory *dir;
+  DirectoryEntry *dir_entry;
+
+  if ((dir = openDirectory(directory)) == NULL)
+  {
+    // display error if directory is main "options.graphics_directory" etc.
+    if (tree_type == TREE_TYPE_LEVEL_DIR ||
+       directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
+      Error(ERR_WARN, "cannot read directory '%s'", directory);
+
+    return;
+  }
+
+  while ((dir_entry = readDirectory(dir)) != NULL)     // loop all entries
+  {
+    // skip non-zip files (and also directories with zip extension)
+    if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
+      continue;
+
+    char *zip_filename = getPath2(directory, dir_entry->basename);
+    char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
+    char *zip_filename_rejected  = getStringCat2(zip_filename, ".rejected");
+
+    // check if zip file hasn't already been extracted or rejected
+    if (!fileExists(zip_filename_extracted) &&
+       !fileExists(zip_filename_rejected))
+    {
+      char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
+                                                 tree_type);
+      char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
+                              zip_filename_rejected);
+      FILE *marker_file;
+
+      // create empty file to mark zip file as extracted or rejected
+      if ((marker_file = fopen(marker_filename, MODE_WRITE)))
+       fclose(marker_file);
+
+      free(zip_filename);
+      free(zip_filename_extracted);
+      free(zip_filename_rejected);
+    }
+  }
+
+  closeDirectory(dir);
+}
+
 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
 
@@ -3108,6 +3326,12 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
                                      TreeInfo *node_parent,
                                      char *level_directory)
 {
+  // ---------- 1st stage: process any level set zip files ----------
+
+  ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
+
+  // ---------- 2nd stage: check for level set directories ----------
+
   Directory *dir;
   DirectoryEntry *dir_entry;
   boolean valid_entry_found = FALSE;
@@ -3351,6 +3575,12 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
                                          TreeInfo *node_parent,
                                          char *base_directory, int type)
 {
+  // ---------- 1st stage: process any artwork set zip files ----------
+
+  ProcessZipFilesInDirectory(base_directory, type);
+
+  // ---------- 2nd stage: check for artwork set directories ----------
+
   Directory *dir;
   DirectoryEntry *dir_entry;
   boolean valid_entry_found = FALSE;
@@ -3630,13 +3860,29 @@ void LoadLevelArtworkInfo(void)
   print_timestamp_done("LoadLevelArtworkInfo");
 }
 
-static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
+static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
+                                      char *tree_subdir_new, int type)
 {
-  // get level info tree node of first (original) user level set
-  char *level_subdir_old = getLoginName();
-  LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
-                                                        level_subdir_old);
-  if (leveldir_old == NULL)            // should not happen
+  if (tree_node_old == NULL)
+  {
+    if (type == TREE_TYPE_LEVEL_DIR)
+    {
+      // get level info tree node of personal user level set
+      tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
+    }
+    else
+    {
+      // get artwork info tree node of first artwork set
+      tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
+    }
+  }
+
+  if (tree_dir == NULL)
+    tree_dir = TREE_USERDIR(type);
+
+  if (tree_node_old   == NULL ||
+      tree_dir        == NULL ||
+      tree_subdir_new == NULL)         // should not happen
     return FALSE;
 
   int draw_deactivation_mask = GetDrawDeactivationMask();
@@ -3644,33 +3890,54 @@ static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
   // override draw deactivation mask (temporarily disable drawing)
   SetDrawDeactivationMask(REDRAW_ALL);
 
-  // load new level set config and add it next to first user level set
-  LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
-                            leveldir_old->basepath, level_subdir_new);
+  if (type == TREE_TYPE_LEVEL_DIR)
+  {
+    // load new level set config and add it next to first user level set
+    LoadLevelInfoFromLevelConf(&tree_node_old->next,
+                              tree_node_old->node_parent,
+                              tree_dir, tree_subdir_new);
+  }
+  else
+  {
+    // load new artwork set config and add it next to first artwork set
+    LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
+                                  tree_node_old->node_parent,
+                                  tree_dir, tree_subdir_new, type);
+  }
 
   // set draw deactivation mask to previous value
   SetDrawDeactivationMask(draw_deactivation_mask);
 
-  // get level info tree node of newly added user level set
-  LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
-                                                        level_subdir_new);
-  if (leveldir_new == NULL)            // should not happen
+  // get first node of level or artwork info tree
+  TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
+
+  // get tree info node of newly added level or artwork set
+  TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
+                                                     tree_subdir_new);
+
+  if (tree_node_new == NULL)           // should not happen
     return FALSE;
 
   // correct top link and parent node link of newly created tree node
-  leveldir_new->node_top    = leveldir_old->node_top;
-  leveldir_new->node_parent = leveldir_old->node_parent;
+  tree_node_new->node_top    = tree_node_old->node_top;
+  tree_node_new->node_parent = tree_node_old->node_parent;
 
-  // sort level info tree to adjust position of newly added level set
-  sortTreeInfo(&leveldir_first);
+  // sort tree info to adjust position of newly added tree set
+  sortTreeInfo(tree_node_first);
 
   return TRUE;
 }
 
+void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
+                         char *tree_subdir_new, int type)
+{
+  if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
+    Error(ERR_EXIT, "internal tree info structure corrupted -- aborting");
+}
+
 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
 {
-  if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
-    Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
+  AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
 }
 
 char *getArtworkIdentifierForUserLevelSet(int type)
@@ -4069,6 +4336,11 @@ static void checkSeriesInfo(void)
 {
   static char *level_directory = NULL;
   Directory *dir;
+#if 0
+  DirectoryEntry *dir_entry;
+#endif
+
+  checked_free(level_directory);
 
   // check for more levels besides the 'levels' field of 'levelinfo.conf'
 
@@ -4084,6 +4356,35 @@ static void checkSeriesInfo(void)
     return;
   }
 
+#if 0
+  while ((dir_entry = readDirectory(dir)) != NULL)     // loop all entries
+  {
+    if (strlen(dir_entry->basename) > 4 &&
+       dir_entry->basename[3] == '.' &&
+       strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
+    {
+      char levelnum_str[4];
+      int levelnum_value;
+
+      strncpy(levelnum_str, dir_entry->basename, 3);
+      levelnum_str[3] = '\0';
+
+      levelnum_value = atoi(levelnum_str);
+
+      if (levelnum_value < leveldir_current->first_level)
+      {
+       Error(ERR_WARN, "additional level %d found", levelnum_value);
+       leveldir_current->first_level = levelnum_value;
+      }
+      else if (levelnum_value > leveldir_current->last_level)
+      {
+       Error(ERR_WARN, "additional level %d found", levelnum_value);
+       leveldir_current->last_level = levelnum_value;
+      }
+    }
+  }
+#endif
+
   closeDirectory(dir);
 }