+}
+
+
+// ----------------------------------------------------------------------------
+// functions for handling level and custom artwork info cache
+// ----------------------------------------------------------------------------
+
+static void LoadArtworkInfoCache(void)
+{
+ InitCacheDirectory();
+
+ if (artworkinfo_cache_old == NULL)
+ {
+ char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
+
+ // try to load artwork info hash from already existing cache file
+ artworkinfo_cache_old = loadSetupFileHash(filename);
+
+ // try to get program version that artwork info cache was written with
+ char *version = getHashEntry(artworkinfo_cache_old, "program.version");
+
+ // check program version of artwork info cache against current version
+ if (!strEqual(version, program.version_string))
+ {
+ freeSetupFileHash(artworkinfo_cache_old);
+
+ artworkinfo_cache_old = NULL;
+ }
+
+ // if no artwork info cache file was found, start with empty hash
+ if (artworkinfo_cache_old == NULL)
+ artworkinfo_cache_old = newSetupFileHash();
+
+ free(filename);
+ }
+
+ if (artworkinfo_cache_new == NULL)
+ artworkinfo_cache_new = newSetupFileHash();
+
+ update_artworkinfo_cache = FALSE;
+}
+
+static void SaveArtworkInfoCache(void)
+{
+ if (!update_artworkinfo_cache)
+ return;
+
+ char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
+
+ InitCacheDirectory();
+
+ saveSetupFileHash(artworkinfo_cache_new, filename);
+
+ free(filename);
+}
+
+static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
+{
+ static char *prefix = NULL;
+
+ checked_free(prefix);
+
+ prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
+
+ return prefix;
+}
+
+// (identical to above function, but separate string buffer needed -- nasty)
+static char *getCacheToken(char *prefix, char *suffix)
+{
+ static char *token = NULL;
+
+ checked_free(token);
+
+ token = getStringCat2WithSeparator(prefix, suffix, ".");
+
+ return token;
+}
+
+static char *getFileTimestampString(char *filename)
+{
+ return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
+}
+
+static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
+{
+ struct stat file_status;
+
+ if (timestamp_string == NULL)
+ return TRUE;
+
+ if (!fileExists(filename)) // file does not exist
+ return (atoi(timestamp_string) != 0);
+
+ if (stat(filename, &file_status) != 0) // cannot stat file
+ return TRUE;
+
+ return (file_status.st_mtime != atoi(timestamp_string));
+}
+
+static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
+{
+ char *identifier = level_node->subdir;
+ char *type_string = ARTWORK_DIRECTORY(type);
+ char *token_prefix = getCacheTokenPrefix(type_string, identifier);
+ char *token_main = getCacheToken(token_prefix, "CACHED");
+ char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
+ boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
+ TreeInfo *artwork_info = NULL;
+
+ if (!use_artworkinfo_cache)
+ return NULL;
+
+ if (optional_tokens_hash == NULL)
+ {
+ int i;
+
+ // create hash from list of optional tokens (for quick access)
+ optional_tokens_hash = newSetupFileHash();
+ for (i = 0; optional_tokens[i] != NULL; i++)
+ setHashEntry(optional_tokens_hash, optional_tokens[i], "");
+ }
+
+ if (cached)
+ {
+ int i;
+
+ artwork_info = newTreeInfo();
+ setTreeInfoToDefaults(artwork_info, type);
+
+ // set all structure fields according to the token/value pairs
+ ldi = *artwork_info;
+ for (i = 0; artworkinfo_tokens[i].type != -1; i++)
+ {
+ char *token_suffix = artworkinfo_tokens[i].text;
+ char *token = getCacheToken(token_prefix, token_suffix);
+ char *value = getHashEntry(artworkinfo_cache_old, token);
+ boolean optional =
+ (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
+
+ setSetupInfo(artworkinfo_tokens, i, value);
+
+ // check if cache entry for this item is mandatory, but missing
+ if (value == NULL && !optional)
+ {
+ Warn("missing cache entry '%s'", token);
+
+ cached = FALSE;
+ }
+ }
+
+ *artwork_info = ldi;
+ }
+
+ if (cached)
+ {
+ char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
+ LEVELINFO_FILENAME);
+ char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
+ ARTWORKINFO_FILENAME(type));
+
+ // check if corresponding "levelinfo.conf" file has changed
+ token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
+ cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
+
+ if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
+ cached = FALSE;
+
+ // check if corresponding "<artworkinfo>.conf" file has changed
+ token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
+ cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
+
+ if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
+ cached = FALSE;
+
+ checked_free(filename_levelinfo);
+ checked_free(filename_artworkinfo);
+ }
+
+ if (!cached && artwork_info != NULL)
+ {
+ freeTreeInfo(artwork_info);
+
+ return NULL;
+ }
+
+ return artwork_info;
+}
+
+static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
+ LevelDirTree *level_node, int type)
+{
+ char *identifier = level_node->subdir;
+ char *type_string = ARTWORK_DIRECTORY(type);
+ char *token_prefix = getCacheTokenPrefix(type_string, identifier);
+ char *token_main = getCacheToken(token_prefix, "CACHED");
+ boolean set_cache_timestamps = TRUE;
+ int i;
+
+ setHashEntry(artworkinfo_cache_new, token_main, "true");
+
+ if (set_cache_timestamps)
+ {
+ char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
+ LEVELINFO_FILENAME);
+ char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
+ ARTWORKINFO_FILENAME(type));
+ 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);
+
+ token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
+ setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
+
+ checked_free(filename_levelinfo);
+ checked_free(filename_artworkinfo);
+ checked_free(timestamp_levelinfo);
+ checked_free(timestamp_artworkinfo);
+ }
+
+ ldi = *artwork_info;
+ for (i = 0; artworkinfo_tokens[i].type != -1; i++)
+ {
+ char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
+ char *value = getSetupValue(artworkinfo_tokens[i].type,
+ artworkinfo_tokens[i].value);
+ if (value != NULL)
+ setHashEntry(artworkinfo_cache_new, token, value);
+ }
+}
+
+
+// ----------------------------------------------------------------------------
+// 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)