rnd-20100719-1-src
[rocksndiamonds.git] / src / libgame / setup.c
index 9d095724b4363edf088ebf8b0928f6741c4ac846..412779d0bc8ca28a90cd62a4cd1388578b73938d 100644 (file)
@@ -334,7 +334,9 @@ char *setLevelArtworkDir(TreeInfo *ti)
   checked_free(*artwork_path_ptr);
 
   if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
+  {
     *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
+  }
   else
   {
     /*
@@ -410,6 +412,19 @@ char *getSolutionTapeFilename(int nr)
   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
   filename = getPath2(getSolutionTapeDir(), basename);
 
+  if (!fileExists(filename))
+  {
+    static char *filename_sln = NULL;
+
+    checked_free(filename_sln);
+
+    sprintf(basename, "%03d.sln", nr);
+    filename_sln = getPath2(getSolutionTapeDir(), basename);
+
+    if (fileExists(filename_sln))
+      return filename_sln;
+  }
+
   return filename;
 }
 
@@ -1408,21 +1423,41 @@ static int posix_mkdir(const char *pathname, mode_t mode)
 #endif
 }
 
+static boolean posix_process_running_setgid()
+{
+#if defined(PLATFORM_UNIX)
+  return (getgid() != getegid());
+#else
+  return FALSE;
+#endif
+}
+
 void createDirectory(char *dir, char *text, int permission_class)
 {
   /* leave "other" permissions in umask untouched, but ensure group parts
      of USERDATA_DIR_MODE are not masked */
   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
                     DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
-  mode_t normal_umask = posix_umask(0);
+  mode_t last_umask = posix_umask(0);
   mode_t group_umask = ~(dir_mode & S_IRWXG);
-  posix_umask(normal_umask & group_umask);
+  int running_setgid = posix_process_running_setgid();
+
+  /* if we're setgid, protect files against "other" */
+  /* else keep umask(0) to make the dir world-writable */
+
+  if (running_setgid)
+    posix_umask(last_umask & group_umask);
+  else
+    dir_mode |= MODE_W_ALL;
 
   if (!fileExists(dir))
     if (posix_mkdir(dir, dir_mode) != 0)
       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
 
-  posix_umask(normal_umask);           /* reset normal umask */
+  if (permission_class == PERMS_PUBLIC && !running_setgid)
+    chmod(dir, dir_mode);
+
+  posix_umask(last_umask);             /* restore previous umask */
 }
 
 void InitUserDataDirectory()
