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 static char *getCorrectedArtworkBasename(char *basename)
502 char *basename_corrected = basename;
504 #if defined(PLATFORM_MSDOS)
505 if (program.filename_prefix != NULL)
507 int prefix_len = strlen(program.filename_prefix);
509 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
510 basename_corrected = &basename[prefix_len];
512 /* if corrected filename is still longer than standard MS-DOS filename
513 size (8 characters + 1 dot + 3 characters file extension), shorten
514 filename by writing file extension after 8th basename character */
515 if (strlen(basename_corrected) > 8 + 1 + 3)
517 static char *msdos_filename = NULL;
519 checked_free(msdos_filename);
521 msdos_filename = getStringCopy(basename_corrected);
522 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
524 basename_corrected = msdos_filename;
529 return basename_corrected;
532 char *getCustomImageFilename(char *basename)
534 static char *filename = NULL;
535 boolean skip_setup_artwork = FALSE;
537 checked_free(filename);
539 basename = getCorrectedArtworkBasename(basename);
541 if (!setup.override_level_graphics)
543 /* 1st try: look for special artwork in current level series directory */
544 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
545 if (fileExists(filename))
550 /* check if there is special artwork configured in level series config */
551 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
553 /* 2nd try: look for special artwork configured in level series config */
554 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
555 if (fileExists(filename))
560 /* take missing artwork configured in level set config from default */
561 skip_setup_artwork = TRUE;
565 if (!skip_setup_artwork)
567 /* 3rd try: look for special artwork in configured artwork directory */
568 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
569 if (fileExists(filename))
575 /* 4th try: look for default artwork in new default artwork directory */
576 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
577 if (fileExists(filename))
582 /* 5th try: look for default artwork in old default artwork directory */
583 filename = getPath2(options.graphics_directory, basename);
584 if (fileExists(filename))
587 return NULL; /* cannot find specified artwork file anywhere */
590 char *getCustomSoundFilename(char *basename)
592 static char *filename = NULL;
593 boolean skip_setup_artwork = FALSE;
595 checked_free(filename);
597 basename = getCorrectedArtworkBasename(basename);
599 if (!setup.override_level_sounds)
601 /* 1st try: look for special artwork in current level series directory */
602 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
603 if (fileExists(filename))
608 /* check if there is special artwork configured in level series config */
609 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
611 /* 2nd try: look for special artwork configured in level series config */
612 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
613 if (fileExists(filename))
618 /* take missing artwork configured in level set config from default */
619 skip_setup_artwork = TRUE;
623 if (!skip_setup_artwork)
625 /* 3rd try: look for special artwork in configured artwork directory */
626 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
627 if (fileExists(filename))
633 /* 4th try: look for default artwork in new default artwork directory */
634 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
635 if (fileExists(filename))
640 /* 5th try: look for default artwork in old default artwork directory */
641 filename = getPath2(options.sounds_directory, basename);
642 if (fileExists(filename))
645 return NULL; /* cannot find specified artwork file anywhere */
648 char *getCustomMusicFilename(char *basename)
650 static char *filename = NULL;
651 boolean skip_setup_artwork = FALSE;
653 checked_free(filename);
655 basename = getCorrectedArtworkBasename(basename);
657 if (!setup.override_level_music)
659 /* 1st try: look for special artwork in current level series directory */
660 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
661 if (fileExists(filename))
666 /* check if there is special artwork configured in level series config */
667 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
669 /* 2nd try: look for special artwork configured in level series config */
670 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
671 if (fileExists(filename))
676 /* take missing artwork configured in level set config from default */
677 skip_setup_artwork = TRUE;
681 if (!skip_setup_artwork)
683 /* 3rd try: look for special artwork in configured artwork directory */
684 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
685 if (fileExists(filename))
691 /* 4th try: look for default artwork in new default artwork directory */
692 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
693 if (fileExists(filename))
698 /* 5th try: look for default artwork in old default artwork directory */
699 filename = getPath2(options.music_directory, basename);
700 if (fileExists(filename))
703 return NULL; /* cannot find specified artwork file anywhere */
706 char *getCustomArtworkFilename(char *basename, int type)
708 if (type == ARTWORK_TYPE_GRAPHICS)
709 return getCustomImageFilename(basename);
710 else if (type == ARTWORK_TYPE_SOUNDS)
711 return getCustomSoundFilename(basename);
712 else if (type == ARTWORK_TYPE_MUSIC)
713 return getCustomMusicFilename(basename);
715 return UNDEFINED_FILENAME;
718 char *getCustomArtworkConfigFilename(int type)
720 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
723 char *getCustomArtworkLevelConfigFilename(int type)
725 static char *filename = NULL;
727 checked_free(filename);
729 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
734 char *getCustomMusicDirectory(void)
736 static char *directory = NULL;
737 boolean skip_setup_artwork = FALSE;
739 checked_free(directory);
741 if (!setup.override_level_music)
743 /* 1st try: look for special artwork in current level series directory */
744 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
745 if (fileExists(directory))
750 /* check if there is special artwork configured in level series config */
751 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
753 /* 2nd try: look for special artwork configured in level series config */
754 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
755 if (fileExists(directory))
760 /* take missing artwork configured in level set config from default */
761 skip_setup_artwork = TRUE;
765 if (!skip_setup_artwork)
767 /* 3rd try: look for special artwork in configured artwork directory */
768 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
769 if (fileExists(directory))
775 /* 4th try: look for default artwork in new default artwork directory */
776 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
777 if (fileExists(directory))
782 /* 5th try: look for default artwork in old default artwork directory */
783 directory = getStringCopy(options.music_directory);
784 if (fileExists(directory))
787 return NULL; /* cannot find specified artwork file anywhere */
790 void InitTapeDirectory(char *level_subdir)
792 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
793 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
794 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
797 void InitScoreDirectory(char *level_subdir)
799 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
800 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
801 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
804 static void SaveUserLevelInfo();
806 void InitUserLevelDirectory(char *level_subdir)
808 if (!fileExists(getUserLevelDir(level_subdir)))
810 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
811 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
812 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
818 void InitLevelSetupDirectory(char *level_subdir)
820 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
821 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
822 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
825 void InitCacheDirectory()
827 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
828 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
832 /* ------------------------------------------------------------------------- */
833 /* some functions to handle lists of level and artwork directories */
834 /* ------------------------------------------------------------------------- */
836 TreeInfo *newTreeInfo()
838 return checked_calloc(sizeof(TreeInfo));
841 TreeInfo *newTreeInfo_setDefaults(int type)
843 TreeInfo *ti = newTreeInfo();
845 setTreeInfoToDefaults(ti, type);
850 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
852 node_new->next = *node_first;
853 *node_first = node_new;
856 int numTreeInfo(TreeInfo *node)
869 boolean validLevelSeries(TreeInfo *node)
871 return (node != NULL && !node->node_group && !node->parent_link);
874 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
879 if (node->node_group) /* enter level group (step down into tree) */
880 return getFirstValidTreeInfoEntry(node->node_group);
881 else if (node->parent_link) /* skip start entry of level group */
883 if (node->next) /* get first real level series entry */
884 return getFirstValidTreeInfoEntry(node->next);
885 else /* leave empty level group and go on */
886 return getFirstValidTreeInfoEntry(node->node_parent->next);
888 else /* this seems to be a regular level series */
892 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
897 if (node->node_parent == NULL) /* top level group */
898 return *node->node_top;
899 else /* sub level group */
900 return node->node_parent->node_group;
903 int numTreeInfoInGroup(TreeInfo *node)
905 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
908 int posTreeInfo(TreeInfo *node)
910 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
915 if (node_cmp == node)
919 node_cmp = node_cmp->next;
925 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
927 TreeInfo *node_default = node;
942 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
944 if (identifier == NULL)
949 if (node->node_group)
951 TreeInfo *node_group;
953 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
958 else if (!node->parent_link)
960 if (strEqual(identifier, node->identifier))
970 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
971 TreeInfo *node, boolean skip_sets_without_levels)
978 if (!node->parent_link && !node->level_group &&
979 skip_sets_without_levels && node->levels == 0)
980 return cloneTreeNode(node_top, node_parent, node->next,
981 skip_sets_without_levels);
983 node_new = newTreeInfo();
985 *node_new = *node; /* copy complete node */
987 node_new->node_top = node_top; /* correct top node link */
988 node_new->node_parent = node_parent; /* correct parent node link */
990 if (node->level_group)
991 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
992 skip_sets_without_levels);
994 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
995 skip_sets_without_levels);
1000 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1002 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1004 *ti_new = ti_cloned;
1007 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1009 boolean settings_changed = FALSE;
1013 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1014 !strEqual(node->graphics_set, node->graphics_set_ecs))
1016 setString(&node->graphics_set, node->graphics_set_ecs);
1017 settings_changed = TRUE;
1019 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1020 !strEqual(node->graphics_set, node->graphics_set_aga))
1022 setString(&node->graphics_set, node->graphics_set_aga);
1023 settings_changed = TRUE;
1026 if (node->node_group != NULL)
1027 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1032 return settings_changed;
1035 void dumpTreeInfo(TreeInfo *node, int depth)
1039 printf("Dumping TreeInfo:\n");
1043 for (i = 0; i < (depth + 1) * 3; i++)
1046 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1047 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1049 if (node->node_group != NULL)
1050 dumpTreeInfo(node->node_group, depth + 1);
1056 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1057 int (*compare_function)(const void *,
1060 int num_nodes = numTreeInfo(*node_first);
1061 TreeInfo **sort_array;
1062 TreeInfo *node = *node_first;
1068 /* allocate array for sorting structure pointers */
1069 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1071 /* writing structure pointers to sorting array */
1072 while (i < num_nodes && node) /* double boundary check... */
1074 sort_array[i] = node;
1080 /* sorting the structure pointers in the sorting array */
1081 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1084 /* update the linkage of list elements with the sorted node array */
1085 for (i = 0; i < num_nodes - 1; i++)
1086 sort_array[i]->next = sort_array[i + 1];
1087 sort_array[num_nodes - 1]->next = NULL;
1089 /* update the linkage of the main list anchor pointer */
1090 *node_first = sort_array[0];
1094 /* now recursively sort the level group structures */
1098 if (node->node_group != NULL)
1099 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1105 void sortTreeInfo(TreeInfo **node_first)
1107 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1111 /* ========================================================================= */
1112 /* some stuff from "files.c" */
1113 /* ========================================================================= */
1115 #if defined(PLATFORM_WIN32)
1117 #define S_IRGRP S_IRUSR
1120 #define S_IROTH S_IRUSR
1123 #define S_IWGRP S_IWUSR
1126 #define S_IWOTH S_IWUSR
1129 #define S_IXGRP S_IXUSR
1132 #define S_IXOTH S_IXUSR
1135 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1140 #endif /* PLATFORM_WIN32 */
1142 /* file permissions for newly written files */
1143 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1144 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1145 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1147 #define MODE_W_PRIVATE (S_IWUSR)
1148 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1149 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1151 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1152 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1154 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1155 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1159 static char *dir = NULL;
1161 #if defined(PLATFORM_WIN32)
1164 dir = checked_malloc(MAX_PATH + 1);
1166 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1169 #elif defined(PLATFORM_UNIX)
1172 if ((dir = getenv("HOME")) == NULL)
1176 if ((pwd = getpwuid(getuid())) != NULL)
1177 dir = getStringCopy(pwd->pw_dir);
1189 char *getCommonDataDir(void)
1191 static char *common_data_dir = NULL;
1193 #if defined(PLATFORM_WIN32)
1194 if (common_data_dir == NULL)
1196 char *dir = checked_malloc(MAX_PATH + 1);
1198 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1199 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1200 common_data_dir = getPath2(dir, program.userdata_subdir);
1202 common_data_dir = options.rw_base_directory;
1205 if (common_data_dir == NULL)
1206 common_data_dir = options.rw_base_directory;
1209 return common_data_dir;
1212 char *getPersonalDataDir(void)
1214 static char *personal_data_dir = NULL;
1216 #if defined(PLATFORM_MACOSX)
1217 if (personal_data_dir == NULL)
1218 personal_data_dir = getPath2(getHomeDir(), "Documents");
1220 if (personal_data_dir == NULL)
1221 personal_data_dir = getHomeDir();
1224 return personal_data_dir;
1227 char *getUserGameDataDir(void)
1229 static char *user_game_data_dir = NULL;
1231 if (user_game_data_dir == NULL)
1232 user_game_data_dir = getPath2(getPersonalDataDir(),
1233 program.userdata_subdir);
1235 return user_game_data_dir;
1238 void updateUserGameDataDir()
1240 #if defined(PLATFORM_MACOSX)
1241 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1242 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1244 /* convert old Unix style game data directory to Mac OS X style, if needed */
1245 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1247 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1249 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1250 userdata_dir_old, userdata_dir_new);
1252 /* continue using Unix style data directory -- this should not happen */
1253 program.userdata_path = getPath2(getPersonalDataDir(),
1254 program.userdata_subdir_unix);
1258 free(userdata_dir_old);
1264 return getUserGameDataDir();
1267 static mode_t posix_umask(mode_t mask)
1269 #if defined(PLATFORM_UNIX)
1276 static int posix_mkdir(const char *pathname, mode_t mode)
1278 #if defined(PLATFORM_WIN32)
1279 return mkdir(pathname);
1281 return mkdir(pathname, mode);
1285 void createDirectory(char *dir, char *text, int permission_class)
1287 /* leave "other" permissions in umask untouched, but ensure group parts
1288 of USERDATA_DIR_MODE are not masked */
1289 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1290 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1291 mode_t normal_umask = posix_umask(0);
1292 mode_t group_umask = ~(dir_mode & S_IRWXG);
1293 posix_umask(normal_umask & group_umask);
1295 if (!fileExists(dir))
1296 if (posix_mkdir(dir, dir_mode) != 0)
1297 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1299 posix_umask(normal_umask); /* reset normal umask */
1302 void InitUserDataDirectory()
1304 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1307 void SetFilePermissions(char *filename, int permission_class)
1309 chmod(filename, (permission_class == PERMS_PRIVATE ?
1310 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1313 char *getCookie(char *file_type)
1315 static char cookie[MAX_COOKIE_LEN + 1];
1317 if (strlen(program.cookie_prefix) + 1 +
1318 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1319 return "[COOKIE ERROR]"; /* should never happen */
1321 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1322 program.cookie_prefix, file_type,
1323 program.version_major, program.version_minor);
1328 int getFileVersionFromCookieString(const char *cookie)
1330 const char *ptr_cookie1, *ptr_cookie2;
1331 const char *pattern1 = "_FILE_VERSION_";
1332 const char *pattern2 = "?.?";
1333 const int len_cookie = strlen(cookie);
1334 const int len_pattern1 = strlen(pattern1);
1335 const int len_pattern2 = strlen(pattern2);
1336 const int len_pattern = len_pattern1 + len_pattern2;
1337 int version_major, version_minor;
1339 if (len_cookie <= len_pattern)
1342 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1343 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1345 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1348 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1349 ptr_cookie2[1] != '.' ||
1350 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1353 version_major = ptr_cookie2[0] - '0';
1354 version_minor = ptr_cookie2[2] - '0';
1356 return VERSION_IDENT(version_major, version_minor, 0, 0);
1359 boolean checkCookieString(const char *cookie, const char *template)
1361 const char *pattern = "_FILE_VERSION_?.?";
1362 const int len_cookie = strlen(cookie);
1363 const int len_template = strlen(template);
1364 const int len_pattern = strlen(pattern);
1366 if (len_cookie != len_template)
1369 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1375 /* ------------------------------------------------------------------------- */
1376 /* setup file list and hash handling functions */
1377 /* ------------------------------------------------------------------------- */
1379 char *getFormattedSetupEntry(char *token, char *value)
1382 static char entry[MAX_LINE_LEN];
1384 /* if value is an empty string, just return token without value */
1388 /* start with the token and some spaces to format output line */
1389 sprintf(entry, "%s:", token);
1390 for (i = strlen(entry); i < token_value_position; i++)
1393 /* continue with the token's value */
1394 strcat(entry, value);
1399 SetupFileList *newSetupFileList(char *token, char *value)
1401 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1403 new->token = getStringCopy(token);
1404 new->value = getStringCopy(value);
1411 void freeSetupFileList(SetupFileList *list)
1416 checked_free(list->token);
1417 checked_free(list->value);
1420 freeSetupFileList(list->next);
1425 char *getListEntry(SetupFileList *list, char *token)
1430 if (strEqual(list->token, token))
1433 return getListEntry(list->next, token);
1436 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1441 if (strEqual(list->token, token))
1443 checked_free(list->value);
1445 list->value = getStringCopy(value);
1449 else if (list->next == NULL)
1450 return (list->next = newSetupFileList(token, value));
1452 return setListEntry(list->next, token, value);
1455 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1460 if (list->next == NULL)
1461 return (list->next = newSetupFileList(token, value));
1463 return addListEntry(list->next, token, value);
1467 static void printSetupFileList(SetupFileList *list)
1472 printf("token: '%s'\n", list->token);
1473 printf("value: '%s'\n", list->value);
1475 printSetupFileList(list->next);
1480 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1481 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1482 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1483 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1485 #define insert_hash_entry hashtable_insert
1486 #define search_hash_entry hashtable_search
1487 #define change_hash_entry hashtable_change
1488 #define remove_hash_entry hashtable_remove
1491 static unsigned int get_hash_from_key(void *key)
1496 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1497 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1498 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1499 it works better than many other constants, prime or not) has never been
1500 adequately explained.
1502 If you just want to have a good hash function, and cannot wait, djb2
1503 is one of the best string hash functions i know. It has excellent
1504 distribution and speed on many different sets of keys and table sizes.
1505 You are not likely to do better with one of the "well known" functions
1506 such as PJW, K&R, etc.
1508 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1511 char *str = (char *)key;
1512 unsigned int hash = 5381;
1515 while ((c = *str++))
1516 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1521 static int keys_are_equal(void *key1, void *key2)
1523 return (strEqual((char *)key1, (char *)key2));
1526 SetupFileHash *newSetupFileHash()
1528 SetupFileHash *new_hash =
1529 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1531 if (new_hash == NULL)
1532 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1537 void freeSetupFileHash(SetupFileHash *hash)
1542 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1545 char *getHashEntry(SetupFileHash *hash, char *token)
1550 return search_hash_entry(hash, token);
1553 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1560 value_copy = getStringCopy(value);
1562 /* change value; if it does not exist, insert it as new */
1563 if (!change_hash_entry(hash, token, value_copy))
1564 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1565 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1568 char *removeHashEntry(SetupFileHash *hash, char *token)
1573 return remove_hash_entry(hash, token);
1577 static void printSetupFileHash(SetupFileHash *hash)
1579 BEGIN_HASH_ITERATION(hash, itr)
1581 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1582 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1584 END_HASH_ITERATION(hash, itr)
1588 static void *loadSetupFileData(char *filename, boolean use_hash)
1590 char line[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1591 char *token, *value, *line_ptr;
1592 void *setup_file_data, *insert_ptr = NULL;
1593 boolean read_continued_line = FALSE;
1596 if (!(file = fopen(filename, MODE_READ)))
1598 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1604 setup_file_data = newSetupFileHash();
1606 insert_ptr = setup_file_data = newSetupFileList("", "");
1610 /* read next line of input file */
1611 if (!fgets(line, MAX_LINE_LEN, file))
1614 /* cut trailing newline or carriage return */
1615 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1616 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1619 if (read_continued_line)
1621 /* cut leading whitespaces from input line */
1622 for (line_ptr = line; *line_ptr; line_ptr++)
1623 if (*line_ptr != ' ' && *line_ptr != '\t')
1626 /* append new line to existing line, if there is enough space */
1627 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1628 strcat(previous_line, line_ptr);
1630 strcpy(line, previous_line); /* copy storage buffer to line */
1632 read_continued_line = FALSE;
1635 /* if the last character is '\', continue at next line */
1636 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1638 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1639 strcpy(previous_line, line); /* copy line to storage buffer */
1641 read_continued_line = TRUE;
1646 /* cut trailing comment from input line */
1647 for (line_ptr = line; *line_ptr; line_ptr++)
1649 if (*line_ptr == '#')
1656 /* cut trailing whitespaces from input line */
1657 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1658 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1661 /* ignore empty lines */
1665 /* cut leading whitespaces from token */
1666 for (token = line; *token; token++)
1667 if (*token != ' ' && *token != '\t')
1670 /* start with empty value as reliable default */
1673 /* find end of token to determine start of value */
1674 for (line_ptr = token; *line_ptr; line_ptr++)
1677 if (*line_ptr == ':' || *line_ptr == '=')
1679 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1682 *line_ptr = '\0'; /* terminate token string */
1683 value = line_ptr + 1; /* set beginning of value */
1689 /* cut trailing whitespaces from token */
1690 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1691 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1694 /* cut leading whitespaces from value */
1695 for (; *value; value++)
1696 if (*value != ' ' && *value != '\t')
1701 value = "true"; /* treat tokens without value as "true" */
1707 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1709 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1717 if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1718 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1722 SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1723 SetupFileList *first_valid_list_entry = setup_file_list->next;
1725 /* free empty list header */
1726 setup_file_list->next = NULL;
1727 freeSetupFileList(setup_file_list);
1728 setup_file_data = first_valid_list_entry;
1730 if (first_valid_list_entry == NULL)
1731 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1734 return setup_file_data;
1737 void saveSetupFileHash(SetupFileHash *hash, char *filename)
1741 if (!(file = fopen(filename, MODE_WRITE)))
1743 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
1748 BEGIN_HASH_ITERATION(hash, itr)
1750 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
1751 HASH_ITERATION_VALUE(itr)));
1753 END_HASH_ITERATION(hash, itr)
1758 SetupFileList *loadSetupFileList(char *filename)
1760 return (SetupFileList *)loadSetupFileData(filename, FALSE);
1763 SetupFileHash *loadSetupFileHash(char *filename)
1765 return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1768 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1769 char *filename, char *identifier)
1771 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1774 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
1775 else if (!checkCookieString(value, identifier))
1776 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
1780 /* ========================================================================= */
1781 /* setup file stuff */
1782 /* ========================================================================= */
1784 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1785 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1786 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1788 /* level directory info */
1789 #define LEVELINFO_TOKEN_IDENTIFIER 0
1790 #define LEVELINFO_TOKEN_NAME 1
1791 #define LEVELINFO_TOKEN_NAME_SORTING 2
1792 #define LEVELINFO_TOKEN_AUTHOR 3
1793 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
1794 #define LEVELINFO_TOKEN_IMPORTED_BY 5
1795 #define LEVELINFO_TOKEN_LEVELS 6
1796 #define LEVELINFO_TOKEN_FIRST_LEVEL 7
1797 #define LEVELINFO_TOKEN_SORT_PRIORITY 8
1798 #define LEVELINFO_TOKEN_LATEST_ENGINE 9
1799 #define LEVELINFO_TOKEN_LEVEL_GROUP 10
1800 #define LEVELINFO_TOKEN_READONLY 11
1801 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 12
1802 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 13
1803 #define LEVELINFO_TOKEN_GRAPHICS_SET 14
1804 #define LEVELINFO_TOKEN_SOUNDS_SET 15
1805 #define LEVELINFO_TOKEN_MUSIC_SET 16
1806 #define LEVELINFO_TOKEN_FILENAME 17
1807 #define LEVELINFO_TOKEN_FILETYPE 18
1808 #define LEVELINFO_TOKEN_HANDICAP 19
1809 #define LEVELINFO_TOKEN_SKIP_LEVELS 20
1811 #define NUM_LEVELINFO_TOKENS 21
1813 static LevelDirTree ldi;
1815 static struct TokenInfo levelinfo_tokens[] =
1817 /* level directory info */
1818 { TYPE_STRING, &ldi.identifier, "identifier" },
1819 { TYPE_STRING, &ldi.name, "name" },
1820 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1821 { TYPE_STRING, &ldi.author, "author" },
1822 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1823 { TYPE_STRING, &ldi.imported_by, "imported_by" },
1824 { TYPE_INTEGER, &ldi.levels, "levels" },
1825 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1826 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1827 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
1828 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1829 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1830 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
1831 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
1832 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1833 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1834 { TYPE_STRING, &ldi.music_set, "music_set" },
1835 { TYPE_STRING, &ldi.level_filename, "filename" },
1836 { TYPE_STRING, &ldi.level_filetype, "filetype" },
1837 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
1838 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
1841 static struct TokenInfo artworkinfo_tokens[] =
1843 /* artwork directory info */
1844 { TYPE_STRING, &ldi.identifier, "identifier" },
1845 { TYPE_STRING, &ldi.subdir, "subdir" },
1846 { TYPE_STRING, &ldi.name, "name" },
1847 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1848 { TYPE_STRING, &ldi.author, "author" },
1849 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1850 { TYPE_STRING, &ldi.basepath, "basepath" },
1851 { TYPE_STRING, &ldi.fullpath, "fullpath" },
1852 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
1853 { TYPE_INTEGER, &ldi.color, "color" },
1854 { TYPE_STRING, &ldi.class_desc, "class_desc" },
1859 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
1863 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1864 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1865 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1866 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1869 ti->node_parent = NULL;
1870 ti->node_group = NULL;
1877 ti->fullpath = NULL;
1878 ti->basepath = NULL;
1879 ti->identifier = NULL;
1880 ti->name = getStringCopy(ANONYMOUS_NAME);
1881 ti->name_sorting = NULL;
1882 ti->author = getStringCopy(ANONYMOUS_NAME);
1884 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1885 ti->latest_engine = FALSE; /* default: get from level */
1886 ti->parent_link = FALSE;
1887 ti->in_user_dir = FALSE;
1888 ti->user_defined = FALSE;
1890 ti->class_desc = NULL;
1892 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
1894 if (ti->type == TREE_TYPE_LEVEL_DIR)
1896 ti->imported_from = NULL;
1897 ti->imported_by = NULL;
1899 ti->graphics_set_ecs = NULL;
1900 ti->graphics_set_aga = NULL;
1901 ti->graphics_set = NULL;
1902 ti->sounds_set = NULL;
1903 ti->music_set = NULL;
1904 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1905 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1906 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
1908 ti->level_filename = NULL;
1909 ti->level_filetype = NULL;
1912 ti->first_level = 0;
1914 ti->level_group = FALSE;
1915 ti->handicap_level = 0;
1916 ti->readonly = TRUE;
1917 ti->handicap = TRUE;
1918 ti->skip_levels = FALSE;
1922 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
1926 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1928 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
1933 /* copy all values from the parent structure */
1935 ti->type = parent->type;
1937 ti->node_top = parent->node_top;
1938 ti->node_parent = parent;
1939 ti->node_group = NULL;
1946 ti->fullpath = NULL;
1947 ti->basepath = NULL;
1948 ti->identifier = NULL;
1949 ti->name = getStringCopy(ANONYMOUS_NAME);
1950 ti->name_sorting = NULL;
1951 ti->author = getStringCopy(parent->author);
1953 ti->sort_priority = parent->sort_priority;
1954 ti->latest_engine = parent->latest_engine;
1955 ti->parent_link = FALSE;
1956 ti->in_user_dir = parent->in_user_dir;
1957 ti->user_defined = parent->user_defined;
1958 ti->color = parent->color;
1959 ti->class_desc = getStringCopy(parent->class_desc);
1961 ti->infotext = getStringCopy(parent->infotext);
1963 if (ti->type == TREE_TYPE_LEVEL_DIR)
1965 ti->imported_from = getStringCopy(parent->imported_from);
1966 ti->imported_by = getStringCopy(parent->imported_by);
1968 ti->graphics_set_ecs = NULL;
1969 ti->graphics_set_aga = NULL;
1970 ti->graphics_set = NULL;
1971 ti->sounds_set = NULL;
1972 ti->music_set = NULL;
1973 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1974 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1975 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
1977 ti->level_filename = NULL;
1978 ti->level_filetype = NULL;
1981 ti->first_level = 0;
1983 ti->level_group = FALSE;
1984 ti->handicap_level = 0;
1985 ti->readonly = TRUE;
1986 ti->handicap = TRUE;
1987 ti->skip_levels = FALSE;
1991 static void freeTreeInfo(TreeInfo *ti)
1996 checked_free(ti->subdir);
1997 checked_free(ti->fullpath);
1998 checked_free(ti->basepath);
1999 checked_free(ti->identifier);
2001 checked_free(ti->name);
2002 checked_free(ti->name_sorting);
2003 checked_free(ti->author);
2005 checked_free(ti->class_desc);
2007 checked_free(ti->infotext);
2009 if (ti->type == TREE_TYPE_LEVEL_DIR)
2011 checked_free(ti->imported_from);
2012 checked_free(ti->imported_by);
2014 checked_free(ti->graphics_set_ecs);
2015 checked_free(ti->graphics_set_aga);
2016 checked_free(ti->graphics_set);
2017 checked_free(ti->sounds_set);
2018 checked_free(ti->music_set);
2020 checked_free(ti->graphics_path);
2021 checked_free(ti->sounds_path);
2022 checked_free(ti->music_path);
2024 checked_free(ti->level_filename);
2025 checked_free(ti->level_filetype);
2031 void setSetupInfo(struct TokenInfo *token_info,
2032 int token_nr, char *token_value)
2034 int token_type = token_info[token_nr].type;
2035 void *setup_value = token_info[token_nr].value;
2037 if (token_value == NULL)
2040 /* set setup field to corresponding token value */
2045 *(boolean *)setup_value = get_boolean_from_string(token_value);
2049 *(Key *)setup_value = getKeyFromKeyName(token_value);
2053 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2057 *(int *)setup_value = get_integer_from_string(token_value);
2061 checked_free(*(char **)setup_value);
2062 *(char **)setup_value = getStringCopy(token_value);
2070 static int compareTreeInfoEntries(const void *object1, const void *object2)
2072 const TreeInfo *entry1 = *((TreeInfo **)object1);
2073 const TreeInfo *entry2 = *((TreeInfo **)object2);
2074 int class_sorting1, class_sorting2;
2077 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2079 class_sorting1 = LEVELSORTING(entry1);
2080 class_sorting2 = LEVELSORTING(entry2);
2084 class_sorting1 = ARTWORKSORTING(entry1);
2085 class_sorting2 = ARTWORKSORTING(entry2);
2088 if (entry1->parent_link || entry2->parent_link)
2089 compare_result = (entry1->parent_link ? -1 : +1);
2090 else if (entry1->sort_priority == entry2->sort_priority)
2092 char *name1 = getStringToLower(entry1->name_sorting);
2093 char *name2 = getStringToLower(entry2->name_sorting);
2095 compare_result = strcmp(name1, name2);
2100 else if (class_sorting1 == class_sorting2)
2101 compare_result = entry1->sort_priority - entry2->sort_priority;
2103 compare_result = class_sorting1 - class_sorting2;
2105 return compare_result;
2108 static void createParentTreeInfoNode(TreeInfo *node_parent)
2112 if (node_parent == NULL)
2115 ti_new = newTreeInfo();
2116 setTreeInfoToDefaults(ti_new, node_parent->type);
2118 ti_new->node_parent = node_parent;
2119 ti_new->parent_link = TRUE;
2121 setString(&ti_new->identifier, node_parent->identifier);
2122 setString(&ti_new->name, ".. (parent directory)");
2123 setString(&ti_new->name_sorting, ti_new->name);
2125 setString(&ti_new->subdir, "..");
2126 setString(&ti_new->fullpath, node_parent->fullpath);
2128 ti_new->sort_priority = node_parent->sort_priority;
2129 ti_new->latest_engine = node_parent->latest_engine;
2131 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2133 pushTreeInfo(&node_parent->node_group, ti_new);
2137 /* -------------------------------------------------------------------------- */
2138 /* functions for handling level and custom artwork info cache */
2139 /* -------------------------------------------------------------------------- */
2141 static void LoadArtworkInfoCache()
2143 InitCacheDirectory();
2145 if (artworkinfo_cache_old == NULL)
2147 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2149 /* try to load artwork info hash from already existing cache file */
2150 artworkinfo_cache_old = loadSetupFileHash(filename);
2152 /* if no artwork info cache file was found, start with empty hash */
2153 if (artworkinfo_cache_old == NULL)
2154 artworkinfo_cache_old = newSetupFileHash();
2159 if (artworkinfo_cache_new == NULL)
2160 artworkinfo_cache_new = newSetupFileHash();
2163 static void SaveArtworkInfoCache()
2165 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2167 InitCacheDirectory();
2169 saveSetupFileHash(artworkinfo_cache_new, filename);
2174 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2176 static char *prefix = NULL;
2178 checked_free(prefix);
2180 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2185 /* (identical to above function, but separate string buffer needed -- nasty) */
2186 static char *getCacheToken(char *prefix, char *suffix)
2188 static char *token = NULL;
2190 checked_free(token);
2192 token = getStringCat2WithSeparator(prefix, suffix, ".");
2197 static char *getFileTimestamp(char *filename)
2199 struct stat file_status;
2201 if (stat(filename, &file_status) != 0) /* cannot stat file */
2202 return getStringCopy(i_to_a(0));
2204 return getStringCopy(i_to_a(file_status.st_mtime));
2207 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2209 struct stat file_status;
2211 if (timestamp_string == NULL)
2214 if (stat(filename, &file_status) != 0) /* cannot stat file */
2217 return (file_status.st_mtime != atoi(timestamp_string));
2220 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2222 char *identifier = level_node->subdir;
2223 char *type_string = ARTWORK_DIRECTORY(type);
2224 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2225 char *token_main = getCacheToken(token_prefix, "CACHED");
2226 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2227 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2228 TreeInfo *artwork_info = NULL;
2230 if (!use_artworkinfo_cache)
2237 artwork_info = newTreeInfo();
2238 setTreeInfoToDefaults(artwork_info, type);
2240 /* set all structure fields according to the token/value pairs */
2241 ldi = *artwork_info;
2242 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2244 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2245 char *value = getHashEntry(artworkinfo_cache_old, token);
2247 setSetupInfo(artworkinfo_tokens, i, value);
2249 /* check if cache entry for this item is invalid or incomplete */
2253 printf("::: - WARNING: cache entry '%s' invalid\n", token);
2259 *artwork_info = ldi;
2264 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2265 LEVELINFO_FILENAME);
2266 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2267 ARTWORKINFO_FILENAME(type));
2269 /* check if corresponding "levelinfo.conf" file has changed */
2270 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2271 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2273 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2276 /* check if corresponding "<artworkinfo>.conf" file has changed */
2277 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2278 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2280 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2285 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2288 checked_free(filename_levelinfo);
2289 checked_free(filename_artworkinfo);
2292 if (!cached && artwork_info != NULL)
2294 freeTreeInfo(artwork_info);
2299 return artwork_info;
2302 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2303 LevelDirTree *level_node, int type)
2305 char *identifier = level_node->subdir;
2306 char *type_string = ARTWORK_DIRECTORY(type);
2307 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2308 char *token_main = getCacheToken(token_prefix, "CACHED");
2309 boolean set_cache_timestamps = TRUE;
2312 setHashEntry(artworkinfo_cache_new, token_main, "true");
2314 if (set_cache_timestamps)
2316 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2317 LEVELINFO_FILENAME);
2318 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2319 ARTWORKINFO_FILENAME(type));
2320 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2321 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2323 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2324 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2326 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2327 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2329 checked_free(filename_levelinfo);
2330 checked_free(filename_artworkinfo);
2331 checked_free(timestamp_levelinfo);
2332 checked_free(timestamp_artworkinfo);
2335 ldi = *artwork_info;
2336 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2338 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2339 char *value = getSetupValue(artworkinfo_tokens[i].type,
2340 artworkinfo_tokens[i].value);
2342 setHashEntry(artworkinfo_cache_new, token, value);
2347 /* -------------------------------------------------------------------------- */
2348 /* functions for loading level info and custom artwork info */
2349 /* -------------------------------------------------------------------------- */
2351 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2352 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2354 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2355 TreeInfo *node_parent,
2356 char *level_directory,
2357 char *directory_name)
2359 char *directory_path = getPath2(level_directory, directory_name);
2360 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2361 SetupFileHash *setup_file_hash;
2362 LevelDirTree *leveldir_new = NULL;
2365 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2366 if (!options.debug && !fileExists(filename))
2368 free(directory_path);
2374 setup_file_hash = loadSetupFileHash(filename);
2376 if (setup_file_hash == NULL)
2378 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2380 free(directory_path);
2386 leveldir_new = newTreeInfo();
2389 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2391 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2393 leveldir_new->subdir = getStringCopy(directory_name);
2395 checkSetupFileHashIdentifier(setup_file_hash, filename,
2396 getCookie("LEVELINFO"));
2398 /* set all structure fields according to the token/value pairs */
2399 ldi = *leveldir_new;
2400 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2401 setSetupInfo(levelinfo_tokens, i,
2402 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2403 *leveldir_new = ldi;
2405 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2406 setString(&leveldir_new->name, leveldir_new->subdir);
2408 if (leveldir_new->identifier == NULL)
2409 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2411 if (leveldir_new->name_sorting == NULL)
2412 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2414 if (node_parent == NULL) /* top level group */
2416 leveldir_new->basepath = getStringCopy(level_directory);
2417 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2419 else /* sub level group */
2421 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2422 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2426 if (leveldir_new->levels < 1)
2427 leveldir_new->levels = 1;
2430 leveldir_new->last_level =
2431 leveldir_new->first_level + leveldir_new->levels - 1;
2433 leveldir_new->in_user_dir =
2434 (!strEqual(leveldir_new->basepath, options.level_directory));
2436 /* adjust some settings if user's private level directory was detected */
2437 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2438 leveldir_new->in_user_dir &&
2439 (strEqual(leveldir_new->subdir, getLoginName()) ||
2440 strEqual(leveldir_new->name, getLoginName()) ||
2441 strEqual(leveldir_new->author, getRealName())))
2443 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2444 leveldir_new->readonly = FALSE;
2447 leveldir_new->user_defined =
2448 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2450 leveldir_new->color = LEVELCOLOR(leveldir_new);
2452 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2454 leveldir_new->handicap_level = /* set handicap to default value */
2455 (leveldir_new->user_defined || !leveldir_new->handicap ?
2456 leveldir_new->last_level : leveldir_new->first_level);
2458 if (leveldir_new->level_group)
2459 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2462 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2464 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2466 /* skip level sets without levels (which are probably artwork base sets) */
2468 freeSetupFileHash(setup_file_hash);
2469 free(directory_path);
2477 pushTreeInfo(node_first, leveldir_new);
2479 freeSetupFileHash(setup_file_hash);
2481 if (leveldir_new->level_group)
2483 /* create node to link back to current level directory */
2484 createParentTreeInfoNode(leveldir_new);
2486 /* recursively step into sub-directory and look for more level series */
2487 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2488 leveldir_new, directory_path);
2491 free(directory_path);
2497 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2498 TreeInfo *node_parent,
2499 char *level_directory)
2502 struct dirent *dir_entry;
2503 boolean valid_entry_found = FALSE;
2505 if ((dir = opendir(level_directory)) == NULL)
2507 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2511 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2513 struct stat file_status;
2514 char *directory_name = dir_entry->d_name;
2515 char *directory_path = getPath2(level_directory, directory_name);
2517 /* skip entries for current and parent directory */
2518 if (strEqual(directory_name, ".") ||
2519 strEqual(directory_name, ".."))
2521 free(directory_path);
2525 /* find out if directory entry is itself a directory */
2526 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2527 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2529 free(directory_path);
2533 free(directory_path);
2535 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2536 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2537 strEqual(directory_name, MUSIC_DIRECTORY))
2540 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2547 /* special case: top level directory may directly contain "levelinfo.conf" */
2548 if (node_parent == NULL && !valid_entry_found)
2550 /* check if this directory directly contains a file "levelinfo.conf" */
2551 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2552 level_directory, ".");
2555 if (!valid_entry_found)
2556 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2560 boolean AdjustGraphicsForEMC()
2562 boolean settings_changed = FALSE;
2564 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2565 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2567 return settings_changed;
2570 void LoadLevelInfo()
2572 InitUserLevelDirectory(getLoginName());
2574 DrawInitText("Loading level series", 120, FC_GREEN);
2576 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2577 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2579 /* after loading all level set information, clone the level directory tree
2580 and remove all level sets without levels (these may still contain artwork
2581 to be offered in the setup menu as "custom artwork", and are therefore
2582 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2583 leveldir_first_all = leveldir_first;
2584 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2586 AdjustGraphicsForEMC();
2588 /* before sorting, the first entries will be from the user directory */
2589 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2591 if (leveldir_first == NULL)
2592 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2594 sortTreeInfo(&leveldir_first);
2597 dumpTreeInfo(leveldir_first, 0);
2601 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2602 TreeInfo *node_parent,
2603 char *base_directory,
2604 char *directory_name, int type)
2606 char *directory_path = getPath2(base_directory, directory_name);
2607 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2608 SetupFileHash *setup_file_hash = NULL;
2609 TreeInfo *artwork_new = NULL;
2612 if (fileExists(filename))
2613 setup_file_hash = loadSetupFileHash(filename);
2615 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2618 struct dirent *dir_entry;
2619 boolean valid_file_found = FALSE;
2621 if ((dir = opendir(directory_path)) != NULL)
2623 while ((dir_entry = readdir(dir)) != NULL)
2625 char *entry_name = dir_entry->d_name;
2627 if (FileIsArtworkType(entry_name, type))
2629 valid_file_found = TRUE;
2637 if (!valid_file_found)
2639 if (!strEqual(directory_name, "."))
2640 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2642 free(directory_path);
2649 artwork_new = newTreeInfo();
2652 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2654 setTreeInfoToDefaults(artwork_new, type);
2656 artwork_new->subdir = getStringCopy(directory_name);
2658 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2661 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2664 /* set all structure fields according to the token/value pairs */
2666 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2667 setSetupInfo(levelinfo_tokens, i,
2668 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2671 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2672 setString(&artwork_new->name, artwork_new->subdir);
2674 if (artwork_new->identifier == NULL)
2675 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2677 if (artwork_new->name_sorting == NULL)
2678 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2681 if (node_parent == NULL) /* top level group */
2683 artwork_new->basepath = getStringCopy(base_directory);
2684 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2686 else /* sub level group */
2688 artwork_new->basepath = getStringCopy(node_parent->basepath);
2689 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2692 artwork_new->in_user_dir =
2693 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2695 /* (may use ".sort_priority" from "setup_file_hash" above) */
2696 artwork_new->color = ARTWORKCOLOR(artwork_new);
2698 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2700 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2702 if (strEqual(artwork_new->subdir, "."))
2704 if (artwork_new->user_defined)
2706 setString(&artwork_new->identifier, "private");
2707 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2711 setString(&artwork_new->identifier, "classic");
2712 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2715 /* set to new values after changing ".sort_priority" */
2716 artwork_new->color = ARTWORKCOLOR(artwork_new);
2718 setString(&artwork_new->class_desc,
2719 getLevelClassDescription(artwork_new));
2723 setString(&artwork_new->identifier, artwork_new->subdir);
2726 setString(&artwork_new->name, artwork_new->identifier);
2727 setString(&artwork_new->name_sorting, artwork_new->name);
2731 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2734 pushTreeInfo(node_first, artwork_new);
2736 freeSetupFileHash(setup_file_hash);
2738 free(directory_path);
2744 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2745 TreeInfo *node_parent,
2746 char *base_directory, int type)
2749 struct dirent *dir_entry;
2750 boolean valid_entry_found = FALSE;
2752 if ((dir = opendir(base_directory)) == NULL)
2754 /* display error if directory is main "options.graphics_directory" etc. */
2755 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2756 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2761 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2763 struct stat file_status;
2764 char *directory_name = dir_entry->d_name;
2765 char *directory_path = getPath2(base_directory, directory_name);
2767 /* skip directory entries for current and parent directory */
2768 if (strEqual(directory_name, ".") ||
2769 strEqual(directory_name, ".."))
2771 free(directory_path);
2775 /* skip directory entries which are not a directory or are not accessible */
2776 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2777 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2779 free(directory_path);
2783 free(directory_path);
2785 /* check if this directory contains artwork with or without config file */
2786 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2788 directory_name, type);
2793 /* check if this directory directly contains artwork itself */
2794 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2795 base_directory, ".",
2797 if (!valid_entry_found)
2798 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2802 static TreeInfo *getDummyArtworkInfo(int type)
2804 /* this is only needed when there is completely no artwork available */
2805 TreeInfo *artwork_new = newTreeInfo();
2807 setTreeInfoToDefaults(artwork_new, type);
2809 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
2810 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2811 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2813 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
2814 setString(&artwork_new->name, UNDEFINED_FILENAME);
2815 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2820 void LoadArtworkInfo()
2822 LoadArtworkInfoCache();
2824 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
2826 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2827 options.graphics_directory,
2828 TREE_TYPE_GRAPHICS_DIR);
2829 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2830 getUserGraphicsDir(),
2831 TREE_TYPE_GRAPHICS_DIR);
2833 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2834 options.sounds_directory,
2835 TREE_TYPE_SOUNDS_DIR);
2836 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2838 TREE_TYPE_SOUNDS_DIR);
2840 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2841 options.music_directory,
2842 TREE_TYPE_MUSIC_DIR);
2843 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2845 TREE_TYPE_MUSIC_DIR);
2847 if (artwork.gfx_first == NULL)
2848 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2849 if (artwork.snd_first == NULL)
2850 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2851 if (artwork.mus_first == NULL)
2852 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2854 /* before sorting, the first entries will be from the user directory */
2855 artwork.gfx_current =
2856 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2857 if (artwork.gfx_current == NULL)
2858 artwork.gfx_current =
2859 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2860 if (artwork.gfx_current == NULL)
2861 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2863 artwork.snd_current =
2864 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2865 if (artwork.snd_current == NULL)
2866 artwork.snd_current =
2867 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2868 if (artwork.snd_current == NULL)
2869 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2871 artwork.mus_current =
2872 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2873 if (artwork.mus_current == NULL)
2874 artwork.mus_current =
2875 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2876 if (artwork.mus_current == NULL)
2877 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2879 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2880 artwork.snd_current_identifier = artwork.snd_current->identifier;
2881 artwork.mus_current_identifier = artwork.mus_current->identifier;
2884 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2885 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2886 printf("music set == %s\n\n", artwork.mus_current_identifier);
2889 sortTreeInfo(&artwork.gfx_first);
2890 sortTreeInfo(&artwork.snd_first);
2891 sortTreeInfo(&artwork.mus_first);
2894 dumpTreeInfo(artwork.gfx_first, 0);
2895 dumpTreeInfo(artwork.snd_first, 0);
2896 dumpTreeInfo(artwork.mus_first, 0);
2900 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2901 LevelDirTree *level_node)
2903 int type = (*artwork_node)->type;
2905 /* recursively check all level directories for artwork sub-directories */
2909 /* check all tree entries for artwork, but skip parent link entries */
2910 if (!level_node->parent_link)
2912 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
2913 boolean cached = (artwork_new != NULL);
2917 pushTreeInfo(artwork_node, artwork_new);
2921 TreeInfo *topnode_last = *artwork_node;
2922 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2923 ARTWORK_DIRECTORY(type));
2925 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
2927 if (topnode_last != *artwork_node) /* check for newly added node */
2929 artwork_new = *artwork_node;
2931 setString(&artwork_new->identifier, level_node->subdir);
2932 setString(&artwork_new->name, level_node->name);
2933 setString(&artwork_new->name_sorting, level_node->name_sorting);
2935 artwork_new->sort_priority = level_node->sort_priority;
2936 artwork_new->color = LEVELCOLOR(artwork_new);
2942 /* insert artwork info (from old cache or filesystem) into new cache */
2943 if (artwork_new != NULL)
2944 setArtworkInfoCacheEntry(artwork_new, level_node, type);
2948 if (level_node->level_group)
2949 DrawInitText(level_node->name, 150, FC_YELLOW);
2952 if (level_node->node_group != NULL)
2953 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2955 level_node = level_node->next;
2959 void LoadLevelArtworkInfo()
2961 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
2963 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
2964 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
2965 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
2967 SaveArtworkInfoCache();
2969 /* needed for reloading level artwork not known at ealier stage */
2971 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
2973 artwork.gfx_current =
2974 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2975 if (artwork.gfx_current == NULL)
2976 artwork.gfx_current =
2977 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2978 if (artwork.gfx_current == NULL)
2979 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2982 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
2984 artwork.snd_current =
2985 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2986 if (artwork.snd_current == NULL)
2987 artwork.snd_current =
2988 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2989 if (artwork.snd_current == NULL)
2990 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2993 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
2995 artwork.mus_current =
2996 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2997 if (artwork.mus_current == NULL)
2998 artwork.mus_current =
2999 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3000 if (artwork.mus_current == NULL)
3001 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3004 sortTreeInfo(&artwork.gfx_first);
3005 sortTreeInfo(&artwork.snd_first);
3006 sortTreeInfo(&artwork.mus_first);
3009 dumpTreeInfo(artwork.gfx_first, 0);
3010 dumpTreeInfo(artwork.snd_first, 0);
3011 dumpTreeInfo(artwork.mus_first, 0);
3015 static void SaveUserLevelInfo()
3017 LevelDirTree *level_info;
3022 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3024 if (!(file = fopen(filename, MODE_WRITE)))
3026 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3031 level_info = newTreeInfo();
3033 /* always start with reliable default values */
3034 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3036 setString(&level_info->name, getLoginName());
3037 setString(&level_info->author, getRealName());
3038 level_info->levels = 100;
3039 level_info->first_level = 1;
3041 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3043 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3044 getCookie("LEVELINFO")));
3047 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3049 if (i == LEVELINFO_TOKEN_NAME ||
3050 i == LEVELINFO_TOKEN_AUTHOR ||
3051 i == LEVELINFO_TOKEN_LEVELS ||
3052 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3053 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3055 /* just to make things nicer :) */
3056 if (i == LEVELINFO_TOKEN_AUTHOR)
3057 fprintf(file, "\n");
3060 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3064 SetFilePermissions(filename, PERMS_PRIVATE);
3066 freeTreeInfo(level_info);
3070 char *getSetupValue(int type, void *value)
3072 static char value_string[MAX_LINE_LEN];
3080 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3084 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3088 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3092 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3096 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3100 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3104 sprintf(value_string, "%d", *(int *)value);
3108 if (*(char **)value == NULL)
3111 strcpy(value_string, *(char **)value);
3115 value_string[0] = '\0';
3119 if (type & TYPE_GHOSTED)
3120 strcpy(value_string, "n/a");
3122 return value_string;
3125 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3129 static char token_string[MAX_LINE_LEN];
3130 int token_type = token_info[token_nr].type;
3131 void *setup_value = token_info[token_nr].value;
3132 char *token_text = token_info[token_nr].text;
3133 char *value_string = getSetupValue(token_type, setup_value);
3135 /* build complete token string */
3136 sprintf(token_string, "%s%s", prefix, token_text);
3138 /* build setup entry line */
3139 line = getFormattedSetupEntry(token_string, value_string);
3141 if (token_type == TYPE_KEY_X11)
3143 Key key = *(Key *)setup_value;
3144 char *keyname = getKeyNameFromKey(key);
3146 /* add comment, if useful */
3147 if (!strEqual(keyname, "(undefined)") &&
3148 !strEqual(keyname, "(unknown)"))
3150 /* add at least one whitespace */
3152 for (i = strlen(line); i < token_comment_position; i++)
3156 strcat(line, keyname);
3163 void LoadLevelSetup_LastSeries()
3165 /* ----------------------------------------------------------------------- */
3166 /* ~/.<program>/levelsetup.conf */
3167 /* ----------------------------------------------------------------------- */
3169 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3170 SetupFileHash *level_setup_hash = NULL;
3172 /* always start with reliable default values */
3173 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3175 if ((level_setup_hash = loadSetupFileHash(filename)))
3177 char *last_level_series =
3178 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3180 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3182 if (leveldir_current == NULL)
3183 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3185 checkSetupFileHashIdentifier(level_setup_hash, filename,
3186 getCookie("LEVELSETUP"));
3188 freeSetupFileHash(level_setup_hash);
3191 Error(ERR_WARN, "using default setup values");
3196 void SaveLevelSetup_LastSeries()
3198 /* ----------------------------------------------------------------------- */
3199 /* ~/.<program>/levelsetup.conf */
3200 /* ----------------------------------------------------------------------- */
3202 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3203 char *level_subdir = leveldir_current->subdir;
3206 InitUserDataDirectory();
3208 if (!(file = fopen(filename, MODE_WRITE)))
3210 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3215 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3216 getCookie("LEVELSETUP")));
3217 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3222 SetFilePermissions(filename, PERMS_PRIVATE);
3227 static void checkSeriesInfo()
3229 static char *level_directory = NULL;
3231 struct dirent *dir_entry;
3233 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3235 level_directory = getPath2((leveldir_current->in_user_dir ?
3236 getUserLevelDir(NULL) :
3237 options.level_directory),
3238 leveldir_current->fullpath);
3240 if ((dir = opendir(level_directory)) == NULL)
3242 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3246 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3248 if (strlen(dir_entry->d_name) > 4 &&
3249 dir_entry->d_name[3] == '.' &&
3250 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3252 char levelnum_str[4];
3255 strncpy(levelnum_str, dir_entry->d_name, 3);
3256 levelnum_str[3] = '\0';
3258 levelnum_value = atoi(levelnum_str);
3261 if (levelnum_value < leveldir_current->first_level)
3263 Error(ERR_WARN, "additional level %d found", levelnum_value);
3264 leveldir_current->first_level = levelnum_value;
3266 else if (levelnum_value > leveldir_current->last_level)
3268 Error(ERR_WARN, "additional level %d found", levelnum_value);
3269 leveldir_current->last_level = levelnum_value;
3278 void LoadLevelSetup_SeriesInfo()
3281 SetupFileHash *level_setup_hash = NULL;
3282 char *level_subdir = leveldir_current->subdir;
3284 /* always start with reliable default values */
3285 level_nr = leveldir_current->first_level;
3287 checkSeriesInfo(leveldir_current);
3289 /* ----------------------------------------------------------------------- */
3290 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3291 /* ----------------------------------------------------------------------- */
3293 level_subdir = leveldir_current->subdir;
3295 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3297 if ((level_setup_hash = loadSetupFileHash(filename)))
3301 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3305 level_nr = atoi(token_value);
3307 if (level_nr < leveldir_current->first_level)
3308 level_nr = leveldir_current->first_level;
3309 if (level_nr > leveldir_current->last_level)
3310 level_nr = leveldir_current->last_level;
3313 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3317 int level_nr = atoi(token_value);
3319 if (level_nr < leveldir_current->first_level)
3320 level_nr = leveldir_current->first_level;
3321 if (level_nr > leveldir_current->last_level + 1)
3322 level_nr = leveldir_current->last_level;
3324 if (leveldir_current->user_defined || !leveldir_current->handicap)
3325 level_nr = leveldir_current->last_level;
3327 leveldir_current->handicap_level = level_nr;
3330 checkSetupFileHashIdentifier(level_setup_hash, filename,
3331 getCookie("LEVELSETUP"));
3333 freeSetupFileHash(level_setup_hash);
3336 Error(ERR_WARN, "using default setup values");
3341 void SaveLevelSetup_SeriesInfo()
3344 char *level_subdir = leveldir_current->subdir;
3345 char *level_nr_str = int2str(level_nr, 0);
3346 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3349 /* ----------------------------------------------------------------------- */
3350 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3351 /* ----------------------------------------------------------------------- */
3353 InitLevelSetupDirectory(level_subdir);
3355 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3357 if (!(file = fopen(filename, MODE_WRITE)))
3359 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3364 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3365 getCookie("LEVELSETUP")));
3366 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3368 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3369 handicap_level_str));
3373 SetFilePermissions(filename, PERMS_PRIVATE);