+ }
+
+ // copy all values from the parent structure
+
+ ti->type = parent->type;
+
+ ti->node_top = parent->node_top;
+ ti->node_parent = parent;
+ ti->node_group = NULL;
+ ti->next = NULL;
+
+ ti->cl_first = -1;
+ ti->cl_cursor = -1;
+
+ ti->subdir = NULL;
+ ti->fullpath = NULL;
+ ti->basepath = NULL;
+ ti->identifier = NULL;
+ ti->name = getStringCopy(ANONYMOUS_NAME);
+ ti->name_sorting = NULL;
+ ti->author = getStringCopy(parent->author);
+ ti->year = getStringCopy(parent->year);
+
+ ti->program_title = getStringCopy(parent->program_title);
+ ti->program_copyright = getStringCopy(parent->program_copyright);
+ ti->program_company = getStringCopy(parent->program_company);
+
+ ti->sort_priority = parent->sort_priority;
+ ti->latest_engine = parent->latest_engine;
+ ti->parent_link = FALSE;
+ ti->in_user_dir = parent->in_user_dir;
+ ti->user_defined = parent->user_defined;
+ ti->color = parent->color;
+ ti->class_desc = getStringCopy(parent->class_desc);
+
+ ti->infotext = getStringCopy(parent->infotext);
+
+ if (ti->type == TREE_TYPE_LEVEL_DIR)
+ {
+ ti->imported_from = getStringCopy(parent->imported_from);
+ ti->imported_by = getStringCopy(parent->imported_by);
+ ti->tested_by = getStringCopy(parent->tested_by);
+
+ ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
+ ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
+ ti->graphics_set = getStringCopy(parent->graphics_set);
+ ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
+ ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
+ ti->sounds_set = getStringCopy(parent->sounds_set);
+ ti->music_set = getStringCopy(parent->music_set);
+ ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
+ ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
+ ti->music_path = getStringCopy(UNDEFINED_FILENAME);
+
+ ti->level_filename = getStringCopy(parent->level_filename);
+ ti->level_filetype = getStringCopy(parent->level_filetype);
+
+ ti->special_flags = getStringCopy(parent->special_flags);
+
+ ti->levels = parent->levels;
+ ti->first_level = parent->first_level;
+ ti->last_level = parent->last_level;
+ ti->level_group = FALSE;
+ ti->handicap_level = parent->handicap_level;
+ ti->readonly = parent->readonly;
+ ti->handicap = parent->handicap;
+ ti->skip_levels = parent->skip_levels;
+
+ ti->use_emc_tiles = parent->use_emc_tiles;
+ }
+}
+
+static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
+{
+ TreeInfo *ti_copy = newTreeInfo();
+
+ // copy all values from the original structure
+
+ ti_copy->type = ti->type;
+
+ ti_copy->node_top = ti->node_top;
+ ti_copy->node_parent = ti->node_parent;
+ ti_copy->node_group = ti->node_group;
+ ti_copy->next = ti->next;
+
+ ti_copy->cl_first = ti->cl_first;
+ ti_copy->cl_cursor = ti->cl_cursor;
+
+ ti_copy->subdir = getStringCopy(ti->subdir);
+ ti_copy->fullpath = getStringCopy(ti->fullpath);
+ ti_copy->basepath = getStringCopy(ti->basepath);
+ ti_copy->identifier = getStringCopy(ti->identifier);
+ ti_copy->name = getStringCopy(ti->name);
+ ti_copy->name_sorting = getStringCopy(ti->name_sorting);
+ ti_copy->author = getStringCopy(ti->author);
+ ti_copy->year = getStringCopy(ti->year);
+
+ ti_copy->program_title = getStringCopy(ti->program_title);
+ ti_copy->program_copyright = getStringCopy(ti->program_copyright);
+ ti_copy->program_company = getStringCopy(ti->program_company);
+
+ ti_copy->imported_from = getStringCopy(ti->imported_from);
+ ti_copy->imported_by = getStringCopy(ti->imported_by);
+ ti_copy->tested_by = getStringCopy(ti->tested_by);
+
+ ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
+ ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
+ ti_copy->graphics_set = getStringCopy(ti->graphics_set);
+ ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
+ ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
+ ti_copy->sounds_set = getStringCopy(ti->sounds_set);
+ ti_copy->music_set = getStringCopy(ti->music_set);
+ ti_copy->graphics_path = getStringCopy(ti->graphics_path);
+ ti_copy->sounds_path = getStringCopy(ti->sounds_path);
+ ti_copy->music_path = getStringCopy(ti->music_path);
+
+ 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;
+ ti_copy->sort_priority = ti->sort_priority;
+
+ ti_copy->latest_engine = ti->latest_engine;
+
+ ti_copy->level_group = ti->level_group;
+ ti_copy->parent_link = ti->parent_link;
+ ti_copy->in_user_dir = ti->in_user_dir;
+ ti_copy->user_defined = ti->user_defined;
+ ti_copy->readonly = ti->readonly;
+ ti_copy->handicap = ti->handicap;
+ ti_copy->skip_levels = ti->skip_levels;
+
+ ti_copy->use_emc_tiles = ti->use_emc_tiles;
+
+ ti_copy->color = ti->color;
+ ti_copy->class_desc = getStringCopy(ti->class_desc);
+ ti_copy->handicap_level = ti->handicap_level;
+
+ ti_copy->infotext = getStringCopy(ti->infotext);
+
+ return ti_copy;
+}
+
+void freeTreeInfo(TreeInfo *ti)
+{
+ if (ti == NULL)
+ return;
+
+ checked_free(ti->subdir);
+ checked_free(ti->fullpath);
+ checked_free(ti->basepath);
+ checked_free(ti->identifier);
+
+ checked_free(ti->name);
+ checked_free(ti->name_sorting);
+ checked_free(ti->author);
+ checked_free(ti->year);
+
+ checked_free(ti->program_title);
+ checked_free(ti->program_copyright);
+ checked_free(ti->program_company);
+
+ checked_free(ti->class_desc);
+
+ checked_free(ti->infotext);
+
+ if (ti->type == TREE_TYPE_LEVEL_DIR)
+ {
+ checked_free(ti->imported_from);
+ checked_free(ti->imported_by);
+ checked_free(ti->tested_by);
+
+ checked_free(ti->graphics_set_ecs);
+ checked_free(ti->graphics_set_aga);
+ checked_free(ti->graphics_set);
+ checked_free(ti->sounds_set_default);
+ checked_free(ti->sounds_set_lowpass);
+ checked_free(ti->sounds_set);
+ checked_free(ti->music_set);
+
+ checked_free(ti->graphics_path);
+ checked_free(ti->sounds_path);
+ checked_free(ti->music_path);
+
+ checked_free(ti->level_filename);
+ checked_free(ti->level_filetype);
+
+ checked_free(ti->special_flags);
+ }
+
+ // recursively free child node
+ if (ti->node_group)
+ freeTreeInfo(ti->node_group);
+
+ // recursively free next node
+ if (ti->next)
+ freeTreeInfo(ti->next);
+
+ checked_free(ti);
+}
+
+void setSetupInfo(struct TokenInfo *token_info,
+ int token_nr, char *token_value)
+{
+ int token_type = token_info[token_nr].type;
+ void *setup_value = token_info[token_nr].value;
+
+ if (token_value == NULL)
+ return;
+
+ // set setup field to corresponding token value
+ switch (token_type)
+ {
+ case TYPE_BOOLEAN:
+ case TYPE_SWITCH:
+ *(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;
+
+ case TYPE_KEY_X11:
+ *(Key *)setup_value = getKeyFromX11KeyName(token_value);
+ break;
+
+ case TYPE_INTEGER:
+ *(int *)setup_value = get_integer_from_string(token_value);
+ break;
+
+ case TYPE_STRING:
+ checked_free(*(char **)setup_value);
+ *(char **)setup_value = getStringCopy(token_value);
+ break;
+
+ case TYPE_PLAYER:
+ *(int *)setup_value = get_player_nr_from_string(token_value);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int compareTreeInfoEntries(const void *object1, const void *object2)
+{
+ const TreeInfo *entry1 = *((TreeInfo **)object1);
+ const TreeInfo *entry2 = *((TreeInfo **)object2);
+ int class_sorting1 = 0, class_sorting2 = 0;
+ int compare_result;
+
+ if (entry1->type == TREE_TYPE_LEVEL_DIR)
+ {
+ class_sorting1 = LEVELSORTING(entry1);
+ class_sorting2 = LEVELSORTING(entry2);
+ }
+ else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
+ entry1->type == TREE_TYPE_SOUNDS_DIR ||
+ entry1->type == TREE_TYPE_MUSIC_DIR)
+ {
+ class_sorting1 = ARTWORKSORTING(entry1);
+ class_sorting2 = ARTWORKSORTING(entry2);
+ }
+
+ if (entry1->parent_link || entry2->parent_link)
+ compare_result = (entry1->parent_link ? -1 : +1);
+ else if (entry1->sort_priority == entry2->sort_priority)
+ {
+ char *name1 = getStringToLower(entry1->name_sorting);
+ char *name2 = getStringToLower(entry2->name_sorting);
+
+ compare_result = strcmp(name1, name2);
+
+ free(name1);
+ free(name2);
+ }
+ else if (class_sorting1 == class_sorting2)
+ compare_result = entry1->sort_priority - entry2->sort_priority;
+ else
+ compare_result = class_sorting1 - class_sorting2;
+
+ return compare_result;
+}
+
+static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
+{
+ TreeInfo *ti_new;
+
+ if (node_parent == NULL)
+ return NULL;
+
+ ti_new = newTreeInfo();
+ setTreeInfoToDefaults(ti_new, node_parent->type);
+
+ ti_new->node_parent = node_parent;
+ ti_new->parent_link = TRUE;
+
+ setString(&ti_new->identifier, node_parent->identifier);
+ setString(&ti_new->name, ".. (parent directory)");
+ setString(&ti_new->name_sorting, ti_new->name);
+
+ setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
+ setString(&ti_new->fullpath, node_parent->fullpath);
+
+ ti_new->sort_priority = node_parent->sort_priority;
+ ti_new->latest_engine = node_parent->latest_engine;
+
+ setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
+
+ pushTreeInfo(&node_parent->node_group, ti_new);
+
+ return ti_new;
+}
+
+static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
+{
+ TreeInfo *ti_new, *ti_new2;
+
+ if (node_first == NULL)
+ return NULL;
+
+ ti_new = newTreeInfo();
+ setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
+
+ ti_new->node_parent = NULL;
+ ti_new->parent_link = FALSE;
+
+ setString(&ti_new->identifier, node_first->identifier);
+ setString(&ti_new->name, "level sets");
+ setString(&ti_new->name_sorting, ti_new->name);
+
+ setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
+ setString(&ti_new->fullpath, ".");
+
+ ti_new->sort_priority = node_first->sort_priority;;
+ ti_new->latest_engine = node_first->latest_engine;
+
+ setString(&ti_new->class_desc, "level sets");
+
+ ti_new->node_group = node_first;
+ ti_new->level_group = TRUE;
+
+ ti_new2 = createParentTreeInfoNode(ti_new);
+
+ setString(&ti_new2->name, ".. (main menu)");
+ setString(&ti_new2->name_sorting, ti_new2->name);
+
+ return ti_new;
+}
+
+
+// ----------------------------------------------------------------------------
+// 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);
+
+ // 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();
+}
+
+static void SaveArtworkInfoCache(void)
+{
+ 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 (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;
+ }