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 int compareTreeInfoEntries(const void *, const void *);
93 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
94 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
96 static SetupFileHash *artworkinfo_cache_old = NULL;
97 static SetupFileHash *artworkinfo_cache_new = NULL;
98 static boolean use_artworkinfo_cache = TRUE;
101 /* ------------------------------------------------------------------------- */
103 /* ------------------------------------------------------------------------- */
105 static char *getLevelClassDescription(TreeInfo *ti)
107 int position = ti->sort_priority / 100;
109 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
110 return levelclass_desc[position];
112 return "Unknown Level Class";
115 static char *getUserLevelDir(char *level_subdir)
117 static char *userlevel_dir = NULL;
118 char *data_dir = getUserGameDataDir();
119 char *userlevel_subdir = LEVELS_DIRECTORY;
121 checked_free(userlevel_dir);
123 if (level_subdir != NULL)
124 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
126 userlevel_dir = getPath2(data_dir, userlevel_subdir);
128 return userlevel_dir;
131 static char *getScoreDir(char *level_subdir)
133 static char *score_dir = NULL;
134 char *data_dir = getCommonDataDir();
135 char *score_subdir = SCORES_DIRECTORY;
137 checked_free(score_dir);
139 if (level_subdir != NULL)
140 score_dir = getPath3(data_dir, score_subdir, level_subdir);
142 score_dir = getPath2(data_dir, score_subdir);
147 static char *getLevelSetupDir(char *level_subdir)
149 static char *levelsetup_dir = NULL;
150 char *data_dir = getUserGameDataDir();
151 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
153 checked_free(levelsetup_dir);
155 if (level_subdir != NULL)
156 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
158 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
160 return levelsetup_dir;
163 static char *getCacheDir()
165 static char *cache_dir = NULL;
167 if (cache_dir == NULL)
168 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
173 static char *getLevelDirFromTreeInfo(TreeInfo *node)
175 static char *level_dir = NULL;
178 return options.level_directory;
180 checked_free(level_dir);
182 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
183 options.level_directory), node->fullpath);
188 char *getCurrentLevelDir()
190 return getLevelDirFromTreeInfo(leveldir_current);
193 static char *getTapeDir(char *level_subdir)
195 static char *tape_dir = NULL;
196 char *data_dir = getUserGameDataDir();
197 char *tape_subdir = TAPES_DIRECTORY;
199 checked_free(tape_dir);
201 if (level_subdir != NULL)
202 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
204 tape_dir = getPath2(data_dir, tape_subdir);
209 static char *getSolutionTapeDir()
211 static char *tape_dir = NULL;
212 char *data_dir = getCurrentLevelDir();
213 char *tape_subdir = TAPES_DIRECTORY;
215 checked_free(tape_dir);
217 tape_dir = getPath2(data_dir, tape_subdir);
222 static char *getDefaultGraphicsDir(char *graphics_subdir)
224 static char *graphics_dir = NULL;
226 if (graphics_subdir == NULL)
227 return options.graphics_directory;
229 checked_free(graphics_dir);
231 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
236 static char *getDefaultSoundsDir(char *sounds_subdir)
238 static char *sounds_dir = NULL;
240 if (sounds_subdir == NULL)
241 return options.sounds_directory;
243 checked_free(sounds_dir);
245 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
250 static char *getDefaultMusicDir(char *music_subdir)
252 static char *music_dir = NULL;
254 if (music_subdir == NULL)
255 return options.music_directory;
257 checked_free(music_dir);
259 music_dir = getPath2(options.music_directory, music_subdir);
264 static char *getDefaultArtworkSet(int type)
266 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
267 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
268 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
271 static char *getDefaultArtworkDir(int type)
273 return (type == TREE_TYPE_GRAPHICS_DIR ?
274 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
275 type == TREE_TYPE_SOUNDS_DIR ?
276 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
277 type == TREE_TYPE_MUSIC_DIR ?
278 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
281 static char *getUserGraphicsDir()
283 static char *usergraphics_dir = NULL;
285 if (usergraphics_dir == NULL)
286 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
288 return usergraphics_dir;
291 static char *getUserSoundsDir()
293 static char *usersounds_dir = NULL;
295 if (usersounds_dir == NULL)
296 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
298 return usersounds_dir;
301 static char *getUserMusicDir()
303 static char *usermusic_dir = NULL;
305 if (usermusic_dir == NULL)
306 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
308 return usermusic_dir;
311 static char *getSetupArtworkDir(TreeInfo *ti)
313 static char *artwork_dir = NULL;
315 checked_free(artwork_dir);
317 artwork_dir = getPath2(ti->basepath, ti->fullpath);
322 char *setLevelArtworkDir(TreeInfo *ti)
324 char **artwork_path_ptr, **artwork_set_ptr;
325 TreeInfo *level_artwork;
327 if (ti == NULL || leveldir_current == NULL)
330 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
331 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
333 checked_free(*artwork_path_ptr);
335 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
336 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
339 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
340 normally result in using the artwork configured in the setup menu. But
341 if an artwork subdirectory exists (which might contain custom artwork
342 or an artwork configuration file), this level artwork must be treated
343 as relative to the default "classic" artwork, not to the artwork that
344 is currently configured in the setup menu. */
346 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
348 checked_free(*artwork_set_ptr);
352 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
353 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
357 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
358 *artwork_set_ptr = NULL;
364 return *artwork_set_ptr;
367 inline static char *getLevelArtworkSet(int type)
369 if (leveldir_current == NULL)
372 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
375 inline static char *getLevelArtworkDir(int type)
377 if (leveldir_current == NULL)
378 return UNDEFINED_FILENAME;
380 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
383 char *getTapeFilename(int nr)
385 static char *filename = NULL;
386 char basename[MAX_FILENAME_LEN];
388 checked_free(filename);
390 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
391 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
396 char *getSolutionTapeFilename(int nr)
398 static char *filename = NULL;
399 char basename[MAX_FILENAME_LEN];
401 checked_free(filename);
403 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
404 filename = getPath2(getSolutionTapeDir(), basename);
409 char *getScoreFilename(int nr)
411 static char *filename = NULL;
412 char basename[MAX_FILENAME_LEN];
414 checked_free(filename);
416 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
417 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
422 char *getSetupFilename()
424 static char *filename = NULL;
426 checked_free(filename);
428 filename = getPath2(getSetupDir(), SETUP_FILENAME);
433 char *getEditorSetupFilename()
435 static char *filename = NULL;
437 checked_free(filename);
438 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
440 if (fileExists(filename))
443 checked_free(filename);
444 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
449 char *getHelpAnimFilename()
451 static char *filename = NULL;
453 checked_free(filename);
455 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
460 char *getHelpTextFilename()
462 static char *filename = NULL;
464 checked_free(filename);
466 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
471 char *getLevelSetInfoFilename()
473 static char *filename = NULL;
488 for (i = 0; basenames[i] != NULL; i++)
490 checked_free(filename);
491 filename = getPath2(getCurrentLevelDir(), basenames[i]);
493 if (fileExists(filename))
500 char *getLevelSetMessageFilename()
502 static char *filename = NULL;
517 for (i = 0; basenames[i] != NULL; i++)
519 checked_free(filename);
520 filename = getPath2(getCurrentLevelDir(), basenames[i]);
522 if (fileExists(filename))
529 static char *getCorrectedArtworkBasename(char *basename)
531 char *basename_corrected = basename;
533 #if defined(PLATFORM_MSDOS)
534 if (program.filename_prefix != NULL)
536 int prefix_len = strlen(program.filename_prefix);
538 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
539 basename_corrected = &basename[prefix_len];
541 /* if corrected filename is still longer than standard MS-DOS filename
542 size (8 characters + 1 dot + 3 characters file extension), shorten
543 filename by writing file extension after 8th basename character */
544 if (strlen(basename_corrected) > 8 + 1 + 3)
546 static char *msdos_filename = NULL;
548 checked_free(msdos_filename);
550 msdos_filename = getStringCopy(basename_corrected);
551 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
553 basename_corrected = msdos_filename;
558 return basename_corrected;
561 char *getCustomImageFilename(char *basename)
563 static char *filename = NULL;
564 boolean skip_setup_artwork = FALSE;
566 checked_free(filename);
568 basename = getCorrectedArtworkBasename(basename);
570 if (!setup.override_level_graphics)
572 /* 1st try: look for special artwork in current level series directory */
573 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
574 if (fileExists(filename))
579 /* check if there is special artwork configured in level series config */
580 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
582 /* 2nd try: look for special artwork configured in level series config */
583 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
584 if (fileExists(filename))
589 /* take missing artwork configured in level set config from default */
590 skip_setup_artwork = TRUE;
594 if (!skip_setup_artwork)
596 /* 3rd try: look for special artwork in configured artwork directory */
597 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
598 if (fileExists(filename))
604 /* 4th try: look for default artwork in new default artwork directory */
605 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
606 if (fileExists(filename))
611 /* 5th try: look for default artwork in old default artwork directory */
612 filename = getPath2(options.graphics_directory, basename);
613 if (fileExists(filename))
616 return NULL; /* cannot find specified artwork file anywhere */
619 char *getCustomSoundFilename(char *basename)
621 static char *filename = NULL;
622 boolean skip_setup_artwork = FALSE;
624 checked_free(filename);
626 basename = getCorrectedArtworkBasename(basename);
628 if (!setup.override_level_sounds)
630 /* 1st try: look for special artwork in current level series directory */
631 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
632 if (fileExists(filename))
637 /* check if there is special artwork configured in level series config */
638 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
640 /* 2nd try: look for special artwork configured in level series config */
641 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
642 if (fileExists(filename))
647 /* take missing artwork configured in level set config from default */
648 skip_setup_artwork = TRUE;
652 if (!skip_setup_artwork)
654 /* 3rd try: look for special artwork in configured artwork directory */
655 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
656 if (fileExists(filename))
662 /* 4th try: look for default artwork in new default artwork directory */
663 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
664 if (fileExists(filename))
669 /* 5th try: look for default artwork in old default artwork directory */
670 filename = getPath2(options.sounds_directory, basename);
671 if (fileExists(filename))
674 return NULL; /* cannot find specified artwork file anywhere */
677 char *getCustomMusicFilename(char *basename)
679 static char *filename = NULL;
680 boolean skip_setup_artwork = FALSE;
682 checked_free(filename);
684 basename = getCorrectedArtworkBasename(basename);
686 if (!setup.override_level_music)
688 /* 1st try: look for special artwork in current level series directory */
689 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
690 if (fileExists(filename))
695 /* check if there is special artwork configured in level series config */
696 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
698 /* 2nd try: look for special artwork configured in level series config */
699 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
700 if (fileExists(filename))
705 /* take missing artwork configured in level set config from default */
706 skip_setup_artwork = TRUE;
710 if (!skip_setup_artwork)
712 /* 3rd try: look for special artwork in configured artwork directory */
713 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
714 if (fileExists(filename))
720 /* 4th try: look for default artwork in new default artwork directory */
721 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
722 if (fileExists(filename))
727 /* 5th try: look for default artwork in old default artwork directory */
728 filename = getPath2(options.music_directory, basename);
729 if (fileExists(filename))
732 return NULL; /* cannot find specified artwork file anywhere */
735 char *getCustomArtworkFilename(char *basename, int type)
737 if (type == ARTWORK_TYPE_GRAPHICS)
738 return getCustomImageFilename(basename);
739 else if (type == ARTWORK_TYPE_SOUNDS)
740 return getCustomSoundFilename(basename);
741 else if (type == ARTWORK_TYPE_MUSIC)
742 return getCustomMusicFilename(basename);
744 return UNDEFINED_FILENAME;
747 char *getCustomArtworkConfigFilename(int type)
749 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
752 char *getCustomArtworkLevelConfigFilename(int type)
754 static char *filename = NULL;
756 checked_free(filename);
758 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
763 char *getCustomMusicDirectory(void)
765 static char *directory = NULL;
766 boolean skip_setup_artwork = FALSE;
768 checked_free(directory);
770 if (!setup.override_level_music)
772 /* 1st try: look for special artwork in current level series directory */
773 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
774 if (fileExists(directory))
779 /* check if there is special artwork configured in level series config */
780 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
782 /* 2nd try: look for special artwork configured in level series config */
783 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
784 if (fileExists(directory))
789 /* take missing artwork configured in level set config from default */
790 skip_setup_artwork = TRUE;
794 if (!skip_setup_artwork)
796 /* 3rd try: look for special artwork in configured artwork directory */
797 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
798 if (fileExists(directory))
804 /* 4th try: look for default artwork in new default artwork directory */
805 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
806 if (fileExists(directory))
811 /* 5th try: look for default artwork in old default artwork directory */
812 directory = getStringCopy(options.music_directory);
813 if (fileExists(directory))
816 return NULL; /* cannot find specified artwork file anywhere */
819 void InitTapeDirectory(char *level_subdir)
821 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
822 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
823 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
826 void InitScoreDirectory(char *level_subdir)
828 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
829 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
830 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
833 static void SaveUserLevelInfo();
835 void InitUserLevelDirectory(char *level_subdir)
837 if (!fileExists(getUserLevelDir(level_subdir)))
839 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
840 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
841 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
847 void InitLevelSetupDirectory(char *level_subdir)
849 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
850 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
851 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
854 void InitCacheDirectory()
856 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
857 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
861 /* ------------------------------------------------------------------------- */
862 /* some functions to handle lists of level and artwork directories */
863 /* ------------------------------------------------------------------------- */
865 TreeInfo *newTreeInfo()
867 return checked_calloc(sizeof(TreeInfo));
870 TreeInfo *newTreeInfo_setDefaults(int type)
872 TreeInfo *ti = newTreeInfo();
874 setTreeInfoToDefaults(ti, type);
879 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
881 node_new->next = *node_first;
882 *node_first = node_new;
885 int numTreeInfo(TreeInfo *node)
898 boolean validLevelSeries(TreeInfo *node)
900 return (node != NULL && !node->node_group && !node->parent_link);
903 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
908 if (node->node_group) /* enter level group (step down into tree) */
909 return getFirstValidTreeInfoEntry(node->node_group);
910 else if (node->parent_link) /* skip start entry of level group */
912 if (node->next) /* get first real level series entry */
913 return getFirstValidTreeInfoEntry(node->next);
914 else /* leave empty level group and go on */
915 return getFirstValidTreeInfoEntry(node->node_parent->next);
917 else /* this seems to be a regular level series */
921 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
926 if (node->node_parent == NULL) /* top level group */
927 return *node->node_top;
928 else /* sub level group */
929 return node->node_parent->node_group;
932 int numTreeInfoInGroup(TreeInfo *node)
934 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
937 int posTreeInfo(TreeInfo *node)
939 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
944 if (node_cmp == node)
948 node_cmp = node_cmp->next;
954 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
956 TreeInfo *node_default = node;
971 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
973 if (identifier == NULL)
978 if (node->node_group)
980 TreeInfo *node_group;
982 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
987 else if (!node->parent_link)
989 if (strEqual(identifier, node->identifier))
999 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1000 TreeInfo *node, boolean skip_sets_without_levels)
1007 if (!node->parent_link && !node->level_group &&
1008 skip_sets_without_levels && node->levels == 0)
1009 return cloneTreeNode(node_top, node_parent, node->next,
1010 skip_sets_without_levels);
1012 node_new = newTreeInfo();
1014 *node_new = *node; /* copy complete node */
1016 node_new->node_top = node_top; /* correct top node link */
1017 node_new->node_parent = node_parent; /* correct parent node link */
1019 if (node->level_group)
1020 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1021 skip_sets_without_levels);
1023 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1024 skip_sets_without_levels);
1029 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1031 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1033 *ti_new = ti_cloned;
1036 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1038 boolean settings_changed = FALSE;
1042 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1043 !strEqual(node->graphics_set, node->graphics_set_ecs))
1045 setString(&node->graphics_set, node->graphics_set_ecs);
1046 settings_changed = TRUE;
1048 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1049 !strEqual(node->graphics_set, node->graphics_set_aga))
1051 setString(&node->graphics_set, node->graphics_set_aga);
1052 settings_changed = TRUE;
1055 if (node->node_group != NULL)
1056 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1061 return settings_changed;
1064 void dumpTreeInfo(TreeInfo *node, int depth)
1068 printf("Dumping TreeInfo:\n");
1072 for (i = 0; i < (depth + 1) * 3; i++)
1075 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1076 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1078 if (node->node_group != NULL)
1079 dumpTreeInfo(node->node_group, depth + 1);
1085 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1086 int (*compare_function)(const void *,
1089 int num_nodes = numTreeInfo(*node_first);
1090 TreeInfo **sort_array;
1091 TreeInfo *node = *node_first;
1097 /* allocate array for sorting structure pointers */
1098 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1100 /* writing structure pointers to sorting array */
1101 while (i < num_nodes && node) /* double boundary check... */
1103 sort_array[i] = node;
1109 /* sorting the structure pointers in the sorting array */
1110 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1113 /* update the linkage of list elements with the sorted node array */
1114 for (i = 0; i < num_nodes - 1; i++)
1115 sort_array[i]->next = sort_array[i + 1];
1116 sort_array[num_nodes - 1]->next = NULL;
1118 /* update the linkage of the main list anchor pointer */
1119 *node_first = sort_array[0];
1123 /* now recursively sort the level group structures */
1127 if (node->node_group != NULL)
1128 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1134 void sortTreeInfo(TreeInfo **node_first)
1136 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1140 /* ========================================================================= */
1141 /* some stuff from "files.c" */
1142 /* ========================================================================= */
1144 #if defined(PLATFORM_WIN32)
1146 #define S_IRGRP S_IRUSR
1149 #define S_IROTH S_IRUSR
1152 #define S_IWGRP S_IWUSR
1155 #define S_IWOTH S_IWUSR
1158 #define S_IXGRP S_IXUSR
1161 #define S_IXOTH S_IXUSR
1164 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1169 #endif /* PLATFORM_WIN32 */
1171 /* file permissions for newly written files */
1172 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1173 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1174 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1176 #define MODE_W_PRIVATE (S_IWUSR)
1177 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1178 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1180 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1181 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1183 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1184 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1188 static char *dir = NULL;
1190 #if defined(PLATFORM_WIN32)
1193 dir = checked_malloc(MAX_PATH + 1);
1195 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1198 #elif defined(PLATFORM_UNIX)
1201 if ((dir = getenv("HOME")) == NULL)
1205 if ((pwd = getpwuid(getuid())) != NULL)
1206 dir = getStringCopy(pwd->pw_dir);
1218 char *getCommonDataDir(void)
1220 static char *common_data_dir = NULL;
1222 #if defined(PLATFORM_WIN32)
1223 if (common_data_dir == NULL)
1225 char *dir = checked_malloc(MAX_PATH + 1);
1227 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1228 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1229 common_data_dir = getPath2(dir, program.userdata_subdir);
1231 common_data_dir = options.rw_base_directory;
1234 if (common_data_dir == NULL)
1235 common_data_dir = options.rw_base_directory;
1238 return common_data_dir;
1241 char *getPersonalDataDir(void)
1243 static char *personal_data_dir = NULL;
1245 #if defined(PLATFORM_MACOSX)
1246 if (personal_data_dir == NULL)
1247 personal_data_dir = getPath2(getHomeDir(), "Documents");
1249 if (personal_data_dir == NULL)
1250 personal_data_dir = getHomeDir();
1253 return personal_data_dir;
1256 char *getUserGameDataDir(void)
1258 static char *user_game_data_dir = NULL;
1260 if (user_game_data_dir == NULL)
1261 user_game_data_dir = getPath2(getPersonalDataDir(),
1262 program.userdata_subdir);
1264 return user_game_data_dir;
1267 void updateUserGameDataDir()
1269 #if defined(PLATFORM_MACOSX)
1270 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1271 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1273 /* convert old Unix style game data directory to Mac OS X style, if needed */
1274 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1276 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1278 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1279 userdata_dir_old, userdata_dir_new);
1281 /* continue using Unix style data directory -- this should not happen */
1282 program.userdata_path = getPath2(getPersonalDataDir(),
1283 program.userdata_subdir_unix);
1287 free(userdata_dir_old);
1293 return getUserGameDataDir();
1296 static mode_t posix_umask(mode_t mask)
1298 #if defined(PLATFORM_UNIX)
1305 static int posix_mkdir(const char *pathname, mode_t mode)
1307 #if defined(PLATFORM_WIN32)
1308 return mkdir(pathname);
1310 return mkdir(pathname, mode);
1314 void createDirectory(char *dir, char *text, int permission_class)
1316 /* leave "other" permissions in umask untouched, but ensure group parts
1317 of USERDATA_DIR_MODE are not masked */
1318 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1319 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1320 mode_t normal_umask = posix_umask(0);
1321 mode_t group_umask = ~(dir_mode & S_IRWXG);
1322 posix_umask(normal_umask & group_umask);
1324 if (!fileExists(dir))
1325 if (posix_mkdir(dir, dir_mode) != 0)
1326 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1328 posix_umask(normal_umask); /* reset normal umask */
1331 void InitUserDataDirectory()
1333 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1336 void SetFilePermissions(char *filename, int permission_class)
1338 chmod(filename, (permission_class == PERMS_PRIVATE ?
1339 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1342 char *getCookie(char *file_type)
1344 static char cookie[MAX_COOKIE_LEN + 1];
1346 if (strlen(program.cookie_prefix) + 1 +
1347 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1348 return "[COOKIE ERROR]"; /* should never happen */
1350 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1351 program.cookie_prefix, file_type,
1352 program.version_major, program.version_minor);
1357 int getFileVersionFromCookieString(const char *cookie)
1359 const char *ptr_cookie1, *ptr_cookie2;
1360 const char *pattern1 = "_FILE_VERSION_";
1361 const char *pattern2 = "?.?";
1362 const int len_cookie = strlen(cookie);
1363 const int len_pattern1 = strlen(pattern1);
1364 const int len_pattern2 = strlen(pattern2);
1365 const int len_pattern = len_pattern1 + len_pattern2;
1366 int version_major, version_minor;
1368 if (len_cookie <= len_pattern)
1371 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1372 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1374 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1377 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1378 ptr_cookie2[1] != '.' ||
1379 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1382 version_major = ptr_cookie2[0] - '0';
1383 version_minor = ptr_cookie2[2] - '0';
1385 return VERSION_IDENT(version_major, version_minor, 0, 0);
1388 boolean checkCookieString(const char *cookie, const char *template)
1390 const char *pattern = "_FILE_VERSION_?.?";
1391 const int len_cookie = strlen(cookie);
1392 const int len_template = strlen(template);
1393 const int len_pattern = strlen(pattern);
1395 if (len_cookie != len_template)
1398 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1404 /* ------------------------------------------------------------------------- */
1405 /* setup file list and hash handling functions */
1406 /* ------------------------------------------------------------------------- */
1408 char *getFormattedSetupEntry(char *token, char *value)
1411 static char entry[MAX_LINE_LEN];
1413 /* if value is an empty string, just return token without value */
1417 /* start with the token and some spaces to format output line */
1418 sprintf(entry, "%s:", token);
1419 for (i = strlen(entry); i < token_value_position; i++)
1422 /* continue with the token's value */
1423 strcat(entry, value);
1428 SetupFileList *newSetupFileList(char *token, char *value)
1430 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1432 new->token = getStringCopy(token);
1433 new->value = getStringCopy(value);
1440 void freeSetupFileList(SetupFileList *list)
1445 checked_free(list->token);
1446 checked_free(list->value);
1449 freeSetupFileList(list->next);
1454 char *getListEntry(SetupFileList *list, char *token)
1459 if (strEqual(list->token, token))
1462 return getListEntry(list->next, token);
1465 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1470 if (strEqual(list->token, token))
1472 checked_free(list->value);
1474 list->value = getStringCopy(value);
1478 else if (list->next == NULL)
1479 return (list->next = newSetupFileList(token, value));
1481 return setListEntry(list->next, token, value);
1484 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1489 if (list->next == NULL)
1490 return (list->next = newSetupFileList(token, value));
1492 return addListEntry(list->next, token, value);
1496 static void printSetupFileList(SetupFileList *list)
1501 printf("token: '%s'\n", list->token);
1502 printf("value: '%s'\n", list->value);
1504 printSetupFileList(list->next);
1509 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1510 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1511 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1512 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1514 #define insert_hash_entry hashtable_insert
1515 #define search_hash_entry hashtable_search
1516 #define change_hash_entry hashtable_change
1517 #define remove_hash_entry hashtable_remove
1520 static unsigned int get_hash_from_key(void *key)
1525 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1526 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1527 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1528 it works better than many other constants, prime or not) has never been
1529 adequately explained.
1531 If you just want to have a good hash function, and cannot wait, djb2
1532 is one of the best string hash functions i know. It has excellent
1533 distribution and speed on many different sets of keys and table sizes.
1534 You are not likely to do better with one of the "well known" functions
1535 such as PJW, K&R, etc.
1537 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1540 char *str = (char *)key;
1541 unsigned int hash = 5381;
1544 while ((c = *str++))
1545 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1550 static int keys_are_equal(void *key1, void *key2)
1552 return (strEqual((char *)key1, (char *)key2));
1555 SetupFileHash *newSetupFileHash()
1557 SetupFileHash *new_hash =
1558 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1560 if (new_hash == NULL)
1561 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1566 void freeSetupFileHash(SetupFileHash *hash)
1571 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1574 char *getHashEntry(SetupFileHash *hash, char *token)
1579 return search_hash_entry(hash, token);
1582 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1589 value_copy = getStringCopy(value);
1591 /* change value; if it does not exist, insert it as new */
1592 if (!change_hash_entry(hash, token, value_copy))
1593 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1594 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1597 char *removeHashEntry(SetupFileHash *hash, char *token)
1602 return remove_hash_entry(hash, token);
1606 static void printSetupFileHash(SetupFileHash *hash)
1608 BEGIN_HASH_ITERATION(hash, itr)
1610 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1611 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1613 END_HASH_ITERATION(hash, itr)
1617 static void *loadSetupFileData(char *filename, boolean use_hash)
1619 char line[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1620 char *token, *value, *line_ptr;
1621 void *setup_file_data, *insert_ptr = NULL;
1622 boolean read_continued_line = FALSE;
1625 if (!(file = fopen(filename, MODE_READ)))
1627 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1633 setup_file_data = newSetupFileHash();
1635 insert_ptr = setup_file_data = newSetupFileList("", "");
1639 /* read next line of input file */
1640 if (!fgets(line, MAX_LINE_LEN, file))
1643 /* cut trailing newline or carriage return */
1644 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1645 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1648 if (read_continued_line)
1650 /* cut leading whitespaces from input line */
1651 for (line_ptr = line; *line_ptr; line_ptr++)
1652 if (*line_ptr != ' ' && *line_ptr != '\t')
1655 /* append new line to existing line, if there is enough space */
1656 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1657 strcat(previous_line, line_ptr);
1659 strcpy(line, previous_line); /* copy storage buffer to line */
1661 read_continued_line = FALSE;
1664 /* if the last character is '\', continue at next line */
1665 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1667 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1668 strcpy(previous_line, line); /* copy line to storage buffer */
1670 read_continued_line = TRUE;
1675 /* cut trailing comment from input line */
1676 for (line_ptr = line; *line_ptr; line_ptr++)
1678 if (*line_ptr == '#')
1685 /* cut trailing whitespaces from input line */
1686 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1687 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1690 /* ignore empty lines */
1694 /* cut leading whitespaces from token */
1695 for (token = line; *token; token++)
1696 if (*token != ' ' && *token != '\t')
1699 /* start with empty value as reliable default */
1702 /* find end of token to determine start of value */
1703 for (line_ptr = token; *line_ptr; line_ptr++)
1706 if (*line_ptr == ':' || *line_ptr == '=')
1708 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1711 *line_ptr = '\0'; /* terminate token string */
1712 value = line_ptr + 1; /* set beginning of value */
1718 /* cut trailing whitespaces from token */
1719 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1720 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1723 /* cut leading whitespaces from value */
1724 for (; *value; value++)
1725 if (*value != ' ' && *value != '\t')
1730 value = "true"; /* treat tokens without value as "true" */
1736 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1738 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1746 if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1747 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1751 SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1752 SetupFileList *first_valid_list_entry = setup_file_list->next;
1754 /* free empty list header */
1755 setup_file_list->next = NULL;
1756 freeSetupFileList(setup_file_list);
1757 setup_file_data = first_valid_list_entry;
1759 if (first_valid_list_entry == NULL)
1760 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1763 return setup_file_data;
1766 void saveSetupFileHash(SetupFileHash *hash, char *filename)
1770 if (!(file = fopen(filename, MODE_WRITE)))
1772 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
1777 BEGIN_HASH_ITERATION(hash, itr)
1779 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
1780 HASH_ITERATION_VALUE(itr)));
1782 END_HASH_ITERATION(hash, itr)
1787 SetupFileList *loadSetupFileList(char *filename)
1789 return (SetupFileList *)loadSetupFileData(filename, FALSE);
1792 SetupFileHash *loadSetupFileHash(char *filename)
1794 return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1797 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1798 char *filename, char *identifier)
1800 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1803 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
1804 else if (!checkCookieString(value, identifier))
1805 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
1809 /* ========================================================================= */
1810 /* setup file stuff */
1811 /* ========================================================================= */
1813 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1814 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1815 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1817 /* level directory info */
1818 #define LEVELINFO_TOKEN_IDENTIFIER 0
1819 #define LEVELINFO_TOKEN_NAME 1
1820 #define LEVELINFO_TOKEN_NAME_SORTING 2
1821 #define LEVELINFO_TOKEN_AUTHOR 3
1822 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
1823 #define LEVELINFO_TOKEN_IMPORTED_BY 5
1824 #define LEVELINFO_TOKEN_LEVELS 6
1825 #define LEVELINFO_TOKEN_FIRST_LEVEL 7
1826 #define LEVELINFO_TOKEN_SORT_PRIORITY 8
1827 #define LEVELINFO_TOKEN_LATEST_ENGINE 9
1828 #define LEVELINFO_TOKEN_LEVEL_GROUP 10
1829 #define LEVELINFO_TOKEN_READONLY 11
1830 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 12
1831 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 13
1832 #define LEVELINFO_TOKEN_GRAPHICS_SET 14
1833 #define LEVELINFO_TOKEN_SOUNDS_SET 15
1834 #define LEVELINFO_TOKEN_MUSIC_SET 16
1835 #define LEVELINFO_TOKEN_FILENAME 17
1836 #define LEVELINFO_TOKEN_FILETYPE 18
1837 #define LEVELINFO_TOKEN_HANDICAP 19
1838 #define LEVELINFO_TOKEN_SKIP_LEVELS 20
1840 #define NUM_LEVELINFO_TOKENS 21
1842 static LevelDirTree ldi;
1844 static struct TokenInfo levelinfo_tokens[] =
1846 /* level directory info */
1847 { TYPE_STRING, &ldi.identifier, "identifier" },
1848 { TYPE_STRING, &ldi.name, "name" },
1849 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1850 { TYPE_STRING, &ldi.author, "author" },
1851 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1852 { TYPE_STRING, &ldi.imported_by, "imported_by" },
1853 { TYPE_INTEGER, &ldi.levels, "levels" },
1854 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1855 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1856 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
1857 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1858 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1859 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
1860 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
1861 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1862 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1863 { TYPE_STRING, &ldi.music_set, "music_set" },
1864 { TYPE_STRING, &ldi.level_filename, "filename" },
1865 { TYPE_STRING, &ldi.level_filetype, "filetype" },
1866 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
1867 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
1870 static struct TokenInfo artworkinfo_tokens[] =
1872 /* artwork directory info */
1873 { TYPE_STRING, &ldi.identifier, "identifier" },
1874 { TYPE_STRING, &ldi.subdir, "subdir" },
1875 { TYPE_STRING, &ldi.name, "name" },
1876 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1877 { TYPE_STRING, &ldi.author, "author" },
1878 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1879 { TYPE_STRING, &ldi.basepath, "basepath" },
1880 { TYPE_STRING, &ldi.fullpath, "fullpath" },
1881 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
1882 { TYPE_INTEGER, &ldi.color, "color" },
1883 { TYPE_STRING, &ldi.class_desc, "class_desc" },
1888 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
1892 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1893 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1894 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1895 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1898 ti->node_parent = NULL;
1899 ti->node_group = NULL;
1906 ti->fullpath = NULL;
1907 ti->basepath = NULL;
1908 ti->identifier = NULL;
1909 ti->name = getStringCopy(ANONYMOUS_NAME);
1910 ti->name_sorting = NULL;
1911 ti->author = getStringCopy(ANONYMOUS_NAME);
1913 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1914 ti->latest_engine = FALSE; /* default: get from level */
1915 ti->parent_link = FALSE;
1916 ti->in_user_dir = FALSE;
1917 ti->user_defined = FALSE;
1919 ti->class_desc = NULL;
1921 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
1923 if (ti->type == TREE_TYPE_LEVEL_DIR)
1925 ti->imported_from = NULL;
1926 ti->imported_by = NULL;
1928 ti->graphics_set_ecs = NULL;
1929 ti->graphics_set_aga = NULL;
1930 ti->graphics_set = NULL;
1931 ti->sounds_set = NULL;
1932 ti->music_set = NULL;
1933 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1934 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1935 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
1937 ti->level_filename = NULL;
1938 ti->level_filetype = NULL;
1941 ti->first_level = 0;
1943 ti->level_group = FALSE;
1944 ti->handicap_level = 0;
1945 ti->readonly = TRUE;
1946 ti->handicap = TRUE;
1947 ti->skip_levels = FALSE;
1951 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
1955 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1957 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
1962 /* copy all values from the parent structure */
1964 ti->type = parent->type;
1966 ti->node_top = parent->node_top;
1967 ti->node_parent = parent;
1968 ti->node_group = NULL;
1975 ti->fullpath = NULL;
1976 ti->basepath = NULL;
1977 ti->identifier = NULL;
1978 ti->name = getStringCopy(ANONYMOUS_NAME);
1979 ti->name_sorting = NULL;
1980 ti->author = getStringCopy(parent->author);
1982 ti->sort_priority = parent->sort_priority;
1983 ti->latest_engine = parent->latest_engine;
1984 ti->parent_link = FALSE;
1985 ti->in_user_dir = parent->in_user_dir;
1986 ti->user_defined = parent->user_defined;
1987 ti->color = parent->color;
1988 ti->class_desc = getStringCopy(parent->class_desc);
1990 ti->infotext = getStringCopy(parent->infotext);
1992 if (ti->type == TREE_TYPE_LEVEL_DIR)
1994 ti->imported_from = getStringCopy(parent->imported_from);
1995 ti->imported_by = getStringCopy(parent->imported_by);
1997 ti->graphics_set_ecs = NULL;
1998 ti->graphics_set_aga = NULL;
1999 ti->graphics_set = NULL;
2000 ti->sounds_set = NULL;
2001 ti->music_set = NULL;
2002 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2003 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2004 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2006 ti->level_filename = NULL;
2007 ti->level_filetype = NULL;
2010 ti->first_level = 0;
2012 ti->level_group = FALSE;
2013 ti->handicap_level = 0;
2014 ti->readonly = TRUE;
2015 ti->handicap = TRUE;
2016 ti->skip_levels = FALSE;
2020 static void freeTreeInfo(TreeInfo *ti)
2025 checked_free(ti->subdir);
2026 checked_free(ti->fullpath);
2027 checked_free(ti->basepath);
2028 checked_free(ti->identifier);
2030 checked_free(ti->name);
2031 checked_free(ti->name_sorting);
2032 checked_free(ti->author);
2034 checked_free(ti->class_desc);
2036 checked_free(ti->infotext);
2038 if (ti->type == TREE_TYPE_LEVEL_DIR)
2040 checked_free(ti->imported_from);
2041 checked_free(ti->imported_by);
2043 checked_free(ti->graphics_set_ecs);
2044 checked_free(ti->graphics_set_aga);
2045 checked_free(ti->graphics_set);
2046 checked_free(ti->sounds_set);
2047 checked_free(ti->music_set);
2049 checked_free(ti->graphics_path);
2050 checked_free(ti->sounds_path);
2051 checked_free(ti->music_path);
2053 checked_free(ti->level_filename);
2054 checked_free(ti->level_filetype);
2060 void setSetupInfo(struct TokenInfo *token_info,
2061 int token_nr, char *token_value)
2063 int token_type = token_info[token_nr].type;
2064 void *setup_value = token_info[token_nr].value;
2066 if (token_value == NULL)
2069 /* set setup field to corresponding token value */
2074 *(boolean *)setup_value = get_boolean_from_string(token_value);
2078 *(Key *)setup_value = getKeyFromKeyName(token_value);
2082 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2086 *(int *)setup_value = get_integer_from_string(token_value);
2090 checked_free(*(char **)setup_value);
2091 *(char **)setup_value = getStringCopy(token_value);
2099 static int compareTreeInfoEntries(const void *object1, const void *object2)
2101 const TreeInfo *entry1 = *((TreeInfo **)object1);
2102 const TreeInfo *entry2 = *((TreeInfo **)object2);
2103 int class_sorting1, class_sorting2;
2106 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2108 class_sorting1 = LEVELSORTING(entry1);
2109 class_sorting2 = LEVELSORTING(entry2);
2113 class_sorting1 = ARTWORKSORTING(entry1);
2114 class_sorting2 = ARTWORKSORTING(entry2);
2117 if (entry1->parent_link || entry2->parent_link)
2118 compare_result = (entry1->parent_link ? -1 : +1);
2119 else if (entry1->sort_priority == entry2->sort_priority)
2121 char *name1 = getStringToLower(entry1->name_sorting);
2122 char *name2 = getStringToLower(entry2->name_sorting);
2124 compare_result = strcmp(name1, name2);
2129 else if (class_sorting1 == class_sorting2)
2130 compare_result = entry1->sort_priority - entry2->sort_priority;
2132 compare_result = class_sorting1 - class_sorting2;
2134 return compare_result;
2137 static void createParentTreeInfoNode(TreeInfo *node_parent)
2141 if (node_parent == NULL)
2144 ti_new = newTreeInfo();
2145 setTreeInfoToDefaults(ti_new, node_parent->type);
2147 ti_new->node_parent = node_parent;
2148 ti_new->parent_link = TRUE;
2150 setString(&ti_new->identifier, node_parent->identifier);
2151 setString(&ti_new->name, ".. (parent directory)");
2152 setString(&ti_new->name_sorting, ti_new->name);
2154 setString(&ti_new->subdir, "..");
2155 setString(&ti_new->fullpath, node_parent->fullpath);
2157 ti_new->sort_priority = node_parent->sort_priority;
2158 ti_new->latest_engine = node_parent->latest_engine;
2160 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2162 pushTreeInfo(&node_parent->node_group, ti_new);
2166 /* -------------------------------------------------------------------------- */
2167 /* functions for handling level and custom artwork info cache */
2168 /* -------------------------------------------------------------------------- */
2170 static void LoadArtworkInfoCache()
2172 InitCacheDirectory();
2174 if (artworkinfo_cache_old == NULL)
2176 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2178 /* try to load artwork info hash from already existing cache file */
2179 artworkinfo_cache_old = loadSetupFileHash(filename);
2181 /* if no artwork info cache file was found, start with empty hash */
2182 if (artworkinfo_cache_old == NULL)
2183 artworkinfo_cache_old = newSetupFileHash();
2188 if (artworkinfo_cache_new == NULL)
2189 artworkinfo_cache_new = newSetupFileHash();
2192 static void SaveArtworkInfoCache()
2194 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2196 InitCacheDirectory();
2198 saveSetupFileHash(artworkinfo_cache_new, filename);
2203 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2205 static char *prefix = NULL;
2207 checked_free(prefix);
2209 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2214 /* (identical to above function, but separate string buffer needed -- nasty) */
2215 static char *getCacheToken(char *prefix, char *suffix)
2217 static char *token = NULL;
2219 checked_free(token);
2221 token = getStringCat2WithSeparator(prefix, suffix, ".");
2226 static char *getFileTimestamp(char *filename)
2228 struct stat file_status;
2230 if (stat(filename, &file_status) != 0) /* cannot stat file */
2231 return getStringCopy(i_to_a(0));
2233 return getStringCopy(i_to_a(file_status.st_mtime));
2236 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2238 struct stat file_status;
2240 if (timestamp_string == NULL)
2243 if (stat(filename, &file_status) != 0) /* cannot stat file */
2246 return (file_status.st_mtime != atoi(timestamp_string));
2249 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2251 char *identifier = level_node->subdir;
2252 char *type_string = ARTWORK_DIRECTORY(type);
2253 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2254 char *token_main = getCacheToken(token_prefix, "CACHED");
2255 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2256 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2257 TreeInfo *artwork_info = NULL;
2259 if (!use_artworkinfo_cache)
2266 artwork_info = newTreeInfo();
2267 setTreeInfoToDefaults(artwork_info, type);
2269 /* set all structure fields according to the token/value pairs */
2270 ldi = *artwork_info;
2271 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2273 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2274 char *value = getHashEntry(artworkinfo_cache_old, token);
2276 setSetupInfo(artworkinfo_tokens, i, value);
2278 /* check if cache entry for this item is invalid or incomplete */
2282 printf("::: - WARNING: cache entry '%s' invalid\n", token);
2288 *artwork_info = ldi;
2293 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2294 LEVELINFO_FILENAME);
2295 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2296 ARTWORKINFO_FILENAME(type));
2298 /* check if corresponding "levelinfo.conf" file has changed */
2299 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2300 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2302 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2305 /* check if corresponding "<artworkinfo>.conf" file has changed */
2306 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2307 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2309 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2314 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2317 checked_free(filename_levelinfo);
2318 checked_free(filename_artworkinfo);
2321 if (!cached && artwork_info != NULL)
2323 freeTreeInfo(artwork_info);
2328 return artwork_info;
2331 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2332 LevelDirTree *level_node, int type)
2334 char *identifier = level_node->subdir;
2335 char *type_string = ARTWORK_DIRECTORY(type);
2336 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2337 char *token_main = getCacheToken(token_prefix, "CACHED");
2338 boolean set_cache_timestamps = TRUE;
2341 setHashEntry(artworkinfo_cache_new, token_main, "true");
2343 if (set_cache_timestamps)
2345 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2346 LEVELINFO_FILENAME);
2347 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2348 ARTWORKINFO_FILENAME(type));
2349 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2350 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2352 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2353 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2355 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2356 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2358 checked_free(filename_levelinfo);
2359 checked_free(filename_artworkinfo);
2360 checked_free(timestamp_levelinfo);
2361 checked_free(timestamp_artworkinfo);
2364 ldi = *artwork_info;
2365 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2367 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2368 char *value = getSetupValue(artworkinfo_tokens[i].type,
2369 artworkinfo_tokens[i].value);
2371 setHashEntry(artworkinfo_cache_new, token, value);
2376 /* -------------------------------------------------------------------------- */
2377 /* functions for loading level info and custom artwork info */
2378 /* -------------------------------------------------------------------------- */
2380 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2381 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2383 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2384 TreeInfo *node_parent,
2385 char *level_directory,
2386 char *directory_name)
2388 char *directory_path = getPath2(level_directory, directory_name);
2389 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2390 SetupFileHash *setup_file_hash;
2391 LevelDirTree *leveldir_new = NULL;
2394 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2395 if (!options.debug && !fileExists(filename))
2397 free(directory_path);
2403 setup_file_hash = loadSetupFileHash(filename);
2405 if (setup_file_hash == NULL)
2407 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2409 free(directory_path);
2415 leveldir_new = newTreeInfo();
2418 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2420 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2422 leveldir_new->subdir = getStringCopy(directory_name);
2424 checkSetupFileHashIdentifier(setup_file_hash, filename,
2425 getCookie("LEVELINFO"));
2427 /* set all structure fields according to the token/value pairs */
2428 ldi = *leveldir_new;
2429 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2430 setSetupInfo(levelinfo_tokens, i,
2431 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2432 *leveldir_new = ldi;
2434 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2435 setString(&leveldir_new->name, leveldir_new->subdir);
2437 if (leveldir_new->identifier == NULL)
2438 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2440 if (leveldir_new->name_sorting == NULL)
2441 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2443 if (node_parent == NULL) /* top level group */
2445 leveldir_new->basepath = getStringCopy(level_directory);
2446 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2448 else /* sub level group */
2450 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2451 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2455 if (leveldir_new->levels < 1)
2456 leveldir_new->levels = 1;
2459 leveldir_new->last_level =
2460 leveldir_new->first_level + leveldir_new->levels - 1;
2462 leveldir_new->in_user_dir =
2463 (!strEqual(leveldir_new->basepath, options.level_directory));
2465 /* adjust some settings if user's private level directory was detected */
2466 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2467 leveldir_new->in_user_dir &&
2468 (strEqual(leveldir_new->subdir, getLoginName()) ||
2469 strEqual(leveldir_new->name, getLoginName()) ||
2470 strEqual(leveldir_new->author, getRealName())))
2472 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2473 leveldir_new->readonly = FALSE;
2476 leveldir_new->user_defined =
2477 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2479 leveldir_new->color = LEVELCOLOR(leveldir_new);
2481 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2483 leveldir_new->handicap_level = /* set handicap to default value */
2484 (leveldir_new->user_defined || !leveldir_new->handicap ?
2485 leveldir_new->last_level : leveldir_new->first_level);
2487 if (leveldir_new->level_group)
2488 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2491 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2493 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2495 /* skip level sets without levels (which are probably artwork base sets) */
2497 freeSetupFileHash(setup_file_hash);
2498 free(directory_path);
2506 pushTreeInfo(node_first, leveldir_new);
2508 freeSetupFileHash(setup_file_hash);
2510 if (leveldir_new->level_group)
2512 /* create node to link back to current level directory */
2513 createParentTreeInfoNode(leveldir_new);
2515 /* recursively step into sub-directory and look for more level series */
2516 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2517 leveldir_new, directory_path);
2520 free(directory_path);
2526 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2527 TreeInfo *node_parent,
2528 char *level_directory)
2531 struct dirent *dir_entry;
2532 boolean valid_entry_found = FALSE;
2534 if ((dir = opendir(level_directory)) == NULL)
2536 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2540 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2542 struct stat file_status;
2543 char *directory_name = dir_entry->d_name;
2544 char *directory_path = getPath2(level_directory, directory_name);
2546 /* skip entries for current and parent directory */
2547 if (strEqual(directory_name, ".") ||
2548 strEqual(directory_name, ".."))
2550 free(directory_path);
2554 /* find out if directory entry is itself a directory */
2555 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2556 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2558 free(directory_path);
2562 free(directory_path);
2564 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2565 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2566 strEqual(directory_name, MUSIC_DIRECTORY))
2569 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2576 /* special case: top level directory may directly contain "levelinfo.conf" */
2577 if (node_parent == NULL && !valid_entry_found)
2579 /* check if this directory directly contains a file "levelinfo.conf" */
2580 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2581 level_directory, ".");
2584 if (!valid_entry_found)
2585 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2589 boolean AdjustGraphicsForEMC()
2591 boolean settings_changed = FALSE;
2593 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2594 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2596 return settings_changed;
2599 void LoadLevelInfo()
2601 InitUserLevelDirectory(getLoginName());
2603 DrawInitText("Loading level series", 120, FC_GREEN);
2605 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2606 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2608 /* after loading all level set information, clone the level directory tree
2609 and remove all level sets without levels (these may still contain artwork
2610 to be offered in the setup menu as "custom artwork", and are therefore
2611 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2612 leveldir_first_all = leveldir_first;
2613 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2615 AdjustGraphicsForEMC();
2617 /* before sorting, the first entries will be from the user directory */
2618 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2620 if (leveldir_first == NULL)
2621 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2623 sortTreeInfo(&leveldir_first);
2626 dumpTreeInfo(leveldir_first, 0);
2630 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2631 TreeInfo *node_parent,
2632 char *base_directory,
2633 char *directory_name, int type)
2635 char *directory_path = getPath2(base_directory, directory_name);
2636 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2637 SetupFileHash *setup_file_hash = NULL;
2638 TreeInfo *artwork_new = NULL;
2641 if (fileExists(filename))
2642 setup_file_hash = loadSetupFileHash(filename);
2644 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2647 struct dirent *dir_entry;
2648 boolean valid_file_found = FALSE;
2650 if ((dir = opendir(directory_path)) != NULL)
2652 while ((dir_entry = readdir(dir)) != NULL)
2654 char *entry_name = dir_entry->d_name;
2656 if (FileIsArtworkType(entry_name, type))
2658 valid_file_found = TRUE;
2666 if (!valid_file_found)
2668 if (!strEqual(directory_name, "."))
2669 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2671 free(directory_path);
2678 artwork_new = newTreeInfo();
2681 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2683 setTreeInfoToDefaults(artwork_new, type);
2685 artwork_new->subdir = getStringCopy(directory_name);
2687 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2690 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2693 /* set all structure fields according to the token/value pairs */
2695 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2696 setSetupInfo(levelinfo_tokens, i,
2697 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2700 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2701 setString(&artwork_new->name, artwork_new->subdir);
2703 if (artwork_new->identifier == NULL)
2704 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2706 if (artwork_new->name_sorting == NULL)
2707 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2710 if (node_parent == NULL) /* top level group */
2712 artwork_new->basepath = getStringCopy(base_directory);
2713 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2715 else /* sub level group */
2717 artwork_new->basepath = getStringCopy(node_parent->basepath);
2718 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2721 artwork_new->in_user_dir =
2722 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2724 /* (may use ".sort_priority" from "setup_file_hash" above) */
2725 artwork_new->color = ARTWORKCOLOR(artwork_new);
2727 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2729 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2731 if (strEqual(artwork_new->subdir, "."))
2733 if (artwork_new->user_defined)
2735 setString(&artwork_new->identifier, "private");
2736 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2740 setString(&artwork_new->identifier, "classic");
2741 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2744 /* set to new values after changing ".sort_priority" */
2745 artwork_new->color = ARTWORKCOLOR(artwork_new);
2747 setString(&artwork_new->class_desc,
2748 getLevelClassDescription(artwork_new));
2752 setString(&artwork_new->identifier, artwork_new->subdir);
2755 setString(&artwork_new->name, artwork_new->identifier);
2756 setString(&artwork_new->name_sorting, artwork_new->name);
2760 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2763 pushTreeInfo(node_first, artwork_new);
2765 freeSetupFileHash(setup_file_hash);
2767 free(directory_path);
2773 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2774 TreeInfo *node_parent,
2775 char *base_directory, int type)
2778 struct dirent *dir_entry;
2779 boolean valid_entry_found = FALSE;
2781 if ((dir = opendir(base_directory)) == NULL)
2783 /* display error if directory is main "options.graphics_directory" etc. */
2784 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2785 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2790 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2792 struct stat file_status;
2793 char *directory_name = dir_entry->d_name;
2794 char *directory_path = getPath2(base_directory, directory_name);
2796 /* skip directory entries for current and parent directory */
2797 if (strEqual(directory_name, ".") ||
2798 strEqual(directory_name, ".."))
2800 free(directory_path);
2804 /* skip directory entries which are not a directory or are not accessible */
2805 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2806 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2808 free(directory_path);
2812 free(directory_path);
2814 /* check if this directory contains artwork with or without config file */
2815 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2817 directory_name, type);
2822 /* check if this directory directly contains artwork itself */
2823 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2824 base_directory, ".",
2826 if (!valid_entry_found)
2827 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2831 static TreeInfo *getDummyArtworkInfo(int type)
2833 /* this is only needed when there is completely no artwork available */
2834 TreeInfo *artwork_new = newTreeInfo();
2836 setTreeInfoToDefaults(artwork_new, type);
2838 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
2839 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2840 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2842 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
2843 setString(&artwork_new->name, UNDEFINED_FILENAME);
2844 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2849 void LoadArtworkInfo()
2851 LoadArtworkInfoCache();
2853 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
2855 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2856 options.graphics_directory,
2857 TREE_TYPE_GRAPHICS_DIR);
2858 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2859 getUserGraphicsDir(),
2860 TREE_TYPE_GRAPHICS_DIR);
2862 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2863 options.sounds_directory,
2864 TREE_TYPE_SOUNDS_DIR);
2865 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2867 TREE_TYPE_SOUNDS_DIR);
2869 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2870 options.music_directory,
2871 TREE_TYPE_MUSIC_DIR);
2872 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2874 TREE_TYPE_MUSIC_DIR);
2876 if (artwork.gfx_first == NULL)
2877 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2878 if (artwork.snd_first == NULL)
2879 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2880 if (artwork.mus_first == NULL)
2881 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2883 /* before sorting, the first entries will be from the user directory */
2884 artwork.gfx_current =
2885 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2886 if (artwork.gfx_current == NULL)
2887 artwork.gfx_current =
2888 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2889 if (artwork.gfx_current == NULL)
2890 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2892 artwork.snd_current =
2893 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2894 if (artwork.snd_current == NULL)
2895 artwork.snd_current =
2896 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2897 if (artwork.snd_current == NULL)
2898 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2900 artwork.mus_current =
2901 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2902 if (artwork.mus_current == NULL)
2903 artwork.mus_current =
2904 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2905 if (artwork.mus_current == NULL)
2906 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2908 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2909 artwork.snd_current_identifier = artwork.snd_current->identifier;
2910 artwork.mus_current_identifier = artwork.mus_current->identifier;
2913 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2914 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2915 printf("music set == %s\n\n", artwork.mus_current_identifier);
2918 sortTreeInfo(&artwork.gfx_first);
2919 sortTreeInfo(&artwork.snd_first);
2920 sortTreeInfo(&artwork.mus_first);
2923 dumpTreeInfo(artwork.gfx_first, 0);
2924 dumpTreeInfo(artwork.snd_first, 0);
2925 dumpTreeInfo(artwork.mus_first, 0);
2929 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2930 LevelDirTree *level_node)
2932 int type = (*artwork_node)->type;
2934 /* recursively check all level directories for artwork sub-directories */
2938 /* check all tree entries for artwork, but skip parent link entries */
2939 if (!level_node->parent_link)
2941 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
2942 boolean cached = (artwork_new != NULL);
2946 pushTreeInfo(artwork_node, artwork_new);
2950 TreeInfo *topnode_last = *artwork_node;
2951 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2952 ARTWORK_DIRECTORY(type));
2954 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
2956 if (topnode_last != *artwork_node) /* check for newly added node */
2958 artwork_new = *artwork_node;
2960 setString(&artwork_new->identifier, level_node->subdir);
2961 setString(&artwork_new->name, level_node->name);
2962 setString(&artwork_new->name_sorting, level_node->name_sorting);
2964 artwork_new->sort_priority = level_node->sort_priority;
2965 artwork_new->color = LEVELCOLOR(artwork_new);
2971 /* insert artwork info (from old cache or filesystem) into new cache */
2972 if (artwork_new != NULL)
2973 setArtworkInfoCacheEntry(artwork_new, level_node, type);
2977 if (level_node->level_group)
2978 DrawInitText(level_node->name, 150, FC_YELLOW);
2981 if (level_node->node_group != NULL)
2982 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2984 level_node = level_node->next;
2988 void LoadLevelArtworkInfo()
2990 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
2992 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
2993 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
2994 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
2996 SaveArtworkInfoCache();
2998 /* needed for reloading level artwork not known at ealier stage */
3000 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3002 artwork.gfx_current =
3003 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3004 if (artwork.gfx_current == NULL)
3005 artwork.gfx_current =
3006 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3007 if (artwork.gfx_current == NULL)
3008 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3011 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3013 artwork.snd_current =
3014 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3015 if (artwork.snd_current == NULL)
3016 artwork.snd_current =
3017 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3018 if (artwork.snd_current == NULL)
3019 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3022 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3024 artwork.mus_current =
3025 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3026 if (artwork.mus_current == NULL)
3027 artwork.mus_current =
3028 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3029 if (artwork.mus_current == NULL)
3030 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3033 sortTreeInfo(&artwork.gfx_first);
3034 sortTreeInfo(&artwork.snd_first);
3035 sortTreeInfo(&artwork.mus_first);
3038 dumpTreeInfo(artwork.gfx_first, 0);
3039 dumpTreeInfo(artwork.snd_first, 0);
3040 dumpTreeInfo(artwork.mus_first, 0);
3044 static void SaveUserLevelInfo()
3046 LevelDirTree *level_info;
3051 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3053 if (!(file = fopen(filename, MODE_WRITE)))
3055 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3060 level_info = newTreeInfo();
3062 /* always start with reliable default values */
3063 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3065 setString(&level_info->name, getLoginName());
3066 setString(&level_info->author, getRealName());
3067 level_info->levels = 100;
3068 level_info->first_level = 1;
3070 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3072 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3073 getCookie("LEVELINFO")));
3076 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3078 if (i == LEVELINFO_TOKEN_NAME ||
3079 i == LEVELINFO_TOKEN_AUTHOR ||
3080 i == LEVELINFO_TOKEN_LEVELS ||
3081 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3082 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3084 /* just to make things nicer :) */
3085 if (i == LEVELINFO_TOKEN_AUTHOR)
3086 fprintf(file, "\n");
3089 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3093 SetFilePermissions(filename, PERMS_PRIVATE);
3095 freeTreeInfo(level_info);
3099 char *getSetupValue(int type, void *value)
3101 static char value_string[MAX_LINE_LEN];
3109 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3113 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3117 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3121 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3125 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3129 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3133 sprintf(value_string, "%d", *(int *)value);
3137 if (*(char **)value == NULL)
3140 strcpy(value_string, *(char **)value);
3144 value_string[0] = '\0';
3148 if (type & TYPE_GHOSTED)
3149 strcpy(value_string, "n/a");
3151 return value_string;
3154 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3158 static char token_string[MAX_LINE_LEN];
3159 int token_type = token_info[token_nr].type;
3160 void *setup_value = token_info[token_nr].value;
3161 char *token_text = token_info[token_nr].text;
3162 char *value_string = getSetupValue(token_type, setup_value);
3164 /* build complete token string */
3165 sprintf(token_string, "%s%s", prefix, token_text);
3167 /* build setup entry line */
3168 line = getFormattedSetupEntry(token_string, value_string);
3170 if (token_type == TYPE_KEY_X11)
3172 Key key = *(Key *)setup_value;
3173 char *keyname = getKeyNameFromKey(key);
3175 /* add comment, if useful */
3176 if (!strEqual(keyname, "(undefined)") &&
3177 !strEqual(keyname, "(unknown)"))
3179 /* add at least one whitespace */
3181 for (i = strlen(line); i < token_comment_position; i++)
3185 strcat(line, keyname);
3192 void LoadLevelSetup_LastSeries()
3194 /* ----------------------------------------------------------------------- */
3195 /* ~/.<program>/levelsetup.conf */
3196 /* ----------------------------------------------------------------------- */
3198 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3199 SetupFileHash *level_setup_hash = NULL;
3201 /* always start with reliable default values */
3202 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3204 if ((level_setup_hash = loadSetupFileHash(filename)))
3206 char *last_level_series =
3207 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3209 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3211 if (leveldir_current == NULL)
3212 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3214 checkSetupFileHashIdentifier(level_setup_hash, filename,
3215 getCookie("LEVELSETUP"));
3217 freeSetupFileHash(level_setup_hash);
3220 Error(ERR_WARN, "using default setup values");
3225 void SaveLevelSetup_LastSeries()
3227 /* ----------------------------------------------------------------------- */
3228 /* ~/.<program>/levelsetup.conf */
3229 /* ----------------------------------------------------------------------- */
3231 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3232 char *level_subdir = leveldir_current->subdir;
3235 InitUserDataDirectory();
3237 if (!(file = fopen(filename, MODE_WRITE)))
3239 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3244 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3245 getCookie("LEVELSETUP")));
3246 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3251 SetFilePermissions(filename, PERMS_PRIVATE);
3256 static void checkSeriesInfo()
3258 static char *level_directory = NULL;
3260 struct dirent *dir_entry;
3262 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3264 level_directory = getPath2((leveldir_current->in_user_dir ?
3265 getUserLevelDir(NULL) :
3266 options.level_directory),
3267 leveldir_current->fullpath);
3269 if ((dir = opendir(level_directory)) == NULL)
3271 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3275 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3277 if (strlen(dir_entry->d_name) > 4 &&
3278 dir_entry->d_name[3] == '.' &&
3279 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3281 char levelnum_str[4];
3284 strncpy(levelnum_str, dir_entry->d_name, 3);
3285 levelnum_str[3] = '\0';
3287 levelnum_value = atoi(levelnum_str);
3290 if (levelnum_value < leveldir_current->first_level)
3292 Error(ERR_WARN, "additional level %d found", levelnum_value);
3293 leveldir_current->first_level = levelnum_value;
3295 else if (levelnum_value > leveldir_current->last_level)
3297 Error(ERR_WARN, "additional level %d found", levelnum_value);
3298 leveldir_current->last_level = levelnum_value;
3307 void LoadLevelSetup_SeriesInfo()
3310 SetupFileHash *level_setup_hash = NULL;
3311 char *level_subdir = leveldir_current->subdir;
3313 /* always start with reliable default values */
3314 level_nr = leveldir_current->first_level;
3316 checkSeriesInfo(leveldir_current);
3318 /* ----------------------------------------------------------------------- */
3319 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3320 /* ----------------------------------------------------------------------- */
3322 level_subdir = leveldir_current->subdir;
3324 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3326 if ((level_setup_hash = loadSetupFileHash(filename)))
3330 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3334 level_nr = atoi(token_value);
3336 if (level_nr < leveldir_current->first_level)
3337 level_nr = leveldir_current->first_level;
3338 if (level_nr > leveldir_current->last_level)
3339 level_nr = leveldir_current->last_level;
3342 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3346 int level_nr = atoi(token_value);
3348 if (level_nr < leveldir_current->first_level)
3349 level_nr = leveldir_current->first_level;
3350 if (level_nr > leveldir_current->last_level + 1)
3351 level_nr = leveldir_current->last_level;
3353 if (leveldir_current->user_defined || !leveldir_current->handicap)
3354 level_nr = leveldir_current->last_level;
3356 leveldir_current->handicap_level = level_nr;
3359 checkSetupFileHashIdentifier(level_setup_hash, filename,
3360 getCookie("LEVELSETUP"));
3362 freeSetupFileHash(level_setup_hash);
3365 Error(ERR_WARN, "using default setup values");
3370 void SaveLevelSetup_SeriesInfo()
3373 char *level_subdir = leveldir_current->subdir;
3374 char *level_nr_str = int2str(level_nr, 0);
3375 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3378 /* ----------------------------------------------------------------------- */
3379 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3380 /* ----------------------------------------------------------------------- */
3382 InitLevelSetupDirectory(level_subdir);
3384 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3386 if (!(file = fopen(filename, MODE_WRITE)))
3388 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3393 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3394 getCookie("LEVELSETUP")));
3395 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3397 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3398 handicap_level_str));
3402 SetFilePermissions(filename, PERMS_PRIVATE);