@@ -1432,8 +1467,14 @@ void InitUserDataDirectory()
 
 void SetFilePermissions(char *filename, int permission_class)
 {
-  chmod(filename, (permission_class == PERMS_PRIVATE ?
-                  FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
+  int running_setgid = posix_process_running_setgid();
+  int perms = (permission_class == PERMS_PRIVATE ?
+              FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
+
+  if (permission_class == PERMS_PUBLIC && !running_setgid)
+    perms |= MODE_W_ALL;
+
+  chmod(filename, perms);
 }
 
 char *getCookie(char *file_type)
@@ -1614,7 +1655,7 @@ DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
 #define remove_hash_entry hashtable_remove
 #endif
 
-static unsigned int get_hash_from_key(void *key)
+unsigned int get_hash_from_key(void *key)
 {
   /*
     djb2
@@ -2373,10 +2414,11 @@ void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
 #define LEVELINFO_TOKEN_MUSIC_SET              18
 #define LEVELINFO_TOKEN_FILENAME               19
 #define LEVELINFO_TOKEN_FILETYPE               20
-#define LEVELINFO_TOKEN_HANDICAP               21
-#define LEVELINFO_TOKEN_SKIP_LEVELS            22
+#define LEVELINFO_TOKEN_SPECIAL_FLAGS          21
+#define LEVELINFO_TOKEN_HANDICAP               22
+#define LEVELINFO_TOKEN_SKIP_LEVELS            23
 
-#define NUM_LEVELINFO_TOKENS                   23
+#define NUM_LEVELINFO_TOKENS                   24
 
 static LevelDirTree ldi;
 
@@ -2404,6 +2446,7 @@ static struct TokenInfo levelinfo_tokens[] =
   { TYPE_STRING,       &ldi.music_set,         "music_set"             },
   { TYPE_STRING,       &ldi.level_filename,    "filename"              },
   { TYPE_STRING,       &ldi.level_filetype,    "filetype"              },
+  { TYPE_STRING,       &ldi.special_flags,     "special_flags"         },
   { TYPE_BOOLEAN,      &ldi.handicap,          "handicap"              },
   { TYPE_BOOLEAN,      &ldi.skip_levels,       "skip_levels"           }
 };
@@ -2480,6 +2523,8 @@ static void setTreeInfoToDefaults(TreeInfo *ti, int type)
     ti->level_filename = NULL;
     ti->level_filetype = NULL;
 
+    ti->special_flags = NULL;
+
     ti->levels = 0;
     ti->first_level = 0;
     ti->last_level = 0;
@@ -2551,12 +2596,18 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
     ti->level_filename = NULL;
     ti->level_filetype = NULL;
 
+    ti->special_flags = getStringCopy(parent->special_flags);
+
     ti->levels = 0;
     ti->first_level = 0;
     ti->last_level = 0;
     ti->level_group = FALSE;
     ti->handicap_level = 0;
+#if 1
+    ti->readonly = parent->readonly;
+#else
     ti->readonly = TRUE;
+#endif
     ti->handicap = TRUE;
     ti->skip_levels = FALSE;
   }
@@ -2602,6 +2653,8 @@ static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
   ti_copy->level_filename      = getStringCopy(ti->level_filename);
   ti_copy->level_filetype      = getStringCopy(ti->level_filetype);
 
+  ti_copy->special_flags       = getStringCopy(ti->special_flags);
+
   ti_copy->levels              = ti->levels;
   ti_copy->first_level         = ti->first_level;
   ti_copy->last_level          = ti->last_level;
@@ -2626,7 +2679,7 @@ static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
   return ti_copy;
 }
 
-static void freeTreeInfo(TreeInfo *ti)
+void freeTreeInfo(TreeInfo *ti)
 {
   if (ti == NULL)
     return;
@@ -2663,6 +2716,8 @@ static void freeTreeInfo(TreeInfo *ti)
 
     checked_free(ti->level_filename);
     checked_free(ti->level_filetype);
+
+    checked_free(ti->special_flags);
   }
 
   checked_free(ti);
@@ -2685,6 +2740,10 @@ void setSetupInfo(struct TokenInfo *token_info,
       *(boolean *)setup_value = get_boolean_from_string(token_value);
       break;
 
+    case TYPE_SWITCH3:
+      *(int *)setup_value = get_switch3_from_string(token_value);
+      break;
+
     case TYPE_KEY:
       *(Key *)setup_value = getKeyFromKeyName(token_value);
       break;
@@ -2834,14 +2893,18 @@ static char *getCacheToken(char *prefix, char *suffix)
   return token;
 }
 
-static char *getFileTimestamp(char *filename)
+static char *getFileTimestampString(char *filename)
 {
+#if 1
+  return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
+#else
   struct stat file_status;
 
   if (stat(filename, &file_status) != 0)       /* cannot stat file */
     return getStringCopy(i_to_a(0));
 
   return getStringCopy(i_to_a(file_status.st_mtime));
+#endif
 }
 
 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
@@ -2958,8 +3021,8 @@ static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
                                        LEVELINFO_FILENAME);
     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
                                          ARTWORKINFO_FILENAME(type));
-    char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
-    char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
+    char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
+    char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
 
     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
@@ -3078,6 +3141,12 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
   leveldir_new->in_user_dir =
     (!strEqual(leveldir_new->basepath, options.level_directory));
 
+#if 0
+  printf("::: '%s' -> %d\n",
+        leveldir_new->identifier,
+        leveldir_new->in_user_dir);
+#endif
+
   /* adjust some settings if user's private level directory was detected */
   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
       leveldir_new->in_user_dir &&
@@ -3747,10 +3816,20 @@ char *getSetupValue(int type, void *value)
       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
       break;
 
+    case TYPE_SWITCH3:
+      strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
+                           *(int *)value == FALSE ? "off" : "on"));
+      break;
+
     case TYPE_YES_NO:
       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
       break;
 
