1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
22 #if !defined(PLATFORM_WIN32)
24 #include <sys/param.h>
34 #define NUM_LEVELCLASS_DESC 8
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
49 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
50 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
51 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
57 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
61 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
62 IS_LEVELCLASS_BD(n) ? 2 : \
63 IS_LEVELCLASS_EM(n) ? 3 : \
64 IS_LEVELCLASS_SP(n) ? 4 : \
65 IS_LEVELCLASS_DX(n) ? 5 : \
66 IS_LEVELCLASS_SB(n) ? 6 : \
67 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
68 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
72 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
73 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
74 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
78 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
79 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
80 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
83 #define TOKEN_VALUE_POSITION_SHORT 32
84 #define TOKEN_VALUE_POSITION_DEFAULT 40
85 #define TOKEN_COMMENT_POSITION_DEFAULT 60
87 #define MAX_COOKIE_LEN 256
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
94 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
102 /* ------------------------------------------------------------------------- */
104 /* ------------------------------------------------------------------------- */
106 static char *getLevelClassDescription(TreeInfo *ti)
108 int position = ti->sort_priority / 100;
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
113 return "Unknown Level Class";
116 static char *getUserLevelDir(char *level_subdir)
118 static char *userlevel_dir = NULL;
119 char *data_dir = getUserGameDataDir();
120 char *userlevel_subdir = LEVELS_DIRECTORY;
122 checked_free(userlevel_dir);
124 if (level_subdir != NULL)
125 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
127 userlevel_dir = getPath2(data_dir, userlevel_subdir);
129 return userlevel_dir;
132 static char *getScoreDir(char *level_subdir)
134 static char *score_dir = NULL;
135 char *data_dir = getCommonDataDir();
136 char *score_subdir = SCORES_DIRECTORY;
138 checked_free(score_dir);
140 if (level_subdir != NULL)
141 score_dir = getPath3(data_dir, score_subdir, level_subdir);
143 score_dir = getPath2(data_dir, score_subdir);
148 static char *getLevelSetupDir(char *level_subdir)
150 static char *levelsetup_dir = NULL;
151 char *data_dir = getUserGameDataDir();
152 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
154 checked_free(levelsetup_dir);
156 if (level_subdir != NULL)
157 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
159 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
161 return levelsetup_dir;
164 static char *getCacheDir()
166 static char *cache_dir = NULL;
168 if (cache_dir == NULL)
169 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
176 static char *level_dir = NULL;
179 return options.level_directory;
181 checked_free(level_dir);
183 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184 options.level_directory), node->fullpath);
189 char *getCurrentLevelDir()
191 return getLevelDirFromTreeInfo(leveldir_current);
194 static char *getTapeDir(char *level_subdir)
196 static char *tape_dir = NULL;
197 char *data_dir = getUserGameDataDir();
198 char *tape_subdir = TAPES_DIRECTORY;
200 checked_free(tape_dir);
202 if (level_subdir != NULL)
203 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
205 tape_dir = getPath2(data_dir, tape_subdir);
210 static char *getSolutionTapeDir()
212 static char *tape_dir = NULL;
213 char *data_dir = getCurrentLevelDir();
214 char *tape_subdir = TAPES_DIRECTORY;
216 checked_free(tape_dir);
218 tape_dir = getPath2(data_dir, tape_subdir);
223 static char *getDefaultGraphicsDir(char *graphics_subdir)
225 static char *graphics_dir = NULL;
227 if (graphics_subdir == NULL)
228 return options.graphics_directory;
230 checked_free(graphics_dir);
232 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
237 static char *getDefaultSoundsDir(char *sounds_subdir)
239 static char *sounds_dir = NULL;
241 if (sounds_subdir == NULL)
242 return options.sounds_directory;
244 checked_free(sounds_dir);
246 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
251 static char *getDefaultMusicDir(char *music_subdir)
253 static char *music_dir = NULL;
255 if (music_subdir == NULL)
256 return options.music_directory;
258 checked_free(music_dir);
260 music_dir = getPath2(options.music_directory, music_subdir);
265 static char *getDefaultArtworkSet(int type)
267 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
269 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
272 static char *getDefaultArtworkDir(int type)
274 return (type == TREE_TYPE_GRAPHICS_DIR ?
275 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276 type == TREE_TYPE_SOUNDS_DIR ?
277 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278 type == TREE_TYPE_MUSIC_DIR ?
279 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
282 static char *getUserGraphicsDir()
284 static char *usergraphics_dir = NULL;
286 if (usergraphics_dir == NULL)
287 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
289 return usergraphics_dir;
292 static char *getUserSoundsDir()
294 static char *usersounds_dir = NULL;
296 if (usersounds_dir == NULL)
297 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
299 return usersounds_dir;
302 static char *getUserMusicDir()
304 static char *usermusic_dir = NULL;
306 if (usermusic_dir == NULL)
307 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
309 return usermusic_dir;
312 static char *getSetupArtworkDir(TreeInfo *ti)
314 static char *artwork_dir = NULL;
316 checked_free(artwork_dir);
318 artwork_dir = getPath2(ti->basepath, ti->fullpath);
323 char *setLevelArtworkDir(TreeInfo *ti)
325 char **artwork_path_ptr, **artwork_set_ptr;
326 TreeInfo *level_artwork;
328 if (ti == NULL || leveldir_current == NULL)
331 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
334 checked_free(*artwork_path_ptr);
336 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
340 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
341 normally result in using the artwork configured in the setup menu. But
342 if an artwork subdirectory exists (which might contain custom artwork
343 or an artwork configuration file), this level artwork must be treated
344 as relative to the default "classic" artwork, not to the artwork that
345 is currently configured in the setup menu. */
347 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
349 checked_free(*artwork_set_ptr);
353 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
354 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
358 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
359 *artwork_set_ptr = NULL;
365 return *artwork_set_ptr;
368 inline static char *getLevelArtworkSet(int type)
370 if (leveldir_current == NULL)
373 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
376 inline static char *getLevelArtworkDir(int type)
378 if (leveldir_current == NULL)
379 return UNDEFINED_FILENAME;
381 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
384 char *getTapeFilename(int nr)
386 static char *filename = NULL;
387 char basename[MAX_FILENAME_LEN];
389 checked_free(filename);
391 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
392 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
397 char *getSolutionTapeFilename(int nr)
399 static char *filename = NULL;
400 char basename[MAX_FILENAME_LEN];
402 checked_free(filename);
404 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
405 filename = getPath2(getSolutionTapeDir(), basename);
410 char *getScoreFilename(int nr)
412 static char *filename = NULL;
413 char basename[MAX_FILENAME_LEN];
415 checked_free(filename);
417 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
418 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
423 char *getSetupFilename()
425 static char *filename = NULL;
427 checked_free(filename);
429 filename = getPath2(getSetupDir(), SETUP_FILENAME);
434 char *getEditorSetupFilename()
436 static char *filename = NULL;
438 checked_free(filename);
439 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
441 if (fileExists(filename))
444 checked_free(filename);
445 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
450 char *getHelpAnimFilename()
452 static char *filename = NULL;
454 checked_free(filename);
456 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
461 char *getHelpTextFilename()
463 static char *filename = NULL;
465 checked_free(filename);
467 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
472 char *getLevelSetInfoFilename()
474 static char *filename = NULL;
489 for (i = 0; basenames[i] != NULL; i++)
491 checked_free(filename);
492 filename = getPath2(getCurrentLevelDir(), basenames[i]);
494 if (fileExists(filename))
501 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
503 static char *filename = NULL;
506 sprintf(basename, "%s_%d.txt",
507 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
509 checked_free(filename);
510 filename = getPath2(getCurrentLevelDir(), basename);
512 if (fileExists(filename))
518 static char *getCorrectedArtworkBasename(char *basename)
520 char *basename_corrected = basename;
522 #if defined(PLATFORM_MSDOS)
523 if (program.filename_prefix != NULL)
525 int prefix_len = strlen(program.filename_prefix);
527 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
528 basename_corrected = &basename[prefix_len];
530 /* if corrected filename is still longer than standard MS-DOS filename
531 size (8 characters + 1 dot + 3 characters file extension), shorten
532 filename by writing file extension after 8th basename character */
533 if (strlen(basename_corrected) > 8 + 1 + 3)
535 static char *msdos_filename = NULL;
537 checked_free(msdos_filename);
539 msdos_filename = getStringCopy(basename_corrected);
540 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
542 basename_corrected = msdos_filename;
547 return basename_corrected;
550 char *getCustomImageFilename(char *basename)
552 static char *filename = NULL;
553 boolean skip_setup_artwork = FALSE;
555 checked_free(filename);
557 basename = getCorrectedArtworkBasename(basename);
559 if (!setup.override_level_graphics)
561 /* 1st try: look for special artwork in current level series directory */
562 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
563 if (fileExists(filename))
568 /* check if there is special artwork configured in level series config */
569 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
571 /* 2nd try: look for special artwork configured in level series config */
572 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
573 if (fileExists(filename))
578 /* take missing artwork configured in level set config from default */
579 skip_setup_artwork = TRUE;
583 if (!skip_setup_artwork)
585 /* 3rd try: look for special artwork in configured artwork directory */
586 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
587 if (fileExists(filename))
593 /* 4th try: look for default artwork in new default artwork directory */
594 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
595 if (fileExists(filename))
600 /* 5th try: look for default artwork in old default artwork directory */
601 filename = getPath2(options.graphics_directory, basename);
602 if (fileExists(filename))
605 return NULL; /* cannot find specified artwork file anywhere */
608 char *getCustomSoundFilename(char *basename)
610 static char *filename = NULL;
611 boolean skip_setup_artwork = FALSE;
613 checked_free(filename);
615 basename = getCorrectedArtworkBasename(basename);
617 if (!setup.override_level_sounds)
619 /* 1st try: look for special artwork in current level series directory */
620 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
621 if (fileExists(filename))
626 /* check if there is special artwork configured in level series config */
627 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
629 /* 2nd try: look for special artwork configured in level series config */
630 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
631 if (fileExists(filename))
636 /* take missing artwork configured in level set config from default */
637 skip_setup_artwork = TRUE;
641 if (!skip_setup_artwork)
643 /* 3rd try: look for special artwork in configured artwork directory */
644 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
645 if (fileExists(filename))
651 /* 4th try: look for default artwork in new default artwork directory */
652 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
653 if (fileExists(filename))
658 /* 5th try: look for default artwork in old default artwork directory */
659 filename = getPath2(options.sounds_directory, basename);
660 if (fileExists(filename))
663 return NULL; /* cannot find specified artwork file anywhere */
666 char *getCustomMusicFilename(char *basename)
668 static char *filename = NULL;
669 boolean skip_setup_artwork = FALSE;
671 checked_free(filename);
673 basename = getCorrectedArtworkBasename(basename);
675 if (!setup.override_level_music)
677 /* 1st try: look for special artwork in current level series directory */
678 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
679 if (fileExists(filename))
684 /* check if there is special artwork configured in level series config */
685 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
687 /* 2nd try: look for special artwork configured in level series config */
688 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
689 if (fileExists(filename))
694 /* take missing artwork configured in level set config from default */
695 skip_setup_artwork = TRUE;
699 if (!skip_setup_artwork)
701 /* 3rd try: look for special artwork in configured artwork directory */
702 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
703 if (fileExists(filename))
709 /* 4th try: look for default artwork in new default artwork directory */
710 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
711 if (fileExists(filename))
716 /* 5th try: look for default artwork in old default artwork directory */
717 filename = getPath2(options.music_directory, basename);
718 if (fileExists(filename))
721 return NULL; /* cannot find specified artwork file anywhere */
724 char *getCustomArtworkFilename(char *basename, int type)
726 if (type == ARTWORK_TYPE_GRAPHICS)
727 return getCustomImageFilename(basename);
728 else if (type == ARTWORK_TYPE_SOUNDS)
729 return getCustomSoundFilename(basename);
730 else if (type == ARTWORK_TYPE_MUSIC)
731 return getCustomMusicFilename(basename);
733 return UNDEFINED_FILENAME;
736 char *getCustomArtworkConfigFilename(int type)
738 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
741 char *getCustomArtworkLevelConfigFilename(int type)
743 static char *filename = NULL;
745 checked_free(filename);
747 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
752 char *getCustomMusicDirectory(void)
754 static char *directory = NULL;
755 boolean skip_setup_artwork = FALSE;
757 checked_free(directory);
759 if (!setup.override_level_music)
761 /* 1st try: look for special artwork in current level series directory */
762 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
763 if (fileExists(directory))
768 /* check if there is special artwork configured in level series config */
769 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
771 /* 2nd try: look for special artwork configured in level series config */
772 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
773 if (fileExists(directory))
778 /* take missing artwork configured in level set config from default */
779 skip_setup_artwork = TRUE;
783 if (!skip_setup_artwork)
785 /* 3rd try: look for special artwork in configured artwork directory */
786 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
787 if (fileExists(directory))
793 /* 4th try: look for default artwork in new default artwork directory */
794 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
795 if (fileExists(directory))
800 /* 5th try: look for default artwork in old default artwork directory */
801 directory = getStringCopy(options.music_directory);
802 if (fileExists(directory))
805 return NULL; /* cannot find specified artwork file anywhere */
808 void InitTapeDirectory(char *level_subdir)
810 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
811 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
812 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
815 void InitScoreDirectory(char *level_subdir)
817 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
818 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
819 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
822 static void SaveUserLevelInfo();
824 void InitUserLevelDirectory(char *level_subdir)
826 if (!fileExists(getUserLevelDir(level_subdir)))
828 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
829 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
830 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
836 void InitLevelSetupDirectory(char *level_subdir)
838 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
839 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
840 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
843 void InitCacheDirectory()
845 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
846 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
850 /* ------------------------------------------------------------------------- */
851 /* some functions to handle lists of level and artwork directories */
852 /* ------------------------------------------------------------------------- */
854 TreeInfo *newTreeInfo()
856 return checked_calloc(sizeof(TreeInfo));
859 TreeInfo *newTreeInfo_setDefaults(int type)
861 TreeInfo *ti = newTreeInfo();
863 setTreeInfoToDefaults(ti, type);
868 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
870 node_new->next = *node_first;
871 *node_first = node_new;
874 int numTreeInfo(TreeInfo *node)
887 boolean validLevelSeries(TreeInfo *node)
889 return (node != NULL && !node->node_group && !node->parent_link);
892 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
897 if (node->node_group) /* enter level group (step down into tree) */
898 return getFirstValidTreeInfoEntry(node->node_group);
899 else if (node->parent_link) /* skip start entry of level group */
901 if (node->next) /* get first real level series entry */
902 return getFirstValidTreeInfoEntry(node->next);
903 else /* leave empty level group and go on */
904 return getFirstValidTreeInfoEntry(node->node_parent->next);
906 else /* this seems to be a regular level series */
910 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
915 if (node->node_parent == NULL) /* top level group */
916 return *node->node_top;
917 else /* sub level group */
918 return node->node_parent->node_group;
921 int numTreeInfoInGroup(TreeInfo *node)
923 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
926 int posTreeInfo(TreeInfo *node)
928 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
933 if (node_cmp == node)
937 node_cmp = node_cmp->next;
943 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
945 TreeInfo *node_default = node;
960 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
962 if (identifier == NULL)
967 if (node->node_group)
969 TreeInfo *node_group;
971 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
976 else if (!node->parent_link)
978 if (strEqual(identifier, node->identifier))
988 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
989 TreeInfo *node, boolean skip_sets_without_levels)
996 if (!node->parent_link && !node->level_group &&
997 skip_sets_without_levels && node->levels == 0)
998 return cloneTreeNode(node_top, node_parent, node->next,
999 skip_sets_without_levels);
1002 node_new = getTreeInfoCopy(node); /* copy complete node */
1004 node_new = newTreeInfo();
1006 *node_new = *node; /* copy complete node */
1009 node_new->node_top = node_top; /* correct top node link */
1010 node_new->node_parent = node_parent; /* correct parent node link */
1012 if (node->level_group)
1013 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1014 skip_sets_without_levels);
1016 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1017 skip_sets_without_levels);
1022 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1024 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1026 *ti_new = ti_cloned;
1029 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1031 boolean settings_changed = FALSE;
1035 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1036 !strEqual(node->graphics_set, node->graphics_set_ecs))
1038 setString(&node->graphics_set, node->graphics_set_ecs);
1039 settings_changed = TRUE;
1041 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1042 !strEqual(node->graphics_set, node->graphics_set_aga))
1044 setString(&node->graphics_set, node->graphics_set_aga);
1045 settings_changed = TRUE;
1048 if (node->node_group != NULL)
1049 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1054 return settings_changed;
1057 void dumpTreeInfo(TreeInfo *node, int depth)
1061 printf("Dumping TreeInfo:\n");
1065 for (i = 0; i < (depth + 1) * 3; i++)
1068 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1069 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1071 if (node->node_group != NULL)
1072 dumpTreeInfo(node->node_group, depth + 1);
1078 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1079 int (*compare_function)(const void *,
1082 int num_nodes = numTreeInfo(*node_first);
1083 TreeInfo **sort_array;
1084 TreeInfo *node = *node_first;
1090 /* allocate array for sorting structure pointers */
1091 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1093 /* writing structure pointers to sorting array */
1094 while (i < num_nodes && node) /* double boundary check... */
1096 sort_array[i] = node;
1102 /* sorting the structure pointers in the sorting array */
1103 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1106 /* update the linkage of list elements with the sorted node array */
1107 for (i = 0; i < num_nodes - 1; i++)
1108 sort_array[i]->next = sort_array[i + 1];
1109 sort_array[num_nodes - 1]->next = NULL;
1111 /* update the linkage of the main list anchor pointer */
1112 *node_first = sort_array[0];
1116 /* now recursively sort the level group structures */
1120 if (node->node_group != NULL)
1121 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1127 void sortTreeInfo(TreeInfo **node_first)
1129 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1133 /* ========================================================================= */
1134 /* some stuff from "files.c" */
1135 /* ========================================================================= */
1137 #if defined(PLATFORM_WIN32)
1139 #define S_IRGRP S_IRUSR
1142 #define S_IROTH S_IRUSR
1145 #define S_IWGRP S_IWUSR
1148 #define S_IWOTH S_IWUSR
1151 #define S_IXGRP S_IXUSR
1154 #define S_IXOTH S_IXUSR
1157 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1162 #endif /* PLATFORM_WIN32 */
1164 /* file permissions for newly written files */
1165 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1166 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1167 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1169 #define MODE_W_PRIVATE (S_IWUSR)
1170 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1171 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1173 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1174 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1176 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1177 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1181 static char *dir = NULL;
1183 #if defined(PLATFORM_WIN32)
1186 dir = checked_malloc(MAX_PATH + 1);
1188 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1191 #elif defined(PLATFORM_UNIX)
1194 if ((dir = getenv("HOME")) == NULL)
1198 if ((pwd = getpwuid(getuid())) != NULL)
1199 dir = getStringCopy(pwd->pw_dir);
1211 char *getCommonDataDir(void)
1213 static char *common_data_dir = NULL;
1215 #if defined(PLATFORM_WIN32)
1216 if (common_data_dir == NULL)
1218 char *dir = checked_malloc(MAX_PATH + 1);
1220 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1221 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1222 common_data_dir = getPath2(dir, program.userdata_subdir);
1224 common_data_dir = options.rw_base_directory;
1227 if (common_data_dir == NULL)
1228 common_data_dir = options.rw_base_directory;
1231 return common_data_dir;
1234 char *getPersonalDataDir(void)
1236 static char *personal_data_dir = NULL;
1238 #if defined(PLATFORM_MACOSX)
1239 if (personal_data_dir == NULL)
1240 personal_data_dir = getPath2(getHomeDir(), "Documents");
1242 if (personal_data_dir == NULL)
1243 personal_data_dir = getHomeDir();
1246 return personal_data_dir;
1249 char *getUserGameDataDir(void)
1251 static char *user_game_data_dir = NULL;
1253 if (user_game_data_dir == NULL)
1254 user_game_data_dir = getPath2(getPersonalDataDir(),
1255 program.userdata_subdir);
1257 return user_game_data_dir;
1260 void updateUserGameDataDir()
1262 #if defined(PLATFORM_MACOSX)
1263 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1264 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1266 /* convert old Unix style game data directory to Mac OS X style, if needed */
1267 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1269 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1271 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1272 userdata_dir_old, userdata_dir_new);
1274 /* continue using Unix style data directory -- this should not happen */
1275 program.userdata_path = getPath2(getPersonalDataDir(),
1276 program.userdata_subdir_unix);
1280 free(userdata_dir_old);
1286 return getUserGameDataDir();
1289 static mode_t posix_umask(mode_t mask)
1291 #if defined(PLATFORM_UNIX)
1298 static int posix_mkdir(const char *pathname, mode_t mode)
1300 #if defined(PLATFORM_WIN32)
1301 return mkdir(pathname);
1303 return mkdir(pathname, mode);
1307 void createDirectory(char *dir, char *text, int permission_class)
1309 /* leave "other" permissions in umask untouched, but ensure group parts
1310 of USERDATA_DIR_MODE are not masked */
1311 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1312 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1313 mode_t normal_umask = posix_umask(0);
1314 mode_t group_umask = ~(dir_mode & S_IRWXG);
1315 posix_umask(normal_umask & group_umask);
1317 if (!fileExists(dir))
1318 if (posix_mkdir(dir, dir_mode) != 0)
1319 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1321 posix_umask(normal_umask); /* reset normal umask */
1324 void InitUserDataDirectory()
1326 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1329 void SetFilePermissions(char *filename, int permission_class)
1331 chmod(filename, (permission_class == PERMS_PRIVATE ?
1332 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1335 char *getCookie(char *file_type)
1337 static char cookie[MAX_COOKIE_LEN + 1];
1339 if (strlen(program.cookie_prefix) + 1 +
1340 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1341 return "[COOKIE ERROR]"; /* should never happen */
1343 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1344 program.cookie_prefix, file_type,
1345 program.version_major, program.version_minor);
1350 int getFileVersionFromCookieString(const char *cookie)
1352 const char *ptr_cookie1, *ptr_cookie2;
1353 const char *pattern1 = "_FILE_VERSION_";
1354 const char *pattern2 = "?.?";
1355 const int len_cookie = strlen(cookie);
1356 const int len_pattern1 = strlen(pattern1);
1357 const int len_pattern2 = strlen(pattern2);
1358 const int len_pattern = len_pattern1 + len_pattern2;
1359 int version_major, version_minor;
1361 if (len_cookie <= len_pattern)
1364 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1365 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1367 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1370 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1371 ptr_cookie2[1] != '.' ||
1372 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1375 version_major = ptr_cookie2[0] - '0';
1376 version_minor = ptr_cookie2[2] - '0';
1378 return VERSION_IDENT(version_major, version_minor, 0, 0);
1381 boolean checkCookieString(const char *cookie, const char *template)
1383 const char *pattern = "_FILE_VERSION_?.?";
1384 const int len_cookie = strlen(cookie);
1385 const int len_template = strlen(template);
1386 const int len_pattern = strlen(pattern);
1388 if (len_cookie != len_template)
1391 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1397 /* ------------------------------------------------------------------------- */
1398 /* setup file list and hash handling functions */
1399 /* ------------------------------------------------------------------------- */
1401 char *getFormattedSetupEntry(char *token, char *value)
1404 static char entry[MAX_LINE_LEN];
1406 /* if value is an empty string, just return token without value */
1410 /* start with the token and some spaces to format output line */
1411 sprintf(entry, "%s:", token);
1412 for (i = strlen(entry); i < token_value_position; i++)
1415 /* continue with the token's value */
1416 strcat(entry, value);
1421 SetupFileList *newSetupFileList(char *token, char *value)
1423 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1425 new->token = getStringCopy(token);
1426 new->value = getStringCopy(value);
1433 void freeSetupFileList(SetupFileList *list)
1438 checked_free(list->token);
1439 checked_free(list->value);
1442 freeSetupFileList(list->next);
1447 char *getListEntry(SetupFileList *list, char *token)
1452 if (strEqual(list->token, token))
1455 return getListEntry(list->next, token);
1458 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1463 if (strEqual(list->token, token))
1465 checked_free(list->value);
1467 list->value = getStringCopy(value);
1471 else if (list->next == NULL)
1472 return (list->next = newSetupFileList(token, value));
1474 return setListEntry(list->next, token, value);
1477 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1482 if (list->next == NULL)
1483 return (list->next = newSetupFileList(token, value));
1485 return addListEntry(list->next, token, value);
1489 static void printSetupFileList(SetupFileList *list)
1494 printf("token: '%s'\n", list->token);
1495 printf("value: '%s'\n", list->value);
1497 printSetupFileList(list->next);
1502 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1503 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1504 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1505 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1507 #define insert_hash_entry hashtable_insert
1508 #define search_hash_entry hashtable_search
1509 #define change_hash_entry hashtable_change
1510 #define remove_hash_entry hashtable_remove
1513 static unsigned int get_hash_from_key(void *key)
1518 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1519 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1520 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1521 it works better than many other constants, prime or not) has never been
1522 adequately explained.
1524 If you just want to have a good hash function, and cannot wait, djb2
1525 is one of the best string hash functions i know. It has excellent
1526 distribution and speed on many different sets of keys and table sizes.
1527 You are not likely to do better with one of the "well known" functions
1528 such as PJW, K&R, etc.
1530 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1533 char *str = (char *)key;
1534 unsigned int hash = 5381;
1537 while ((c = *str++))
1538 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1543 static int keys_are_equal(void *key1, void *key2)
1545 return (strEqual((char *)key1, (char *)key2));
1548 SetupFileHash *newSetupFileHash()
1550 SetupFileHash *new_hash =
1551 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1553 if (new_hash == NULL)
1554 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1559 void freeSetupFileHash(SetupFileHash *hash)
1564 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1567 char *getHashEntry(SetupFileHash *hash, char *token)
1572 return search_hash_entry(hash, token);
1575 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1582 value_copy = getStringCopy(value);
1584 /* change value; if it does not exist, insert it as new */
1585 if (!change_hash_entry(hash, token, value_copy))
1586 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1587 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1590 char *removeHashEntry(SetupFileHash *hash, char *token)
1595 return remove_hash_entry(hash, token);
1599 static void printSetupFileHash(SetupFileHash *hash)
1601 BEGIN_HASH_ITERATION(hash, itr)
1603 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1604 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1606 END_HASH_ITERATION(hash, itr)
1610 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1611 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1613 static boolean token_value_separator_found = FALSE;
1614 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1615 static boolean token_value_separator_warning = FALSE;
1618 static boolean getTokenValueFromSetupLineExt(char *line,
1619 char **token_ptr, char **value_ptr,
1620 char *filename, char *line_raw,
1622 boolean separator_required)
1624 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1625 char *token, *value, *line_ptr;
1627 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1628 if (line_raw == NULL)
1630 strncpy(line_copy, line, MAX_LINE_LEN);
1631 line_copy[MAX_LINE_LEN] = '\0';
1634 strcpy(line_raw_copy, line_copy);
1635 line_raw = line_raw_copy;
1638 /* cut trailing comment from input line */
1639 for (line_ptr = line; *line_ptr; line_ptr++)
1641 if (*line_ptr == '#')
1648 /* cut trailing whitespaces from input line */
1649 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1650 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1653 /* ignore empty lines */
1657 /* cut leading whitespaces from token */
1658 for (token = line; *token; token++)
1659 if (*token != ' ' && *token != '\t')
1662 /* start with empty value as reliable default */
1665 token_value_separator_found = FALSE;
1667 /* find end of token to determine start of value */
1668 for (line_ptr = token; *line_ptr; line_ptr++)
1671 /* first look for an explicit token/value separator, like ':' or '=' */
1672 if (*line_ptr == ':' || *line_ptr == '=')
1674 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1677 *line_ptr = '\0'; /* terminate token string */
1678 value = line_ptr + 1; /* set beginning of value */
1680 token_value_separator_found = TRUE;
1686 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1687 /* fallback: if no token/value separator found, also allow whitespaces */
1688 if (!token_value_separator_found && !separator_required)
1690 for (line_ptr = token; *line_ptr; line_ptr++)
1692 if (*line_ptr == ' ' || *line_ptr == '\t')
1694 *line_ptr = '\0'; /* terminate token string */
1695 value = line_ptr + 1; /* set beginning of value */
1697 token_value_separator_found = TRUE;
1703 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1704 if (token_value_separator_found)
1706 if (!token_value_separator_warning)
1708 Error(ERR_INFO_LINE, "-");
1710 if (filename != NULL)
1712 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1713 Error(ERR_INFO, "- config file: '%s'", filename);
1717 Error(ERR_WARN, "missing token/value separator(s):");
1720 token_value_separator_warning = TRUE;
1723 if (filename != NULL)
1724 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1726 Error(ERR_INFO, "- line: '%s'", line_raw);
1732 /* cut trailing whitespaces from token */
1733 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1734 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1737 /* cut leading whitespaces from value */
1738 for (; *value; value++)
1739 if (*value != ' ' && *value != '\t')
1744 value = "true"; /* treat tokens without value as "true" */
1753 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1755 /* while the internal (old) interface does not require a token/value
1756 separator (for downwards compatibility with existing files which
1757 don't use them), it is mandatory for the external (new) interface */
1759 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1763 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1764 boolean top_recursion_level, boolean is_hash)
1766 static SetupFileHash *include_filename_hash = NULL;
1767 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1768 char *token, *value, *line_ptr;
1769 void *insert_ptr = NULL;
1770 boolean read_continued_line = FALSE;
1773 int token_count = 0;
1775 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1776 token_value_separator_warning = FALSE;
1779 if (!(file = fopen(filename, MODE_READ)))
1781 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1786 /* use "insert pointer" to store list end for constant insertion complexity */
1788 insert_ptr = setup_file_data;
1790 /* on top invocation, create hash to mark included files (to prevent loops) */
1791 if (top_recursion_level)
1792 include_filename_hash = newSetupFileHash();
1794 /* mark this file as already included (to prevent including it again) */
1795 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1799 /* read next line of input file */
1800 if (!fgets(line, MAX_LINE_LEN, file))
1803 /* check if line was completely read and is terminated by line break */
1804 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1807 /* cut trailing line break (this can be newline and/or carriage return) */
1808 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1809 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1812 /* copy raw input line for later use (mainly debugging output) */
1813 strcpy(line_raw, line);
1815 if (read_continued_line)
1818 /* !!! ??? WHY ??? !!! */
1819 /* cut leading whitespaces from input line */
1820 for (line_ptr = line; *line_ptr; line_ptr++)
1821 if (*line_ptr != ' ' && *line_ptr != '\t')
1825 /* append new line to existing line, if there is enough space */
1826 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1827 strcat(previous_line, line_ptr);
1829 strcpy(line, previous_line); /* copy storage buffer to line */
1831 read_continued_line = FALSE;
1834 /* if the last character is '\', continue at next line */
1835 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1837 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1838 strcpy(previous_line, line); /* copy line to storage buffer */
1840 read_continued_line = TRUE;
1845 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1846 line_raw, line_nr, FALSE))
1851 if (strEqual(token, "include"))
1853 if (getHashEntry(include_filename_hash, value) == NULL)
1855 char *basepath = getBasePath(filename);
1856 char *basename = getBaseName(value);
1857 char *filename_include = getPath2(basepath, basename);
1860 Error(ERR_INFO, "[including file '%s']", filename_include);
1863 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1867 free(filename_include);
1871 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1877 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1879 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1888 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1889 if (token_value_separator_warning)
1890 Error(ERR_INFO_LINE, "-");
1893 if (token_count == 0)
1894 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1896 if (top_recursion_level)
1897 freeSetupFileHash(include_filename_hash);
1904 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1905 boolean top_recursion_level, boolean is_hash)
1907 static SetupFileHash *include_filename_hash = NULL;
1908 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1909 char *token, *value, *line_ptr;
1910 void *insert_ptr = NULL;
1911 boolean read_continued_line = FALSE;
1914 int token_count = 0;
1916 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1917 token_value_separator_warning = FALSE;
1920 if (!(file = fopen(filename, MODE_READ)))
1922 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1927 /* use "insert pointer" to store list end for constant insertion complexity */
1929 insert_ptr = setup_file_data;
1931 /* on top invocation, create hash to mark included files (to prevent loops) */
1932 if (top_recursion_level)
1933 include_filename_hash = newSetupFileHash();
1935 /* mark this file as already included (to prevent including it again) */
1936 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1940 /* read next line of input file */
1941 if (!fgets(line, MAX_LINE_LEN, file))
1944 /* check if line was completely read and is terminated by line break */
1945 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1948 /* cut trailing line break (this can be newline and/or carriage return) */
1949 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1950 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1953 /* copy raw input line for later use (mainly debugging output) */
1954 strcpy(line_raw, line);
1956 if (read_continued_line)
1958 /* cut leading whitespaces from input line */
1959 for (line_ptr = line; *line_ptr; line_ptr++)
1960 if (*line_ptr != ' ' && *line_ptr != '\t')
1963 /* append new line to existing line, if there is enough space */
1964 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1965 strcat(previous_line, line_ptr);
1967 strcpy(line, previous_line); /* copy storage buffer to line */
1969 read_continued_line = FALSE;
1972 /* if the last character is '\', continue at next line */
1973 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1975 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1976 strcpy(previous_line, line); /* copy line to storage buffer */
1978 read_continued_line = TRUE;
1983 /* cut trailing comment from input line */
1984 for (line_ptr = line; *line_ptr; line_ptr++)
1986 if (*line_ptr == '#')
1993 /* cut trailing whitespaces from input line */
1994 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1995 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1998 /* ignore empty lines */
2002 /* cut leading whitespaces from token */
2003 for (token = line; *token; token++)
2004 if (*token != ' ' && *token != '\t')
2007 /* start with empty value as reliable default */
2010 token_value_separator_found = FALSE;
2012 /* find end of token to determine start of value */
2013 for (line_ptr = token; *line_ptr; line_ptr++)
2016 /* first look for an explicit token/value separator, like ':' or '=' */
2017 if (*line_ptr == ':' || *line_ptr == '=')
2019 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2022 *line_ptr = '\0'; /* terminate token string */
2023 value = line_ptr + 1; /* set beginning of value */
2025 token_value_separator_found = TRUE;
2031 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2032 /* fallback: if no token/value separator found, also allow whitespaces */
2033 if (!token_value_separator_found)
2035 for (line_ptr = token; *line_ptr; line_ptr++)
2037 if (*line_ptr == ' ' || *line_ptr == '\t')
2039 *line_ptr = '\0'; /* terminate token string */
2040 value = line_ptr + 1; /* set beginning of value */
2042 token_value_separator_found = TRUE;
2048 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2049 if (token_value_separator_found)
2051 if (!token_value_separator_warning)
2053 Error(ERR_INFO_LINE, "-");
2054 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2055 Error(ERR_INFO, "- config file: '%s'", filename);
2057 token_value_separator_warning = TRUE;
2060 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2066 /* cut trailing whitespaces from token */
2067 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2068 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2071 /* cut leading whitespaces from value */
2072 for (; *value; value++)
2073 if (*value != ' ' && *value != '\t')
2078 value = "true"; /* treat tokens without value as "true" */
2083 if (strEqual(token, "include"))
2085 if (getHashEntry(include_filename_hash, value) == NULL)
2087 char *basepath = getBasePath(filename);
2088 char *basename = getBaseName(value);
2089 char *filename_include = getPath2(basepath, basename);
2092 Error(ERR_INFO, "[including file '%s']", filename_include);
2095 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2099 free(filename_include);
2103 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2109 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2111 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2120 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2121 if (token_value_separator_warning)
2122 Error(ERR_INFO_LINE, "-");
2125 if (token_count == 0)
2126 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2128 if (top_recursion_level)
2129 freeSetupFileHash(include_filename_hash);
2135 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2139 if (!(file = fopen(filename, MODE_WRITE)))
2141 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2146 BEGIN_HASH_ITERATION(hash, itr)
2148 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2149 HASH_ITERATION_VALUE(itr)));
2151 END_HASH_ITERATION(hash, itr)
2156 SetupFileList *loadSetupFileList(char *filename)
2158 SetupFileList *setup_file_list = newSetupFileList("", "");
2159 SetupFileList *first_valid_list_entry;
2161 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2163 freeSetupFileList(setup_file_list);
2168 first_valid_list_entry = setup_file_list->next;
2170 /* free empty list header */
2171 setup_file_list->next = NULL;
2172 freeSetupFileList(setup_file_list);
2174 return first_valid_list_entry;
2177 SetupFileHash *loadSetupFileHash(char *filename)
2179 SetupFileHash *setup_file_hash = newSetupFileHash();
2181 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2183 freeSetupFileHash(setup_file_hash);
2188 return setup_file_hash;
2191 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2192 char *filename, char *identifier)
2194 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2197 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2198 else if (!checkCookieString(value, identifier))
2199 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2203 /* ========================================================================= */
2204 /* setup file stuff */
2205 /* ========================================================================= */
2207 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2208 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2209 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2211 /* level directory info */
2212 #define LEVELINFO_TOKEN_IDENTIFIER 0
2213 #define LEVELINFO_TOKEN_NAME 1
2214 #define LEVELINFO_TOKEN_NAME_SORTING 2
2215 #define LEVELINFO_TOKEN_AUTHOR 3
2216 #define LEVELINFO_TOKEN_YEAR 4
2217 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2218 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2219 #define LEVELINFO_TOKEN_TESTED_BY 7
2220 #define LEVELINFO_TOKEN_LEVELS 8
2221 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2222 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2223 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2224 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2225 #define LEVELINFO_TOKEN_READONLY 13
2226 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2227 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2228 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2229 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2230 #define LEVELINFO_TOKEN_MUSIC_SET 18
2231 #define LEVELINFO_TOKEN_FILENAME 19
2232 #define LEVELINFO_TOKEN_FILETYPE 20
2233 #define LEVELINFO_TOKEN_HANDICAP 21
2234 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2236 #define NUM_LEVELINFO_TOKENS 23
2238 static LevelDirTree ldi;
2240 static struct TokenInfo levelinfo_tokens[] =
2242 /* level directory info */
2243 { TYPE_STRING, &ldi.identifier, "identifier" },
2244 { TYPE_STRING, &ldi.name, "name" },
2245 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2246 { TYPE_STRING, &ldi.author, "author" },
2247 { TYPE_STRING, &ldi.year, "year" },
2248 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2249 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2250 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2251 { TYPE_INTEGER, &ldi.levels, "levels" },
2252 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2253 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2254 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2255 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2256 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2257 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2258 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2259 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2260 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2261 { TYPE_STRING, &ldi.music_set, "music_set" },
2262 { TYPE_STRING, &ldi.level_filename, "filename" },
2263 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2264 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2265 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2268 static struct TokenInfo artworkinfo_tokens[] =
2270 /* artwork directory info */
2271 { TYPE_STRING, &ldi.identifier, "identifier" },
2272 { TYPE_STRING, &ldi.subdir, "subdir" },
2273 { TYPE_STRING, &ldi.name, "name" },
2274 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2275 { TYPE_STRING, &ldi.author, "author" },
2276 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2277 { TYPE_STRING, &ldi.basepath, "basepath" },
2278 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2279 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2280 { TYPE_INTEGER, &ldi.color, "color" },
2281 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2286 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2290 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2291 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2292 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2293 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2296 ti->node_parent = NULL;
2297 ti->node_group = NULL;
2304 ti->fullpath = NULL;
2305 ti->basepath = NULL;
2306 ti->identifier = NULL;
2307 ti->name = getStringCopy(ANONYMOUS_NAME);
2308 ti->name_sorting = NULL;
2309 ti->author = getStringCopy(ANONYMOUS_NAME);
2312 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2313 ti->latest_engine = FALSE; /* default: get from level */
2314 ti->parent_link = FALSE;
2315 ti->in_user_dir = FALSE;
2316 ti->user_defined = FALSE;
2318 ti->class_desc = NULL;
2320 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2322 if (ti->type == TREE_TYPE_LEVEL_DIR)
2324 ti->imported_from = NULL;
2325 ti->imported_by = NULL;
2326 ti->tested_by = NULL;
2328 ti->graphics_set_ecs = NULL;
2329 ti->graphics_set_aga = NULL;
2330 ti->graphics_set = NULL;
2331 ti->sounds_set = NULL;
2332 ti->music_set = NULL;
2333 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2334 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2335 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2337 ti->level_filename = NULL;
2338 ti->level_filetype = NULL;
2341 ti->first_level = 0;
2343 ti->level_group = FALSE;
2344 ti->handicap_level = 0;
2345 ti->readonly = TRUE;
2346 ti->handicap = TRUE;
2347 ti->skip_levels = FALSE;
2351 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2355 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2357 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2362 /* copy all values from the parent structure */
2364 ti->type = parent->type;
2366 ti->node_top = parent->node_top;
2367 ti->node_parent = parent;
2368 ti->node_group = NULL;
2375 ti->fullpath = NULL;
2376 ti->basepath = NULL;
2377 ti->identifier = NULL;
2378 ti->name = getStringCopy(ANONYMOUS_NAME);
2379 ti->name_sorting = NULL;
2380 ti->author = getStringCopy(parent->author);
2381 ti->year = getStringCopy(parent->year);
2383 ti->sort_priority = parent->sort_priority;
2384 ti->latest_engine = parent->latest_engine;
2385 ti->parent_link = FALSE;
2386 ti->in_user_dir = parent->in_user_dir;
2387 ti->user_defined = parent->user_defined;
2388 ti->color = parent->color;
2389 ti->class_desc = getStringCopy(parent->class_desc);
2391 ti->infotext = getStringCopy(parent->infotext);
2393 if (ti->type == TREE_TYPE_LEVEL_DIR)
2395 ti->imported_from = getStringCopy(parent->imported_from);
2396 ti->imported_by = getStringCopy(parent->imported_by);
2397 ti->tested_by = getStringCopy(parent->tested_by);
2399 ti->graphics_set_ecs = NULL;
2400 ti->graphics_set_aga = NULL;
2401 ti->graphics_set = NULL;
2402 ti->sounds_set = NULL;
2403 ti->music_set = NULL;
2404 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2405 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2406 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2408 ti->level_filename = NULL;
2409 ti->level_filetype = NULL;
2412 ti->first_level = 0;
2414 ti->level_group = FALSE;
2415 ti->handicap_level = 0;
2416 ti->readonly = TRUE;
2417 ti->handicap = TRUE;
2418 ti->skip_levels = FALSE;
2422 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2424 TreeInfo *ti_copy = newTreeInfo();
2426 /* copy all values from the original structure */
2428 ti_copy->type = ti->type;
2430 ti_copy->node_top = ti->node_top;
2431 ti_copy->node_parent = ti->node_parent;
2432 ti_copy->node_group = ti->node_group;
2433 ti_copy->next = ti->next;
2435 ti_copy->cl_first = ti->cl_first;
2436 ti_copy->cl_cursor = ti->cl_cursor;
2438 ti_copy->subdir = getStringCopy(ti->subdir);
2439 ti_copy->fullpath = getStringCopy(ti->fullpath);
2440 ti_copy->basepath = getStringCopy(ti->basepath);
2441 ti_copy->identifier = getStringCopy(ti->identifier);
2442 ti_copy->name = getStringCopy(ti->name);
2443 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2444 ti_copy->author = getStringCopy(ti->author);
2445 ti_copy->year = getStringCopy(ti->year);
2446 ti_copy->imported_from = getStringCopy(ti->imported_from);
2447 ti_copy->imported_by = getStringCopy(ti->imported_by);
2448 ti_copy->tested_by = getStringCopy(ti->tested_by);
2450 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2451 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2452 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2453 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2454 ti_copy->music_set = getStringCopy(ti->music_set);
2455 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2456 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2457 ti_copy->music_path = getStringCopy(ti->music_path);
2459 ti_copy->level_filename = getStringCopy(ti->level_filename);
2460 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2462 ti_copy->levels = ti->levels;
2463 ti_copy->first_level = ti->first_level;
2464 ti_copy->last_level = ti->last_level;
2465 ti_copy->sort_priority = ti->sort_priority;
2467 ti_copy->latest_engine = ti->latest_engine;
2469 ti_copy->level_group = ti->level_group;
2470 ti_copy->parent_link = ti->parent_link;
2471 ti_copy->in_user_dir = ti->in_user_dir;
2472 ti_copy->user_defined = ti->user_defined;
2473 ti_copy->readonly = ti->readonly;
2474 ti_copy->handicap = ti->handicap;
2475 ti_copy->skip_levels = ti->skip_levels;
2477 ti_copy->color = ti->color;
2478 ti_copy->class_desc = getStringCopy(ti->class_desc);
2479 ti_copy->handicap_level = ti->handicap_level;
2481 ti_copy->infotext = getStringCopy(ti->infotext);
2486 static void freeTreeInfo(TreeInfo *ti)
2491 checked_free(ti->subdir);
2492 checked_free(ti->fullpath);
2493 checked_free(ti->basepath);
2494 checked_free(ti->identifier);
2496 checked_free(ti->name);
2497 checked_free(ti->name_sorting);
2498 checked_free(ti->author);
2499 checked_free(ti->year);
2501 checked_free(ti->class_desc);
2503 checked_free(ti->infotext);
2505 if (ti->type == TREE_TYPE_LEVEL_DIR)
2507 checked_free(ti->imported_from);
2508 checked_free(ti->imported_by);
2509 checked_free(ti->tested_by);
2511 checked_free(ti->graphics_set_ecs);
2512 checked_free(ti->graphics_set_aga);
2513 checked_free(ti->graphics_set);
2514 checked_free(ti->sounds_set);
2515 checked_free(ti->music_set);
2517 checked_free(ti->graphics_path);
2518 checked_free(ti->sounds_path);
2519 checked_free(ti->music_path);
2521 checked_free(ti->level_filename);
2522 checked_free(ti->level_filetype);
2528 void setSetupInfo(struct TokenInfo *token_info,
2529 int token_nr, char *token_value)
2531 int token_type = token_info[token_nr].type;
2532 void *setup_value = token_info[token_nr].value;
2534 if (token_value == NULL)
2537 /* set setup field to corresponding token value */
2542 *(boolean *)setup_value = get_boolean_from_string(token_value);
2546 *(Key *)setup_value = getKeyFromKeyName(token_value);
2550 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2554 *(int *)setup_value = get_integer_from_string(token_value);
2558 checked_free(*(char **)setup_value);
2559 *(char **)setup_value = getStringCopy(token_value);
2567 static int compareTreeInfoEntries(const void *object1, const void *object2)
2569 const TreeInfo *entry1 = *((TreeInfo **)object1);
2570 const TreeInfo *entry2 = *((TreeInfo **)object2);
2571 int class_sorting1, class_sorting2;
2574 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2576 class_sorting1 = LEVELSORTING(entry1);
2577 class_sorting2 = LEVELSORTING(entry2);
2581 class_sorting1 = ARTWORKSORTING(entry1);
2582 class_sorting2 = ARTWORKSORTING(entry2);
2585 if (entry1->parent_link || entry2->parent_link)
2586 compare_result = (entry1->parent_link ? -1 : +1);
2587 else if (entry1->sort_priority == entry2->sort_priority)
2589 char *name1 = getStringToLower(entry1->name_sorting);
2590 char *name2 = getStringToLower(entry2->name_sorting);
2592 compare_result = strcmp(name1, name2);
2597 else if (class_sorting1 == class_sorting2)
2598 compare_result = entry1->sort_priority - entry2->sort_priority;
2600 compare_result = class_sorting1 - class_sorting2;
2602 return compare_result;
2605 static void createParentTreeInfoNode(TreeInfo *node_parent)
2609 if (node_parent == NULL)
2612 ti_new = newTreeInfo();
2613 setTreeInfoToDefaults(ti_new, node_parent->type);
2615 ti_new->node_parent = node_parent;
2616 ti_new->parent_link = TRUE;
2618 setString(&ti_new->identifier, node_parent->identifier);
2619 setString(&ti_new->name, ".. (parent directory)");
2620 setString(&ti_new->name_sorting, ti_new->name);
2622 setString(&ti_new->subdir, "..");
2623 setString(&ti_new->fullpath, node_parent->fullpath);
2625 ti_new->sort_priority = node_parent->sort_priority;
2626 ti_new->latest_engine = node_parent->latest_engine;
2628 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2630 pushTreeInfo(&node_parent->node_group, ti_new);
2634 /* -------------------------------------------------------------------------- */
2635 /* functions for handling level and custom artwork info cache */
2636 /* -------------------------------------------------------------------------- */
2638 static void LoadArtworkInfoCache()
2640 InitCacheDirectory();
2642 if (artworkinfo_cache_old == NULL)
2644 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2646 /* try to load artwork info hash from already existing cache file */
2647 artworkinfo_cache_old = loadSetupFileHash(filename);
2649 /* if no artwork info cache file was found, start with empty hash */
2650 if (artworkinfo_cache_old == NULL)
2651 artworkinfo_cache_old = newSetupFileHash();
2656 if (artworkinfo_cache_new == NULL)
2657 artworkinfo_cache_new = newSetupFileHash();
2660 static void SaveArtworkInfoCache()
2662 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2664 InitCacheDirectory();
2666 saveSetupFileHash(artworkinfo_cache_new, filename);
2671 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2673 static char *prefix = NULL;
2675 checked_free(prefix);
2677 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2682 /* (identical to above function, but separate string buffer needed -- nasty) */
2683 static char *getCacheToken(char *prefix, char *suffix)
2685 static char *token = NULL;
2687 checked_free(token);
2689 token = getStringCat2WithSeparator(prefix, suffix, ".");
2694 static char *getFileTimestamp(char *filename)
2696 struct stat file_status;
2698 if (stat(filename, &file_status) != 0) /* cannot stat file */
2699 return getStringCopy(i_to_a(0));
2701 return getStringCopy(i_to_a(file_status.st_mtime));
2704 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2706 struct stat file_status;
2708 if (timestamp_string == NULL)
2711 if (stat(filename, &file_status) != 0) /* cannot stat file */
2714 return (file_status.st_mtime != atoi(timestamp_string));
2717 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2719 char *identifier = level_node->subdir;
2720 char *type_string = ARTWORK_DIRECTORY(type);
2721 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2722 char *token_main = getCacheToken(token_prefix, "CACHED");
2723 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2724 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2725 TreeInfo *artwork_info = NULL;
2727 if (!use_artworkinfo_cache)
2734 artwork_info = newTreeInfo();
2735 setTreeInfoToDefaults(artwork_info, type);
2737 /* set all structure fields according to the token/value pairs */
2738 ldi = *artwork_info;
2739 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2741 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2742 char *value = getHashEntry(artworkinfo_cache_old, token);
2744 setSetupInfo(artworkinfo_tokens, i, value);
2746 /* check if cache entry for this item is invalid or incomplete */
2750 Error(ERR_WARN, "cache entry '%s' invalid", token);
2757 *artwork_info = ldi;
2762 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2763 LEVELINFO_FILENAME);
2764 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2765 ARTWORKINFO_FILENAME(type));
2767 /* check if corresponding "levelinfo.conf" file has changed */
2768 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2769 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2771 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2774 /* check if corresponding "<artworkinfo>.conf" file has changed */
2775 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2776 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2778 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2783 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2786 checked_free(filename_levelinfo);
2787 checked_free(filename_artworkinfo);
2790 if (!cached && artwork_info != NULL)
2792 freeTreeInfo(artwork_info);
2797 return artwork_info;
2800 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2801 LevelDirTree *level_node, int type)
2803 char *identifier = level_node->subdir;
2804 char *type_string = ARTWORK_DIRECTORY(type);
2805 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2806 char *token_main = getCacheToken(token_prefix, "CACHED");
2807 boolean set_cache_timestamps = TRUE;
2810 setHashEntry(artworkinfo_cache_new, token_main, "true");
2812 if (set_cache_timestamps)
2814 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2815 LEVELINFO_FILENAME);
2816 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2817 ARTWORKINFO_FILENAME(type));
2818 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2819 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2821 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2822 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2824 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2825 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2827 checked_free(filename_levelinfo);
2828 checked_free(filename_artworkinfo);
2829 checked_free(timestamp_levelinfo);
2830 checked_free(timestamp_artworkinfo);
2833 ldi = *artwork_info;
2834 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2836 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2837 char *value = getSetupValue(artworkinfo_tokens[i].type,
2838 artworkinfo_tokens[i].value);
2840 setHashEntry(artworkinfo_cache_new, token, value);
2845 /* -------------------------------------------------------------------------- */
2846 /* functions for loading level info and custom artwork info */
2847 /* -------------------------------------------------------------------------- */
2849 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2850 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2852 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2853 TreeInfo *node_parent,
2854 char *level_directory,
2855 char *directory_name)
2857 static unsigned long progress_delay = 0;
2858 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2859 char *directory_path = getPath2(level_directory, directory_name);
2860 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2861 SetupFileHash *setup_file_hash;
2862 LevelDirTree *leveldir_new = NULL;
2865 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2866 if (!options.debug && !fileExists(filename))
2868 free(directory_path);
2874 setup_file_hash = loadSetupFileHash(filename);
2876 if (setup_file_hash == NULL)
2878 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2880 free(directory_path);
2886 leveldir_new = newTreeInfo();
2889 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2891 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2893 leveldir_new->subdir = getStringCopy(directory_name);
2895 checkSetupFileHashIdentifier(setup_file_hash, filename,
2896 getCookie("LEVELINFO"));
2898 /* set all structure fields according to the token/value pairs */
2899 ldi = *leveldir_new;
2900 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2901 setSetupInfo(levelinfo_tokens, i,
2902 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2903 *leveldir_new = ldi;
2905 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2906 setString(&leveldir_new->name, leveldir_new->subdir);
2908 if (leveldir_new->identifier == NULL)
2909 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2911 if (leveldir_new->name_sorting == NULL)
2912 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2914 if (node_parent == NULL) /* top level group */
2916 leveldir_new->basepath = getStringCopy(level_directory);
2917 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2919 else /* sub level group */
2921 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2922 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2926 if (leveldir_new->levels < 1)
2927 leveldir_new->levels = 1;
2930 leveldir_new->last_level =
2931 leveldir_new->first_level + leveldir_new->levels - 1;
2933 leveldir_new->in_user_dir =
2934 (!strEqual(leveldir_new->basepath, options.level_directory));
2936 /* adjust some settings if user's private level directory was detected */
2937 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2938 leveldir_new->in_user_dir &&
2939 (strEqual(leveldir_new->subdir, getLoginName()) ||
2940 strEqual(leveldir_new->name, getLoginName()) ||
2941 strEqual(leveldir_new->author, getRealName())))
2943 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2944 leveldir_new->readonly = FALSE;
2947 leveldir_new->user_defined =
2948 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2950 leveldir_new->color = LEVELCOLOR(leveldir_new);
2952 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2954 leveldir_new->handicap_level = /* set handicap to default value */
2955 (leveldir_new->user_defined || !leveldir_new->handicap ?
2956 leveldir_new->last_level : leveldir_new->first_level);
2959 if (leveldir_new->level_group ||
2960 DelayReached(&progress_delay, progress_delay_value))
2961 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2963 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2967 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2969 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2971 /* skip level sets without levels (which are probably artwork base sets) */
2973 freeSetupFileHash(setup_file_hash);
2974 free(directory_path);
2982 pushTreeInfo(node_first, leveldir_new);
2984 freeSetupFileHash(setup_file_hash);
2986 if (leveldir_new->level_group)
2988 /* create node to link back to current level directory */
2989 createParentTreeInfoNode(leveldir_new);
2991 /* recursively step into sub-directory and look for more level series */
2992 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2993 leveldir_new, directory_path);
2996 free(directory_path);
3002 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3003 TreeInfo *node_parent,
3004 char *level_directory)
3007 struct dirent *dir_entry;
3008 boolean valid_entry_found = FALSE;
3010 if ((dir = opendir(level_directory)) == NULL)
3012 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3016 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3018 struct stat file_status;
3019 char *directory_name = dir_entry->d_name;
3020 char *directory_path = getPath2(level_directory, directory_name);
3022 /* skip entries for current and parent directory */
3023 if (strEqual(directory_name, ".") ||
3024 strEqual(directory_name, ".."))
3026 free(directory_path);
3030 /* find out if directory entry is itself a directory */
3031 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3032 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3034 free(directory_path);
3038 free(directory_path);
3040 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3041 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3042 strEqual(directory_name, MUSIC_DIRECTORY))
3045 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3052 /* special case: top level directory may directly contain "levelinfo.conf" */
3053 if (node_parent == NULL && !valid_entry_found)
3055 /* check if this directory directly contains a file "levelinfo.conf" */
3056 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3057 level_directory, ".");
3060 if (!valid_entry_found)
3061 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3065 boolean AdjustGraphicsForEMC()
3067 boolean settings_changed = FALSE;
3069 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3070 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3072 return settings_changed;
3075 void LoadLevelInfo()
3077 InitUserLevelDirectory(getLoginName());
3079 DrawInitText("Loading level series", 120, FC_GREEN);
3081 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3082 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3084 /* after loading all level set information, clone the level directory tree
3085 and remove all level sets without levels (these may still contain artwork
3086 to be offered in the setup menu as "custom artwork", and are therefore
3087 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3088 leveldir_first_all = leveldir_first;
3089 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3091 AdjustGraphicsForEMC();
3093 /* before sorting, the first entries will be from the user directory */
3094 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3096 if (leveldir_first == NULL)
3097 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3099 sortTreeInfo(&leveldir_first);
3102 dumpTreeInfo(leveldir_first, 0);
3106 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3107 TreeInfo *node_parent,
3108 char *base_directory,
3109 char *directory_name, int type)
3111 char *directory_path = getPath2(base_directory, directory_name);
3112 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3113 SetupFileHash *setup_file_hash = NULL;
3114 TreeInfo *artwork_new = NULL;
3117 if (fileExists(filename))
3118 setup_file_hash = loadSetupFileHash(filename);
3120 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3123 struct dirent *dir_entry;
3124 boolean valid_file_found = FALSE;
3126 if ((dir = opendir(directory_path)) != NULL)
3128 while ((dir_entry = readdir(dir)) != NULL)
3130 char *entry_name = dir_entry->d_name;
3132 if (FileIsArtworkType(entry_name, type))
3134 valid_file_found = TRUE;
3142 if (!valid_file_found)
3144 if (!strEqual(directory_name, "."))
3145 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3147 free(directory_path);
3154 artwork_new = newTreeInfo();
3157 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3159 setTreeInfoToDefaults(artwork_new, type);
3161 artwork_new->subdir = getStringCopy(directory_name);
3163 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3166 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3169 /* set all structure fields according to the token/value pairs */
3171 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3172 setSetupInfo(levelinfo_tokens, i,
3173 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3176 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3177 setString(&artwork_new->name, artwork_new->subdir);
3179 if (artwork_new->identifier == NULL)
3180 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3182 if (artwork_new->name_sorting == NULL)
3183 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3186 if (node_parent == NULL) /* top level group */
3188 artwork_new->basepath = getStringCopy(base_directory);
3189 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3191 else /* sub level group */
3193 artwork_new->basepath = getStringCopy(node_parent->basepath);
3194 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3197 artwork_new->in_user_dir =
3198 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3200 /* (may use ".sort_priority" from "setup_file_hash" above) */
3201 artwork_new->color = ARTWORKCOLOR(artwork_new);
3203 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3205 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3207 if (strEqual(artwork_new->subdir, "."))
3209 if (artwork_new->user_defined)
3211 setString(&artwork_new->identifier, "private");
3212 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3216 setString(&artwork_new->identifier, "classic");
3217 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3220 /* set to new values after changing ".sort_priority" */
3221 artwork_new->color = ARTWORKCOLOR(artwork_new);
3223 setString(&artwork_new->class_desc,
3224 getLevelClassDescription(artwork_new));
3228 setString(&artwork_new->identifier, artwork_new->subdir);
3231 setString(&artwork_new->name, artwork_new->identifier);
3232 setString(&artwork_new->name_sorting, artwork_new->name);
3236 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3239 pushTreeInfo(node_first, artwork_new);
3241 freeSetupFileHash(setup_file_hash);
3243 free(directory_path);
3249 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3250 TreeInfo *node_parent,
3251 char *base_directory, int type)
3254 struct dirent *dir_entry;
3255 boolean valid_entry_found = FALSE;
3257 if ((dir = opendir(base_directory)) == NULL)
3259 /* display error if directory is main "options.graphics_directory" etc. */
3260 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3261 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3266 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3268 struct stat file_status;
3269 char *directory_name = dir_entry->d_name;
3270 char *directory_path = getPath2(base_directory, directory_name);
3272 /* skip directory entries for current and parent directory */
3273 if (strEqual(directory_name, ".") ||
3274 strEqual(directory_name, ".."))
3276 free(directory_path);
3280 /* skip directory entries which are not a directory or are not accessible */
3281 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3282 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3284 free(directory_path);
3288 free(directory_path);
3290 /* check if this directory contains artwork with or without config file */
3291 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3293 directory_name, type);
3298 /* check if this directory directly contains artwork itself */
3299 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3300 base_directory, ".",
3302 if (!valid_entry_found)
3303 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3307 static TreeInfo *getDummyArtworkInfo(int type)
3309 /* this is only needed when there is completely no artwork available */
3310 TreeInfo *artwork_new = newTreeInfo();
3312 setTreeInfoToDefaults(artwork_new, type);
3314 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3315 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3316 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3318 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3319 setString(&artwork_new->name, UNDEFINED_FILENAME);
3320 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3325 void LoadArtworkInfo()
3327 LoadArtworkInfoCache();
3329 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3331 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3332 options.graphics_directory,
3333 TREE_TYPE_GRAPHICS_DIR);
3334 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3335 getUserGraphicsDir(),
3336 TREE_TYPE_GRAPHICS_DIR);
3338 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3339 options.sounds_directory,
3340 TREE_TYPE_SOUNDS_DIR);
3341 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3343 TREE_TYPE_SOUNDS_DIR);
3345 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3346 options.music_directory,
3347 TREE_TYPE_MUSIC_DIR);
3348 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3350 TREE_TYPE_MUSIC_DIR);
3352 if (artwork.gfx_first == NULL)
3353 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3354 if (artwork.snd_first == NULL)
3355 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3356 if (artwork.mus_first == NULL)
3357 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3359 /* before sorting, the first entries will be from the user directory */
3360 artwork.gfx_current =
3361 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3362 if (artwork.gfx_current == NULL)
3363 artwork.gfx_current =
3364 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3365 if (artwork.gfx_current == NULL)
3366 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3368 artwork.snd_current =
3369 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3370 if (artwork.snd_current == NULL)
3371 artwork.snd_current =
3372 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3373 if (artwork.snd_current == NULL)
3374 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3376 artwork.mus_current =
3377 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3378 if (artwork.mus_current == NULL)
3379 artwork.mus_current =
3380 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3381 if (artwork.mus_current == NULL)
3382 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3384 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3385 artwork.snd_current_identifier = artwork.snd_current->identifier;
3386 artwork.mus_current_identifier = artwork.mus_current->identifier;
3389 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3390 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3391 printf("music set == %s\n\n", artwork.mus_current_identifier);
3394 sortTreeInfo(&artwork.gfx_first);
3395 sortTreeInfo(&artwork.snd_first);
3396 sortTreeInfo(&artwork.mus_first);
3399 dumpTreeInfo(artwork.gfx_first, 0);
3400 dumpTreeInfo(artwork.snd_first, 0);
3401 dumpTreeInfo(artwork.mus_first, 0);
3405 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3406 LevelDirTree *level_node)
3408 static unsigned long progress_delay = 0;
3409 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3410 int type = (*artwork_node)->type;
3412 /* recursively check all level directories for artwork sub-directories */
3416 /* check all tree entries for artwork, but skip parent link entries */
3417 if (!level_node->parent_link)
3419 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3420 boolean cached = (artwork_new != NULL);
3424 pushTreeInfo(artwork_node, artwork_new);
3428 TreeInfo *topnode_last = *artwork_node;
3429 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3430 ARTWORK_DIRECTORY(type));
3432 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3434 if (topnode_last != *artwork_node) /* check for newly added node */
3436 artwork_new = *artwork_node;
3438 setString(&artwork_new->identifier, level_node->subdir);
3439 setString(&artwork_new->name, level_node->name);
3440 setString(&artwork_new->name_sorting, level_node->name_sorting);
3442 artwork_new->sort_priority = level_node->sort_priority;
3443 artwork_new->color = LEVELCOLOR(artwork_new);
3449 /* insert artwork info (from old cache or filesystem) into new cache */
3450 if (artwork_new != NULL)
3451 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3455 if (level_node->level_group ||
3456 DelayReached(&progress_delay, progress_delay_value))
3457 DrawInitText(level_node->name, 150, FC_YELLOW);
3460 if (level_node->node_group != NULL)
3461 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3463 level_node = level_node->next;
3467 void LoadLevelArtworkInfo()
3469 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3471 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3472 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3473 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3475 SaveArtworkInfoCache();
3477 /* needed for reloading level artwork not known at ealier stage */
3479 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3481 artwork.gfx_current =
3482 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3483 if (artwork.gfx_current == NULL)
3484 artwork.gfx_current =
3485 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3486 if (artwork.gfx_current == NULL)
3487 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3490 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3492 artwork.snd_current =
3493 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3494 if (artwork.snd_current == NULL)
3495 artwork.snd_current =
3496 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3497 if (artwork.snd_current == NULL)
3498 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3501 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3503 artwork.mus_current =
3504 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3505 if (artwork.mus_current == NULL)
3506 artwork.mus_current =
3507 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3508 if (artwork.mus_current == NULL)
3509 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3512 sortTreeInfo(&artwork.gfx_first);
3513 sortTreeInfo(&artwork.snd_first);
3514 sortTreeInfo(&artwork.mus_first);
3517 dumpTreeInfo(artwork.gfx_first, 0);
3518 dumpTreeInfo(artwork.snd_first, 0);
3519 dumpTreeInfo(artwork.mus_first, 0);
3523 static void SaveUserLevelInfo()
3525 LevelDirTree *level_info;
3530 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3532 if (!(file = fopen(filename, MODE_WRITE)))
3534 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3539 level_info = newTreeInfo();
3541 /* always start with reliable default values */
3542 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3544 setString(&level_info->name, getLoginName());
3545 setString(&level_info->author, getRealName());
3546 level_info->levels = 100;
3547 level_info->first_level = 1;
3549 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3551 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3552 getCookie("LEVELINFO")));
3555 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3557 if (i == LEVELINFO_TOKEN_NAME ||
3558 i == LEVELINFO_TOKEN_AUTHOR ||
3559 i == LEVELINFO_TOKEN_LEVELS ||
3560 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3561 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3563 /* just to make things nicer :) */
3564 if (i == LEVELINFO_TOKEN_AUTHOR)
3565 fprintf(file, "\n");
3568 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3572 SetFilePermissions(filename, PERMS_PRIVATE);
3574 freeTreeInfo(level_info);
3578 char *getSetupValue(int type, void *value)
3580 static char value_string[MAX_LINE_LEN];
3588 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3592 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3596 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3600 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3604 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3608 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3612 sprintf(value_string, "%d", *(int *)value);
3616 if (*(char **)value == NULL)
3619 strcpy(value_string, *(char **)value);
3623 value_string[0] = '\0';
3627 if (type & TYPE_GHOSTED)
3628 strcpy(value_string, "n/a");
3630 return value_string;
3633 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3637 static char token_string[MAX_LINE_LEN];
3638 int token_type = token_info[token_nr].type;
3639 void *setup_value = token_info[token_nr].value;
3640 char *token_text = token_info[token_nr].text;
3641 char *value_string = getSetupValue(token_type, setup_value);
3643 /* build complete token string */
3644 sprintf(token_string, "%s%s", prefix, token_text);
3646 /* build setup entry line */
3647 line = getFormattedSetupEntry(token_string, value_string);
3649 if (token_type == TYPE_KEY_X11)
3651 Key key = *(Key *)setup_value;
3652 char *keyname = getKeyNameFromKey(key);
3654 /* add comment, if useful */
3655 if (!strEqual(keyname, "(undefined)") &&
3656 !strEqual(keyname, "(unknown)"))
3658 /* add at least one whitespace */
3660 for (i = strlen(line); i < token_comment_position; i++)
3664 strcat(line, keyname);
3671 void LoadLevelSetup_LastSeries()
3673 /* ----------------------------------------------------------------------- */
3674 /* ~/.<program>/levelsetup.conf */
3675 /* ----------------------------------------------------------------------- */
3677 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3678 SetupFileHash *level_setup_hash = NULL;
3680 /* always start with reliable default values */
3681 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3683 if ((level_setup_hash = loadSetupFileHash(filename)))
3685 char *last_level_series =
3686 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3688 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3690 if (leveldir_current == NULL)
3691 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3693 checkSetupFileHashIdentifier(level_setup_hash, filename,
3694 getCookie("LEVELSETUP"));
3696 freeSetupFileHash(level_setup_hash);
3699 Error(ERR_WARN, "using default setup values");
3704 void SaveLevelSetup_LastSeries()
3706 /* ----------------------------------------------------------------------- */
3707 /* ~/.<program>/levelsetup.conf */
3708 /* ----------------------------------------------------------------------- */
3710 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3711 char *level_subdir = leveldir_current->subdir;
3714 InitUserDataDirectory();
3716 if (!(file = fopen(filename, MODE_WRITE)))
3718 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3723 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3724 getCookie("LEVELSETUP")));
3725 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3730 SetFilePermissions(filename, PERMS_PRIVATE);
3735 static void checkSeriesInfo()
3737 static char *level_directory = NULL;
3739 struct dirent *dir_entry;
3741 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3743 level_directory = getPath2((leveldir_current->in_user_dir ?
3744 getUserLevelDir(NULL) :
3745 options.level_directory),
3746 leveldir_current->fullpath);
3748 if ((dir = opendir(level_directory)) == NULL)
3750 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3754 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3756 if (strlen(dir_entry->d_name) > 4 &&
3757 dir_entry->d_name[3] == '.' &&
3758 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3760 char levelnum_str[4];
3763 strncpy(levelnum_str, dir_entry->d_name, 3);
3764 levelnum_str[3] = '\0';
3766 levelnum_value = atoi(levelnum_str);
3769 if (levelnum_value < leveldir_current->first_level)
3771 Error(ERR_WARN, "additional level %d found", levelnum_value);
3772 leveldir_current->first_level = levelnum_value;
3774 else if (levelnum_value > leveldir_current->last_level)
3776 Error(ERR_WARN, "additional level %d found", levelnum_value);
3777 leveldir_current->last_level = levelnum_value;
3786 void LoadLevelSetup_SeriesInfo()
3789 SetupFileHash *level_setup_hash = NULL;
3790 char *level_subdir = leveldir_current->subdir;
3792 /* always start with reliable default values */
3793 level_nr = leveldir_current->first_level;
3795 checkSeriesInfo(leveldir_current);
3797 /* ----------------------------------------------------------------------- */
3798 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3799 /* ----------------------------------------------------------------------- */
3801 level_subdir = leveldir_current->subdir;
3803 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3805 if ((level_setup_hash = loadSetupFileHash(filename)))
3809 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3813 level_nr = atoi(token_value);
3815 if (level_nr < leveldir_current->first_level)
3816 level_nr = leveldir_current->first_level;
3817 if (level_nr > leveldir_current->last_level)
3818 level_nr = leveldir_current->last_level;
3821 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3825 int level_nr = atoi(token_value);
3827 if (level_nr < leveldir_current->first_level)
3828 level_nr = leveldir_current->first_level;
3829 if (level_nr > leveldir_current->last_level + 1)
3830 level_nr = leveldir_current->last_level;
3832 if (leveldir_current->user_defined || !leveldir_current->handicap)
3833 level_nr = leveldir_current->last_level;
3835 leveldir_current->handicap_level = level_nr;
3838 checkSetupFileHashIdentifier(level_setup_hash, filename,
3839 getCookie("LEVELSETUP"));
3841 freeSetupFileHash(level_setup_hash);
3844 Error(ERR_WARN, "using default setup values");
3849 void SaveLevelSetup_SeriesInfo()
3852 char *level_subdir = leveldir_current->subdir;
3853 char *level_nr_str = int2str(level_nr, 0);
3854 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3857 /* ----------------------------------------------------------------------- */
3858 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3859 /* ----------------------------------------------------------------------- */
3861 InitLevelSetupDirectory(level_subdir);
3863 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3865 if (!(file = fopen(filename, MODE_WRITE)))
3867 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3872 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3873 getCookie("LEVELSETUP")));
3874 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3876 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3877 handicap_level_str));
3881 SetFilePermissions(filename, PERMS_PRIVATE);