+    case TYPE_YES_NO_AUTO:
+      strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
+                           *(int *)value == FALSE ? "no" : "yes"));
+      break;
+
     case TYPE_ECS_AGA:
       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
       break;
@@ -3950,10 +4029,17 @@ void LoadLevelSetup_SeriesInfo()
   char *filename;
   SetupFileHash *level_setup_hash = NULL;
   char *level_subdir = leveldir_current->subdir;
+  int i;
 
   /* always start with reliable default values */
   level_nr = leveldir_current->first_level;
 
+  for (i = 0; i < MAX_LEVELS; i++)
+  {
+    LevelStats_setPlayed(i, 0);
+    LevelStats_setSolved(i, 0);
+  }
+
   checkSeriesInfo(leveldir_current);
 
   /* ----------------------------------------------------------------------- */
@@ -3968,6 +4054,8 @@ void LoadLevelSetup_SeriesInfo()
   {
     char *token_value;
 
+    /* get last played level in this level set */
+
     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
 
     if (token_value)
@@ -3980,6 +4068,8 @@ void LoadLevelSetup_SeriesInfo()
        level_nr = leveldir_current->last_level;
     }
 
+    /* get handicap level in this level set */
+
     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
 
     if (token_value)
@@ -3997,6 +4087,31 @@ void LoadLevelSetup_SeriesInfo()
       leveldir_current->handicap_level = level_nr;
     }
 
+    /* get number of played and solved levels in this level set */
+
+    BEGIN_HASH_ITERATION(level_setup_hash, itr)
+    {
+      char *token = HASH_ITERATION_TOKEN(itr);
+      char *value = HASH_ITERATION_VALUE(itr);
+
+      if (strlen(token) == 3 &&
+         token[0] >= '0' && token[0] <= '9' &&
+         token[1] >= '0' && token[1] <= '9' &&
+         token[2] >= '0' && token[2] <= '9')
+      {
+       int level_nr = atoi(token);
+
+       if (value != NULL)
+         LevelStats_setPlayed(level_nr, atoi(value));  /* read 1st column */
+
+       value = strchr(value, ' ');
+
+       if (value != NULL)
+         LevelStats_setSolved(level_nr, atoi(value));  /* read 2nd column */
+      }
+    }
+    END_HASH_ITERATION(hash, itr)
+
     checkSetupFileHashIdentifier(level_setup_hash, filename,
                                 getCookie("LEVELSETUP"));
 
@@ -4015,6 +4130,7 @@ void SaveLevelSetup_SeriesInfo()
   char *level_nr_str = int2str(level_nr, 0);
   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
   FILE *file;
+  int i;
 
   /* ----------------------------------------------------------------------- */
   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
@@ -4035,8 +4151,24 @@ void SaveLevelSetup_SeriesInfo()
                                                 getCookie("LEVELSETUP")));
   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
                                               level_nr_str));
-  fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
-                                              handicap_level_str));
+  fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
+                                                handicap_level_str));
+
+  for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
+       i++)
+  {
+    if (LevelStats_getPlayed(i) > 0 ||
+       LevelStats_getSolved(i) > 0)
+    {
+      char token[16];
+      char value[16];
+
+      sprintf(token, "%03d", i);
+      sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
+
+      fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
+    }
+  }
 
   fclose(file);
 
@@ -4044,3 +4176,37 @@ void SaveLevelSetup_SeriesInfo()
 
   free(filename);
 }
+
+int LevelStats_getPlayed(int nr)
+{
+  return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
+}
+
+int LevelStats_getSolved(int nr)
+{
+  return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
+}
+
+void LevelStats_setPlayed(int nr, int value)
+{
+  if (nr >= 0 && nr < MAX_LEVELS)
+    level_stats[nr].played = value;
+}
+
+void LevelStats_setSolved(int nr, int value)
+{
+  if (nr >= 0 && nr < MAX_LEVELS)
+    level_stats[nr].solved = value;
+}
+
+void LevelStats_incPlayed(int nr)
+{
+  if (nr >= 0 && nr < MAX_LEVELS)
+    level_stats[nr].played++;
+}
+
+void LevelStats_incSolved(int nr)
+{
+  if (nr >= 0 && nr < MAX_LEVELS)
+    level_stats[nr].solved++;
+}