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
89 static void setTreeInfoToDefaults(TreeInfo *, int);
90 static int compareTreeInfoEntries(const void *, const void *);
92 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
93 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
95 static SetupFileHash *level_artwork_info_hash = NULL;
98 /* ------------------------------------------------------------------------- */
100 /* ------------------------------------------------------------------------- */
102 static char *getLevelClassDescription(TreeInfo *ti)
104 int position = ti->sort_priority / 100;
106 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
107 return levelclass_desc[position];
109 return "Unknown Level Class";
112 static char *getUserLevelDir(char *level_subdir)
114 static char *userlevel_dir = NULL;
115 char *data_dir = getUserGameDataDir();
116 char *userlevel_subdir = LEVELS_DIRECTORY;
118 checked_free(userlevel_dir);
120 if (level_subdir != NULL)
121 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
123 userlevel_dir = getPath2(data_dir, userlevel_subdir);
125 return userlevel_dir;
128 static char *getScoreDir(char *level_subdir)
130 static char *score_dir = NULL;
131 char *data_dir = getCommonDataDir();
132 char *score_subdir = SCORES_DIRECTORY;
134 checked_free(score_dir);
136 if (level_subdir != NULL)
137 score_dir = getPath3(data_dir, score_subdir, level_subdir);
139 score_dir = getPath2(data_dir, score_subdir);
144 static char *getLevelSetupDir(char *level_subdir)
146 static char *levelsetup_dir = NULL;
147 char *data_dir = getUserGameDataDir();
148 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
150 checked_free(levelsetup_dir);
152 if (level_subdir != NULL)
153 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
155 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
157 return levelsetup_dir;
160 static char *getLevelDirFromTreeInfo(TreeInfo *node)
162 static char *level_dir = NULL;
165 return options.level_directory;
167 checked_free(level_dir);
169 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
170 options.level_directory), node->fullpath);
175 char *getCurrentLevelDir()
177 return getLevelDirFromTreeInfo(leveldir_current);
180 static char *getTapeDir(char *level_subdir)
182 static char *tape_dir = NULL;
183 char *data_dir = getUserGameDataDir();
184 char *tape_subdir = TAPES_DIRECTORY;
186 checked_free(tape_dir);
188 if (level_subdir != NULL)
189 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
191 tape_dir = getPath2(data_dir, tape_subdir);
196 static char *getSolutionTapeDir()
198 static char *tape_dir = NULL;
199 char *data_dir = getCurrentLevelDir();
200 char *tape_subdir = TAPES_DIRECTORY;
202 checked_free(tape_dir);
204 tape_dir = getPath2(data_dir, tape_subdir);
209 static char *getDefaultGraphicsDir(char *graphics_subdir)
211 static char *graphics_dir = NULL;
213 if (graphics_subdir == NULL)
214 return options.graphics_directory;
216 checked_free(graphics_dir);
218 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
223 static char *getDefaultSoundsDir(char *sounds_subdir)
225 static char *sounds_dir = NULL;
227 if (sounds_subdir == NULL)
228 return options.sounds_directory;
230 checked_free(sounds_dir);
232 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
237 static char *getDefaultMusicDir(char *music_subdir)
239 static char *music_dir = NULL;
241 if (music_subdir == NULL)
242 return options.music_directory;
244 checked_free(music_dir);
246 music_dir = getPath2(options.music_directory, music_subdir);
251 static char *getDefaultArtworkSet(int type)
253 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
254 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
255 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
258 static char *getDefaultArtworkDir(int type)
260 return (type == TREE_TYPE_GRAPHICS_DIR ?
261 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
262 type == TREE_TYPE_SOUNDS_DIR ?
263 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
264 type == TREE_TYPE_MUSIC_DIR ?
265 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
268 static char *getUserGraphicsDir()
270 static char *usergraphics_dir = NULL;
272 if (usergraphics_dir == NULL)
273 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
275 return usergraphics_dir;
278 static char *getUserSoundsDir()
280 static char *usersounds_dir = NULL;
282 if (usersounds_dir == NULL)
283 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
285 return usersounds_dir;
288 static char *getUserMusicDir()
290 static char *usermusic_dir = NULL;
292 if (usermusic_dir == NULL)
293 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
295 return usermusic_dir;
298 static char *getSetupArtworkDir(TreeInfo *ti)
300 static char *artwork_dir = NULL;
302 checked_free(artwork_dir);
304 artwork_dir = getPath2(ti->basepath, ti->fullpath);
309 char *setLevelArtworkDir(TreeInfo *ti)
311 char **artwork_path_ptr, **artwork_set_ptr;
312 TreeInfo *level_artwork;
314 if (ti == NULL || leveldir_current == NULL)
317 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
318 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
320 checked_free(*artwork_path_ptr);
322 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
323 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
326 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
327 normally result in using the artwork configured in the setup menu. But
328 if an artwork subdirectory exists (which might contain custom artwork
329 or an artwork configuration file), this level artwork must be treated
330 as relative to the default "classic" artwork, not to the artwork that
331 is currently configured in the setup menu. */
333 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
335 checked_free(*artwork_set_ptr);
339 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
340 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
344 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
345 *artwork_set_ptr = NULL;
351 return *artwork_set_ptr;
354 inline static char *getLevelArtworkSet(int type)
356 if (leveldir_current == NULL)
359 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
362 inline static char *getLevelArtworkDir(int type)
364 if (leveldir_current == NULL)
365 return UNDEFINED_FILENAME;
367 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
370 char *getTapeFilename(int nr)
372 static char *filename = NULL;
373 char basename[MAX_FILENAME_LEN];
375 checked_free(filename);
377 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
378 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
383 char *getSolutionTapeFilename(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(getSolutionTapeDir(), basename);
396 char *getScoreFilename(int nr)
398 static char *filename = NULL;
399 char basename[MAX_FILENAME_LEN];
401 checked_free(filename);
403 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
404 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
409 char *getSetupFilename()
411 static char *filename = NULL;
413 checked_free(filename);
415 filename = getPath2(getSetupDir(), SETUP_FILENAME);
420 char *getEditorSetupFilename()
422 static char *filename = NULL;
424 checked_free(filename);
425 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
427 if (fileExists(filename))
430 checked_free(filename);
431 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
436 char *getHelpAnimFilename()
438 static char *filename = NULL;
440 checked_free(filename);
442 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
447 char *getHelpTextFilename()
449 static char *filename = NULL;
451 checked_free(filename);
453 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
458 char *getLevelSetInfoFilename()
460 static char *filename = NULL;
475 for (i = 0; basenames[i] != NULL; i++)
477 checked_free(filename);
478 filename = getPath2(getCurrentLevelDir(), basenames[i]);
480 if (fileExists(filename))
487 static char *getCorrectedArtworkBasename(char *basename)
489 char *basename_corrected = basename;
491 #if defined(PLATFORM_MSDOS)
492 if (program.filename_prefix != NULL)
494 int prefix_len = strlen(program.filename_prefix);
496 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
497 basename_corrected = &basename[prefix_len];
499 /* if corrected filename is still longer than standard MS-DOS filename
500 size (8 characters + 1 dot + 3 characters file extension), shorten
501 filename by writing file extension after 8th basename character */
502 if (strlen(basename_corrected) > 8 + 1 + 3)
504 static char *msdos_filename = NULL;
506 checked_free(msdos_filename);
508 msdos_filename = getStringCopy(basename_corrected);
509 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
511 basename_corrected = msdos_filename;
516 return basename_corrected;
519 char *getCustomImageFilename(char *basename)
521 static char *filename = NULL;
522 boolean skip_setup_artwork = FALSE;
524 checked_free(filename);
526 basename = getCorrectedArtworkBasename(basename);
528 if (!setup.override_level_graphics)
530 /* 1st try: look for special artwork in current level series directory */
531 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
532 if (fileExists(filename))
537 /* check if there is special artwork configured in level series config */
538 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
540 /* 2nd try: look for special artwork configured in level series config */
541 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
542 if (fileExists(filename))
547 /* take missing artwork configured in level set config from default */
548 skip_setup_artwork = TRUE;
552 if (!skip_setup_artwork)
554 /* 3rd try: look for special artwork in configured artwork directory */
555 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
556 if (fileExists(filename))
562 /* 4th try: look for default artwork in new default artwork directory */
563 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
564 if (fileExists(filename))
569 /* 5th try: look for default artwork in old default artwork directory */
570 filename = getPath2(options.graphics_directory, basename);
571 if (fileExists(filename))
574 return NULL; /* cannot find specified artwork file anywhere */
577 char *getCustomSoundFilename(char *basename)
579 static char *filename = NULL;
580 boolean skip_setup_artwork = FALSE;
582 checked_free(filename);
584 basename = getCorrectedArtworkBasename(basename);
586 if (!setup.override_level_sounds)
588 /* 1st try: look for special artwork in current level series directory */
589 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
590 if (fileExists(filename))
595 /* check if there is special artwork configured in level series config */
596 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
598 /* 2nd try: look for special artwork configured in level series config */
599 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
600 if (fileExists(filename))
605 /* take missing artwork configured in level set config from default */
606 skip_setup_artwork = TRUE;
610 if (!skip_setup_artwork)
612 /* 3rd try: look for special artwork in configured artwork directory */
613 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
614 if (fileExists(filename))
620 /* 4th try: look for default artwork in new default artwork directory */
621 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
622 if (fileExists(filename))
627 /* 5th try: look for default artwork in old default artwork directory */
628 filename = getPath2(options.sounds_directory, basename);
629 if (fileExists(filename))
632 return NULL; /* cannot find specified artwork file anywhere */
635 char *getCustomMusicFilename(char *basename)
637 static char *filename = NULL;
638 boolean skip_setup_artwork = FALSE;
640 checked_free(filename);
642 basename = getCorrectedArtworkBasename(basename);
644 if (!setup.override_level_music)
646 /* 1st try: look for special artwork in current level series directory */
647 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
648 if (fileExists(filename))
653 /* check if there is special artwork configured in level series config */
654 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
656 /* 2nd try: look for special artwork configured in level series config */
657 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
658 if (fileExists(filename))
663 /* take missing artwork configured in level set config from default */
664 skip_setup_artwork = TRUE;
668 if (!skip_setup_artwork)
670 /* 3rd try: look for special artwork in configured artwork directory */
671 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
672 if (fileExists(filename))
678 /* 4th try: look for default artwork in new default artwork directory */
679 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
680 if (fileExists(filename))
685 /* 5th try: look for default artwork in old default artwork directory */
686 filename = getPath2(options.music_directory, basename);
687 if (fileExists(filename))
690 return NULL; /* cannot find specified artwork file anywhere */
693 char *getCustomArtworkFilename(char *basename, int type)
695 if (type == ARTWORK_TYPE_GRAPHICS)
696 return getCustomImageFilename(basename);
697 else if (type == ARTWORK_TYPE_SOUNDS)
698 return getCustomSoundFilename(basename);
699 else if (type == ARTWORK_TYPE_MUSIC)
700 return getCustomMusicFilename(basename);
702 return UNDEFINED_FILENAME;
705 char *getCustomArtworkConfigFilename(int type)
707 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
710 char *getCustomArtworkLevelConfigFilename(int type)
712 static char *filename = NULL;
714 checked_free(filename);
716 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
721 char *getCustomMusicDirectory(void)
723 static char *directory = NULL;
724 boolean skip_setup_artwork = FALSE;
726 checked_free(directory);
728 if (!setup.override_level_music)
730 /* 1st try: look for special artwork in current level series directory */
731 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
732 if (fileExists(directory))
737 /* check if there is special artwork configured in level series config */
738 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
740 /* 2nd try: look for special artwork configured in level series config */
741 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
742 if (fileExists(directory))
747 /* take missing artwork configured in level set config from default */
748 skip_setup_artwork = TRUE;
752 if (!skip_setup_artwork)
754 /* 3rd try: look for special artwork in configured artwork directory */
755 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
756 if (fileExists(directory))
762 /* 4th try: look for default artwork in new default artwork directory */
763 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
764 if (fileExists(directory))
769 /* 5th try: look for default artwork in old default artwork directory */
770 directory = getStringCopy(options.music_directory);
771 if (fileExists(directory))
774 return NULL; /* cannot find specified artwork file anywhere */
777 void InitTapeDirectory(char *level_subdir)
779 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
780 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
781 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
784 void InitScoreDirectory(char *level_subdir)
786 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
787 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
788 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
791 static void SaveUserLevelInfo();
793 void InitUserLevelDirectory(char *level_subdir)
795 if (!fileExists(getUserLevelDir(level_subdir)))
797 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
798 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
799 createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
805 void InitLevelSetupDirectory(char *level_subdir)
807 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
808 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
809 createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
813 /* ------------------------------------------------------------------------- */
814 /* some functions to handle lists of level and artwork directories */
815 /* ------------------------------------------------------------------------- */
817 TreeInfo *newTreeInfo()
819 return checked_calloc(sizeof(TreeInfo));
822 TreeInfo *newTreeInfo_setDefaults(int type)
824 TreeInfo *ti = newTreeInfo();
826 setTreeInfoToDefaults(ti, type);
831 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
833 node_new->next = *node_first;
834 *node_first = node_new;
837 int numTreeInfo(TreeInfo *node)
850 boolean validLevelSeries(TreeInfo *node)
852 return (node != NULL && !node->node_group && !node->parent_link);
855 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
860 if (node->node_group) /* enter level group (step down into tree) */
861 return getFirstValidTreeInfoEntry(node->node_group);
862 else if (node->parent_link) /* skip start entry of level group */
864 if (node->next) /* get first real level series entry */
865 return getFirstValidTreeInfoEntry(node->next);
866 else /* leave empty level group and go on */
867 return getFirstValidTreeInfoEntry(node->node_parent->next);
869 else /* this seems to be a regular level series */
873 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
878 if (node->node_parent == NULL) /* top level group */
879 return *node->node_top;
880 else /* sub level group */
881 return node->node_parent->node_group;
884 int numTreeInfoInGroup(TreeInfo *node)
886 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
889 int posTreeInfo(TreeInfo *node)
891 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
896 if (node_cmp == node)
900 node_cmp = node_cmp->next;
906 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
908 TreeInfo *node_default = node;
923 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
925 if (identifier == NULL)
930 if (node->node_group)
932 TreeInfo *node_group;
934 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
939 else if (!node->parent_link)
941 if (strEqual(identifier, node->identifier))
951 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
952 TreeInfo *node, boolean skip_sets_without_levels)
959 if (!node->parent_link && !node->level_group &&
960 skip_sets_without_levels && node->levels == 0)
961 return cloneTreeNode(node_top, node_parent, node->next,
962 skip_sets_without_levels);
964 node_new = newTreeInfo();
966 *node_new = *node; /* copy complete node */
968 node_new->node_top = node_top; /* correct top node link */
969 node_new->node_parent = node_parent; /* correct parent node link */
971 if (node->level_group)
972 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
973 skip_sets_without_levels);
975 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
976 skip_sets_without_levels);
981 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
983 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
988 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
990 boolean settings_changed = FALSE;
994 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
995 !strEqual(node->graphics_set, node->graphics_set_ecs))
997 setString(&node->graphics_set, node->graphics_set_ecs);
998 settings_changed = TRUE;
1000 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1001 !strEqual(node->graphics_set, node->graphics_set_aga))
1003 setString(&node->graphics_set, node->graphics_set_aga);
1004 settings_changed = TRUE;
1007 if (node->node_group != NULL)
1008 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1013 return settings_changed;
1016 void dumpTreeInfo(TreeInfo *node, int depth)
1020 printf("Dumping TreeInfo:\n");
1024 for (i = 0; i < (depth + 1) * 3; i++)
1027 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1028 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1030 if (node->node_group != NULL)
1031 dumpTreeInfo(node->node_group, depth + 1);
1037 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1038 int (*compare_function)(const void *,
1041 int num_nodes = numTreeInfo(*node_first);
1042 TreeInfo **sort_array;
1043 TreeInfo *node = *node_first;
1049 /* allocate array for sorting structure pointers */
1050 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1052 /* writing structure pointers to sorting array */
1053 while (i < num_nodes && node) /* double boundary check... */
1055 sort_array[i] = node;
1061 /* sorting the structure pointers in the sorting array */
1062 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1065 /* update the linkage of list elements with the sorted node array */
1066 for (i = 0; i < num_nodes - 1; i++)
1067 sort_array[i]->next = sort_array[i + 1];
1068 sort_array[num_nodes - 1]->next = NULL;
1070 /* update the linkage of the main list anchor pointer */
1071 *node_first = sort_array[0];
1075 /* now recursively sort the level group structures */
1079 if (node->node_group != NULL)
1080 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1086 void sortTreeInfo(TreeInfo **node_first)
1088 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1092 /* ========================================================================= */
1093 /* some stuff from "files.c" */
1094 /* ========================================================================= */
1096 #if defined(PLATFORM_WIN32)
1098 #define S_IRGRP S_IRUSR
1101 #define S_IROTH S_IRUSR
1104 #define S_IWGRP S_IWUSR
1107 #define S_IWOTH S_IWUSR
1110 #define S_IXGRP S_IXUSR
1113 #define S_IXOTH S_IXUSR
1116 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1121 #endif /* PLATFORM_WIN32 */
1123 /* file permissions for newly written files */
1124 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1125 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1126 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1128 #define MODE_W_PRIVATE (S_IWUSR)
1129 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1130 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1132 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1133 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1135 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1136 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1140 static char *dir = NULL;
1142 #if defined(PLATFORM_WIN32)
1145 dir = checked_malloc(MAX_PATH + 1);
1147 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1150 #elif defined(PLATFORM_UNIX)
1153 if ((dir = getenv("HOME")) == NULL)
1157 if ((pwd = getpwuid(getuid())) != NULL)
1158 dir = getStringCopy(pwd->pw_dir);
1170 char *getCommonDataDir(void)
1172 static char *common_data_dir = NULL;
1174 #if defined(PLATFORM_WIN32)
1175 if (common_data_dir == NULL)
1177 char *dir = checked_malloc(MAX_PATH + 1);
1179 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1180 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1181 common_data_dir = getPath2(dir, program.userdata_subdir);
1183 common_data_dir = options.rw_base_directory;
1186 if (common_data_dir == NULL)
1187 common_data_dir = options.rw_base_directory;
1190 return common_data_dir;
1193 char *getPersonalDataDir(void)
1195 static char *personal_data_dir = NULL;
1197 #if defined(PLATFORM_MACOSX)
1198 if (personal_data_dir == NULL)
1199 personal_data_dir = getPath2(getHomeDir(), "Documents");
1201 if (personal_data_dir == NULL)
1202 personal_data_dir = getHomeDir();
1205 return personal_data_dir;
1208 char *getUserGameDataDir(void)
1210 static char *user_game_data_dir = NULL;
1212 if (user_game_data_dir == NULL)
1213 user_game_data_dir = getPath2(getPersonalDataDir(),
1214 program.userdata_subdir);
1216 return user_game_data_dir;
1219 void updateUserGameDataDir()
1221 #if defined(PLATFORM_MACOSX)
1222 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1223 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1225 /* convert old Unix style game data directory to Mac OS X style, if needed */
1226 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1228 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1230 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1231 userdata_dir_old, userdata_dir_new);
1233 /* continue using Unix style data directory -- this should not happen */
1234 program.userdata_path = getPath2(getPersonalDataDir(),
1235 program.userdata_subdir_unix);
1239 free(userdata_dir_old);
1245 return getUserGameDataDir();
1248 static mode_t posix_umask(mode_t mask)
1250 #if defined(PLATFORM_UNIX)
1257 static int posix_mkdir(const char *pathname, mode_t mode)
1259 #if defined(PLATFORM_WIN32)
1260 return mkdir(pathname);
1262 return mkdir(pathname, mode);
1266 void createDirectory(char *dir, char *text, int permission_class)
1268 /* leave "other" permissions in umask untouched, but ensure group parts
1269 of USERDATA_DIR_MODE are not masked */
1270 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1271 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1272 mode_t normal_umask = posix_umask(0);
1273 mode_t group_umask = ~(dir_mode & S_IRWXG);
1274 posix_umask(normal_umask & group_umask);
1276 if (!fileExists(dir))
1277 if (posix_mkdir(dir, dir_mode) != 0)
1278 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1280 posix_umask(normal_umask); /* reset normal umask */
1283 void InitUserDataDirectory()
1285 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1288 void SetFilePermissions(char *filename, int permission_class)
1290 chmod(filename, (permission_class == PERMS_PRIVATE ?
1291 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1294 char *getCookie(char *file_type)
1296 static char cookie[MAX_COOKIE_LEN + 1];
1298 if (strlen(program.cookie_prefix) + 1 +
1299 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1300 return "[COOKIE ERROR]"; /* should never happen */
1302 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1303 program.cookie_prefix, file_type,
1304 program.version_major, program.version_minor);
1309 int getFileVersionFromCookieString(const char *cookie)
1311 const char *ptr_cookie1, *ptr_cookie2;
1312 const char *pattern1 = "_FILE_VERSION_";
1313 const char *pattern2 = "?.?";
1314 const int len_cookie = strlen(cookie);
1315 const int len_pattern1 = strlen(pattern1);
1316 const int len_pattern2 = strlen(pattern2);
1317 const int len_pattern = len_pattern1 + len_pattern2;
1318 int version_major, version_minor;
1320 if (len_cookie <= len_pattern)
1323 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1324 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1326 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1329 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1330 ptr_cookie2[1] != '.' ||
1331 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1334 version_major = ptr_cookie2[0] - '0';
1335 version_minor = ptr_cookie2[2] - '0';
1337 return VERSION_IDENT(version_major, version_minor, 0, 0);
1340 boolean checkCookieString(const char *cookie, const char *template)
1342 const char *pattern = "_FILE_VERSION_?.?";
1343 const int len_cookie = strlen(cookie);
1344 const int len_template = strlen(template);
1345 const int len_pattern = strlen(pattern);
1347 if (len_cookie != len_template)
1350 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1356 /* ------------------------------------------------------------------------- */
1357 /* setup file list and hash handling functions */
1358 /* ------------------------------------------------------------------------- */
1360 char *getFormattedSetupEntry(char *token, char *value)
1363 static char entry[MAX_LINE_LEN];
1365 /* if value is an empty string, just return token without value */
1369 /* start with the token and some spaces to format output line */
1370 sprintf(entry, "%s:", token);
1371 for (i = strlen(entry); i < token_value_position; i++)
1374 /* continue with the token's value */
1375 strcat(entry, value);
1380 SetupFileList *newSetupFileList(char *token, char *value)
1382 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1384 new->token = getStringCopy(token);
1385 new->value = getStringCopy(value);
1392 void freeSetupFileList(SetupFileList *list)
1397 checked_free(list->token);
1398 checked_free(list->value);
1401 freeSetupFileList(list->next);
1406 char *getListEntry(SetupFileList *list, char *token)
1411 if (strEqual(list->token, token))
1414 return getListEntry(list->next, token);
1417 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1422 if (strEqual(list->token, token))
1424 checked_free(list->value);
1426 list->value = getStringCopy(value);
1430 else if (list->next == NULL)
1431 return (list->next = newSetupFileList(token, value));
1433 return setListEntry(list->next, token, value);
1436 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1441 if (list->next == NULL)
1442 return (list->next = newSetupFileList(token, value));
1444 return addListEntry(list->next, token, value);
1448 static void printSetupFileList(SetupFileList *list)
1453 printf("token: '%s'\n", list->token);
1454 printf("value: '%s'\n", list->value);
1456 printSetupFileList(list->next);
1461 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1462 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1463 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1464 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1466 #define insert_hash_entry hashtable_insert
1467 #define search_hash_entry hashtable_search
1468 #define change_hash_entry hashtable_change
1469 #define remove_hash_entry hashtable_remove
1472 static unsigned int get_hash_from_key(void *key)
1477 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1478 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1479 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1480 it works better than many other constants, prime or not) has never been
1481 adequately explained.
1483 If you just want to have a good hash function, and cannot wait, djb2
1484 is one of the best string hash functions i know. It has excellent
1485 distribution and speed on many different sets of keys and table sizes.
1486 You are not likely to do better with one of the "well known" functions
1487 such as PJW, K&R, etc.
1489 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1492 char *str = (char *)key;
1493 unsigned int hash = 5381;
1496 while ((c = *str++))
1497 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1502 static int keys_are_equal(void *key1, void *key2)
1504 return (strEqual((char *)key1, (char *)key2));
1507 SetupFileHash *newSetupFileHash()
1509 SetupFileHash *new_hash =
1510 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1512 if (new_hash == NULL)
1513 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1518 void freeSetupFileHash(SetupFileHash *hash)
1523 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1526 char *getHashEntry(SetupFileHash *hash, char *token)
1531 return search_hash_entry(hash, token);
1534 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1541 value_copy = getStringCopy(value);
1543 /* change value; if it does not exist, insert it as new */
1544 if (!change_hash_entry(hash, token, value_copy))
1545 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1546 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1549 char *removeHashEntry(SetupFileHash *hash, char *token)
1554 return remove_hash_entry(hash, token);
1558 static void printSetupFileHash(SetupFileHash *hash)
1560 BEGIN_HASH_ITERATION(hash, itr)
1562 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1563 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1565 END_HASH_ITERATION(hash, itr)
1569 static void *loadSetupFileData(char *filename, boolean use_hash)
1571 char line[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1572 char *token, *value, *line_ptr;
1573 void *setup_file_data, *insert_ptr = NULL;
1574 boolean read_continued_line = FALSE;
1577 if (!(file = fopen(filename, MODE_READ)))
1579 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1585 setup_file_data = newSetupFileHash();
1587 insert_ptr = setup_file_data = newSetupFileList("", "");
1591 /* read next line of input file */
1592 if (!fgets(line, MAX_LINE_LEN, file))
1595 /* cut trailing newline or carriage return */
1596 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1597 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1600 if (read_continued_line)
1602 /* cut leading whitespaces from input line */
1603 for (line_ptr = line; *line_ptr; line_ptr++)
1604 if (*line_ptr != ' ' && *line_ptr != '\t')
1607 /* append new line to existing line, if there is enough space */
1608 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1609 strcat(previous_line, line_ptr);
1611 strcpy(line, previous_line); /* copy storage buffer to line */
1613 read_continued_line = FALSE;
1616 /* if the last character is '\', continue at next line */
1617 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1619 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1620 strcpy(previous_line, line); /* copy line to storage buffer */
1622 read_continued_line = TRUE;
1627 /* cut trailing comment from input line */
1628 for (line_ptr = line; *line_ptr; line_ptr++)
1630 if (*line_ptr == '#')
1637 /* cut trailing whitespaces from input line */
1638 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1639 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1642 /* ignore empty lines */
1646 /* cut leading whitespaces from token */
1647 for (token = line; *token; token++)
1648 if (*token != ' ' && *token != '\t')
1651 /* start with empty value as reliable default */
1654 /* find end of token to determine start of value */
1655 for (line_ptr = token; *line_ptr; line_ptr++)
1657 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1659 *line_ptr = '\0'; /* terminate token string */
1660 value = line_ptr + 1; /* set beginning of value */
1666 /* cut leading whitespaces from value */
1667 for (; *value; value++)
1668 if (*value != ' ' && *value != '\t')
1673 value = "true"; /* treat tokens without value as "true" */
1679 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1681 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1689 if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1690 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1694 SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1695 SetupFileList *first_valid_list_entry = setup_file_list->next;
1697 /* free empty list header */
1698 setup_file_list->next = NULL;
1699 freeSetupFileList(setup_file_list);
1700 setup_file_data = first_valid_list_entry;
1702 if (first_valid_list_entry == NULL)
1703 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1706 return setup_file_data;
1709 void saveSetupFileHash(SetupFileHash *hash, char *filename)
1713 if (!(file = fopen(filename, MODE_WRITE)))
1715 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
1720 BEGIN_HASH_ITERATION(hash, itr)
1722 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
1723 HASH_ITERATION_VALUE(itr)));
1725 END_HASH_ITERATION(hash, itr)
1730 SetupFileList *loadSetupFileList(char *filename)
1732 return (SetupFileList *)loadSetupFileData(filename, FALSE);
1735 SetupFileHash *loadSetupFileHash(char *filename)
1737 return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1740 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1741 char *filename, char *identifier)
1743 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1746 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
1747 else if (!checkCookieString(value, identifier))
1748 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
1752 /* ========================================================================= */
1753 /* setup file stuff */
1754 /* ========================================================================= */
1756 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1757 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1758 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1760 /* level directory info */
1761 #define LEVELINFO_TOKEN_IDENTIFIER 0
1762 #define LEVELINFO_TOKEN_NAME 1
1763 #define LEVELINFO_TOKEN_NAME_SORTING 2
1764 #define LEVELINFO_TOKEN_AUTHOR 3
1765 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
1766 #define LEVELINFO_TOKEN_IMPORTED_BY 5
1767 #define LEVELINFO_TOKEN_LEVELS 6
1768 #define LEVELINFO_TOKEN_FIRST_LEVEL 7
1769 #define LEVELINFO_TOKEN_SORT_PRIORITY 8
1770 #define LEVELINFO_TOKEN_LATEST_ENGINE 9
1771 #define LEVELINFO_TOKEN_LEVEL_GROUP 10
1772 #define LEVELINFO_TOKEN_READONLY 11
1773 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 12
1774 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 13
1775 #define LEVELINFO_TOKEN_GRAPHICS_SET 14
1776 #define LEVELINFO_TOKEN_SOUNDS_SET 15
1777 #define LEVELINFO_TOKEN_MUSIC_SET 16
1778 #define LEVELINFO_TOKEN_FILENAME 17
1779 #define LEVELINFO_TOKEN_FILETYPE 18
1780 #define LEVELINFO_TOKEN_HANDICAP 19
1781 #define LEVELINFO_TOKEN_SKIP_LEVELS 20
1783 #define NUM_LEVELINFO_TOKENS 21
1785 static LevelDirTree ldi;
1787 static struct TokenInfo levelinfo_tokens[] =
1789 /* level directory info */
1790 { TYPE_STRING, &ldi.identifier, "identifier" },
1791 { TYPE_STRING, &ldi.name, "name" },
1792 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1793 { TYPE_STRING, &ldi.author, "author" },
1794 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1795 { TYPE_STRING, &ldi.imported_by, "imported_by" },
1796 { TYPE_INTEGER, &ldi.levels, "levels" },
1797 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1798 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1799 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
1800 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1801 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1802 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
1803 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
1804 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1805 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1806 { TYPE_STRING, &ldi.music_set, "music_set" },
1807 { TYPE_STRING, &ldi.level_filename, "filename" },
1808 { TYPE_STRING, &ldi.level_filetype, "filetype" },
1809 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
1810 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
1813 static struct TokenInfo artworkinfo_tokens[] =
1815 /* artwork directory info */
1816 { TYPE_STRING, &ldi.identifier, "identifier" },
1817 { TYPE_STRING, &ldi.subdir, "subdir" },
1818 { TYPE_STRING, &ldi.name, "name" },
1819 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1820 { TYPE_STRING, &ldi.author, "author" },
1821 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1822 { TYPE_STRING, &ldi.basepath, "basepath" },
1823 { TYPE_STRING, &ldi.fullpath, "fullpath" },
1824 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
1825 { TYPE_INTEGER, &ldi.color, "color" },
1826 { TYPE_STRING, &ldi.class_desc, "class_desc" },
1831 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
1835 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1836 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1837 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1838 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1841 ti->node_parent = NULL;
1842 ti->node_group = NULL;
1849 ti->fullpath = NULL;
1850 ti->basepath = NULL;
1851 ti->identifier = NULL;
1852 ti->name = getStringCopy(ANONYMOUS_NAME);
1853 ti->name_sorting = NULL;
1854 ti->author = getStringCopy(ANONYMOUS_NAME);
1856 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1857 ti->latest_engine = FALSE; /* default: get from level */
1858 ti->parent_link = FALSE;
1859 ti->in_user_dir = FALSE;
1860 ti->user_defined = FALSE;
1862 ti->class_desc = NULL;
1864 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
1866 if (ti->type == TREE_TYPE_LEVEL_DIR)
1868 ti->imported_from = NULL;
1869 ti->imported_by = NULL;
1871 ti->graphics_set_ecs = NULL;
1872 ti->graphics_set_aga = NULL;
1873 ti->graphics_set = NULL;
1874 ti->sounds_set = NULL;
1875 ti->music_set = NULL;
1876 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1877 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1878 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
1880 ti->level_filename = NULL;
1881 ti->level_filetype = NULL;
1884 ti->first_level = 0;
1886 ti->level_group = FALSE;
1887 ti->handicap_level = 0;
1888 ti->readonly = TRUE;
1889 ti->handicap = TRUE;
1890 ti->skip_levels = FALSE;
1894 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
1898 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1900 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
1905 /* copy all values from the parent structure */
1907 ti->type = parent->type;
1909 ti->node_top = parent->node_top;
1910 ti->node_parent = parent;
1911 ti->node_group = NULL;
1918 ti->fullpath = NULL;
1919 ti->basepath = NULL;
1920 ti->identifier = NULL;
1921 ti->name = getStringCopy(ANONYMOUS_NAME);
1922 ti->name_sorting = NULL;
1923 ti->author = getStringCopy(parent->author);
1925 ti->sort_priority = parent->sort_priority;
1926 ti->latest_engine = parent->latest_engine;
1927 ti->parent_link = FALSE;
1928 ti->in_user_dir = parent->in_user_dir;
1929 ti->user_defined = parent->user_defined;
1930 ti->color = parent->color;
1931 ti->class_desc = getStringCopy(parent->class_desc);
1933 ti->infotext = getStringCopy(parent->infotext);
1935 if (ti->type == TREE_TYPE_LEVEL_DIR)
1937 ti->imported_from = getStringCopy(parent->imported_from);
1938 ti->imported_by = getStringCopy(parent->imported_by);
1940 ti->graphics_set_ecs = NULL;
1941 ti->graphics_set_aga = NULL;
1942 ti->graphics_set = NULL;
1943 ti->sounds_set = NULL;
1944 ti->music_set = NULL;
1945 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1946 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1947 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
1949 ti->level_filename = NULL;
1950 ti->level_filetype = NULL;
1953 ti->first_level = 0;
1955 ti->level_group = FALSE;
1956 ti->handicap_level = 0;
1957 ti->readonly = TRUE;
1958 ti->handicap = TRUE;
1959 ti->skip_levels = FALSE;
1963 static void freeTreeInfo(TreeInfo *ti)
1965 checked_free(ti->subdir);
1966 checked_free(ti->fullpath);
1967 checked_free(ti->basepath);
1968 checked_free(ti->identifier);
1970 checked_free(ti->name);
1971 checked_free(ti->name_sorting);
1972 checked_free(ti->author);
1974 checked_free(ti->class_desc);
1976 checked_free(ti->infotext);
1978 if (ti->type == TREE_TYPE_LEVEL_DIR)
1980 checked_free(ti->imported_from);
1981 checked_free(ti->imported_by);
1983 checked_free(ti->graphics_set_ecs);
1984 checked_free(ti->graphics_set_aga);
1985 checked_free(ti->graphics_set);
1986 checked_free(ti->sounds_set);
1987 checked_free(ti->music_set);
1989 checked_free(ti->graphics_path);
1990 checked_free(ti->sounds_path);
1991 checked_free(ti->music_path);
1993 checked_free(ti->level_filename);
1994 checked_free(ti->level_filetype);
1998 void setSetupInfo(struct TokenInfo *token_info,
1999 int token_nr, char *token_value)
2001 int token_type = token_info[token_nr].type;
2002 void *setup_value = token_info[token_nr].value;
2004 if (token_value == NULL)
2007 /* set setup field to corresponding token value */
2012 *(boolean *)setup_value = get_boolean_from_string(token_value);
2016 *(Key *)setup_value = getKeyFromKeyName(token_value);
2020 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2024 *(int *)setup_value = get_integer_from_string(token_value);
2028 checked_free(*(char **)setup_value);
2029 *(char **)setup_value = getStringCopy(token_value);
2037 static int compareTreeInfoEntries(const void *object1, const void *object2)
2039 const TreeInfo *entry1 = *((TreeInfo **)object1);
2040 const TreeInfo *entry2 = *((TreeInfo **)object2);
2041 int class_sorting1, class_sorting2;
2044 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2046 class_sorting1 = LEVELSORTING(entry1);
2047 class_sorting2 = LEVELSORTING(entry2);
2051 class_sorting1 = ARTWORKSORTING(entry1);
2052 class_sorting2 = ARTWORKSORTING(entry2);
2055 if (entry1->parent_link || entry2->parent_link)
2056 compare_result = (entry1->parent_link ? -1 : +1);
2057 else if (entry1->sort_priority == entry2->sort_priority)
2059 char *name1 = getStringToLower(entry1->name_sorting);
2060 char *name2 = getStringToLower(entry2->name_sorting);
2062 compare_result = strcmp(name1, name2);
2067 else if (class_sorting1 == class_sorting2)
2068 compare_result = entry1->sort_priority - entry2->sort_priority;
2070 compare_result = class_sorting1 - class_sorting2;
2072 return compare_result;
2075 static void createParentTreeInfoNode(TreeInfo *node_parent)
2079 if (node_parent == NULL)
2082 ti_new = newTreeInfo();
2083 setTreeInfoToDefaults(ti_new, node_parent->type);
2085 ti_new->node_parent = node_parent;
2086 ti_new->parent_link = TRUE;
2088 setString(&ti_new->identifier, node_parent->identifier);
2089 setString(&ti_new->name, ".. (parent directory)");
2090 setString(&ti_new->name_sorting, ti_new->name);
2092 setString(&ti_new->subdir, "..");
2093 setString(&ti_new->fullpath, node_parent->fullpath);
2095 ti_new->sort_priority = node_parent->sort_priority;
2096 ti_new->latest_engine = node_parent->latest_engine;
2098 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2100 pushTreeInfo(&node_parent->node_group, ti_new);
2103 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2104 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2106 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2107 TreeInfo *node_parent,
2108 char *level_directory,
2109 char *directory_name)
2111 char *directory_path = getPath2(level_directory, directory_name);
2112 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2113 SetupFileHash *setup_file_hash;
2114 LevelDirTree *leveldir_new = NULL;
2117 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2118 if (!options.debug && !fileExists(filename))
2120 free(directory_path);
2126 setup_file_hash = loadSetupFileHash(filename);
2128 if (setup_file_hash == NULL)
2130 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2132 free(directory_path);
2138 leveldir_new = newTreeInfo();
2141 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2143 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2145 leveldir_new->subdir = getStringCopy(directory_name);
2147 checkSetupFileHashIdentifier(setup_file_hash, filename,
2148 getCookie("LEVELINFO"));
2150 /* set all structure fields according to the token/value pairs */
2151 ldi = *leveldir_new;
2152 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2153 setSetupInfo(levelinfo_tokens, i,
2154 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2155 *leveldir_new = ldi;
2157 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2158 setString(&leveldir_new->name, leveldir_new->subdir);
2160 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2162 if (leveldir_new->identifier == NULL)
2163 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2165 if (leveldir_new->name_sorting == NULL)
2166 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2168 if (node_parent == NULL) /* top level group */
2170 leveldir_new->basepath = getStringCopy(level_directory);
2171 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2173 else /* sub level group */
2175 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2176 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2180 if (leveldir_new->levels < 1)
2181 leveldir_new->levels = 1;
2184 leveldir_new->last_level =
2185 leveldir_new->first_level + leveldir_new->levels - 1;
2187 leveldir_new->in_user_dir =
2188 (!strEqual(leveldir_new->basepath, options.level_directory));
2190 /* adjust some settings if user's private level directory was detected */
2191 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2192 leveldir_new->in_user_dir &&
2193 (strEqual(leveldir_new->subdir, getLoginName()) ||
2194 strEqual(leveldir_new->name, getLoginName()) ||
2195 strEqual(leveldir_new->author, getRealName())))
2197 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2198 leveldir_new->readonly = FALSE;
2201 leveldir_new->user_defined =
2202 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2204 leveldir_new->color = LEVELCOLOR(leveldir_new);
2206 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2208 leveldir_new->handicap_level = /* set handicap to default value */
2209 (leveldir_new->user_defined || !leveldir_new->handicap ?
2210 leveldir_new->last_level : leveldir_new->first_level);
2213 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2215 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2217 /* skip level sets without levels (which are probably artwork base sets) */
2219 freeSetupFileHash(setup_file_hash);
2220 free(directory_path);
2228 pushTreeInfo(node_first, leveldir_new);
2230 freeSetupFileHash(setup_file_hash);
2232 if (leveldir_new->level_group)
2234 /* create node to link back to current level directory */
2235 createParentTreeInfoNode(leveldir_new);
2237 /* step into sub-directory and look for more level series */
2238 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2239 leveldir_new, directory_path);
2242 free(directory_path);
2248 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2249 TreeInfo *node_parent,
2250 char *level_directory)
2253 struct dirent *dir_entry;
2254 boolean valid_entry_found = FALSE;
2256 if ((dir = opendir(level_directory)) == NULL)
2258 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2262 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2264 struct stat file_status;
2265 char *directory_name = dir_entry->d_name;
2266 char *directory_path = getPath2(level_directory, directory_name);
2268 /* skip entries for current and parent directory */
2269 if (strEqual(directory_name, ".") ||
2270 strEqual(directory_name, ".."))
2272 free(directory_path);
2276 /* find out if directory entry is itself a directory */
2277 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2278 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2280 free(directory_path);
2284 free(directory_path);
2286 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2287 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2288 strEqual(directory_name, MUSIC_DIRECTORY))
2291 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2298 /* special case: top level directory may directly contain "levelinfo.conf" */
2299 if (node_parent == NULL && !valid_entry_found)
2301 /* check if this directory directly contains a file "levelinfo.conf" */
2302 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2303 level_directory, ".");
2306 if (!valid_entry_found)
2307 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2311 boolean AdjustGraphicsForEMC()
2313 boolean settings_changed = FALSE;
2315 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2316 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2318 return settings_changed;
2321 void LoadLevelInfo()
2323 InitUserLevelDirectory(getLoginName());
2325 DrawInitText("Loading level series:", 120, FC_GREEN);
2327 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2328 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2330 /* after loading all level set information, clone the level directory tree
2331 and remove all level sets without levels (these may still contain artwork
2332 to be offered in the setup menu as "custom artwork", and are therefore
2333 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2334 leveldir_first_all = leveldir_first;
2335 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2337 AdjustGraphicsForEMC();
2339 /* before sorting, the first entries will be from the user directory */
2340 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2342 if (leveldir_first == NULL)
2343 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2345 sortTreeInfo(&leveldir_first);
2348 dumpTreeInfo(leveldir_first, 0);
2352 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2353 TreeInfo *node_parent,
2354 char *base_directory,
2355 char *directory_name, int type)
2357 char *directory_path = getPath2(base_directory, directory_name);
2358 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2359 SetupFileHash *setup_file_hash = NULL;
2360 TreeInfo *artwork_new = NULL;
2363 if (fileExists(filename))
2364 setup_file_hash = loadSetupFileHash(filename);
2366 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2369 struct dirent *dir_entry;
2370 boolean valid_file_found = FALSE;
2372 if ((dir = opendir(directory_path)) != NULL)
2374 while ((dir_entry = readdir(dir)) != NULL)
2376 char *entry_name = dir_entry->d_name;
2378 if (FileIsArtworkType(entry_name, type))
2380 valid_file_found = TRUE;
2388 if (!valid_file_found)
2390 if (!strEqual(directory_name, "."))
2391 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2393 free(directory_path);
2400 artwork_new = newTreeInfo();
2403 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2405 setTreeInfoToDefaults(artwork_new, type);
2407 artwork_new->subdir = getStringCopy(directory_name);
2409 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2412 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2415 /* set all structure fields according to the token/value pairs */
2417 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2418 setSetupInfo(levelinfo_tokens, i,
2419 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2422 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2423 setString(&artwork_new->name, artwork_new->subdir);
2426 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2429 if (artwork_new->identifier == NULL)
2430 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2432 if (artwork_new->name_sorting == NULL)
2433 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2436 if (node_parent == NULL) /* top level group */
2438 artwork_new->basepath = getStringCopy(base_directory);
2439 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2441 else /* sub level group */
2443 artwork_new->basepath = getStringCopy(node_parent->basepath);
2444 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2447 artwork_new->in_user_dir =
2448 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2450 /* (may use ".sort_priority" from "setup_file_hash" above) */
2451 artwork_new->color = ARTWORKCOLOR(artwork_new);
2453 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2455 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2457 if (strEqual(artwork_new->subdir, "."))
2459 if (artwork_new->user_defined)
2461 setString(&artwork_new->identifier, "private");
2462 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2466 setString(&artwork_new->identifier, "classic");
2467 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2470 /* set to new values after changing ".sort_priority" */
2471 artwork_new->color = ARTWORKCOLOR(artwork_new);
2473 setString(&artwork_new->class_desc,
2474 getLevelClassDescription(artwork_new));
2478 setString(&artwork_new->identifier, artwork_new->subdir);
2481 setString(&artwork_new->name, artwork_new->identifier);
2482 setString(&artwork_new->name_sorting, artwork_new->name);
2485 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2487 pushTreeInfo(node_first, artwork_new);
2489 freeSetupFileHash(setup_file_hash);
2491 free(directory_path);
2497 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2498 TreeInfo *node_parent,
2499 char *base_directory, int type)
2502 struct dirent *dir_entry;
2503 boolean valid_entry_found = FALSE;
2505 if ((dir = opendir(base_directory)) == NULL)
2507 /* display error if directory is main "options.graphics_directory" etc. */
2508 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2509 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2514 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2516 struct stat file_status;
2517 char *directory_name = dir_entry->d_name;
2518 char *directory_path = getPath2(base_directory, directory_name);
2520 /* skip directory entries for current and parent directory */
2521 if (strEqual(directory_name, ".") ||
2522 strEqual(directory_name, ".."))
2524 free(directory_path);
2528 /* skip directory entries which are not a directory or are not accessible */
2529 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2530 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2532 free(directory_path);
2536 free(directory_path);
2538 /* check if this directory contains artwork with or without config file */
2539 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2541 directory_name, type);
2546 /* check if this directory directly contains artwork itself */
2547 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2548 base_directory, ".",
2550 if (!valid_entry_found)
2551 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2555 static TreeInfo *getDummyArtworkInfo(int type)
2557 /* this is only needed when there is completely no artwork available */
2558 TreeInfo *artwork_new = newTreeInfo();
2560 setTreeInfoToDefaults(artwork_new, type);
2562 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
2563 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2564 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2566 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
2567 setString(&artwork_new->name, UNDEFINED_FILENAME);
2568 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2573 void LoadArtworkInfo()
2576 if (level_artwork_info_hash == NULL)
2578 char *filename = getPath2(getSetupDir(), "test.conf");
2580 level_artwork_info_hash = loadSetupFileHash(filename);
2582 if (level_artwork_info_hash == NULL)
2583 level_artwork_info_hash = newSetupFileHash();
2589 DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2591 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2592 options.graphics_directory,
2593 TREE_TYPE_GRAPHICS_DIR);
2594 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2595 getUserGraphicsDir(),
2596 TREE_TYPE_GRAPHICS_DIR);
2598 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2599 options.sounds_directory,
2600 TREE_TYPE_SOUNDS_DIR);
2601 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2603 TREE_TYPE_SOUNDS_DIR);
2605 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2606 options.music_directory,
2607 TREE_TYPE_MUSIC_DIR);
2608 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2610 TREE_TYPE_MUSIC_DIR);
2612 if (artwork.gfx_first == NULL)
2613 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2614 if (artwork.snd_first == NULL)
2615 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2616 if (artwork.mus_first == NULL)
2617 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2619 /* before sorting, the first entries will be from the user directory */
2620 artwork.gfx_current =
2621 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2622 if (artwork.gfx_current == NULL)
2623 artwork.gfx_current =
2624 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2625 if (artwork.gfx_current == NULL)
2626 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2628 artwork.snd_current =
2629 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2630 if (artwork.snd_current == NULL)
2631 artwork.snd_current =
2632 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2633 if (artwork.snd_current == NULL)
2634 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2636 artwork.mus_current =
2637 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2638 if (artwork.mus_current == NULL)
2639 artwork.mus_current =
2640 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2641 if (artwork.mus_current == NULL)
2642 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2644 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2645 artwork.snd_current_identifier = artwork.snd_current->identifier;
2646 artwork.mus_current_identifier = artwork.mus_current->identifier;
2649 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2650 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2651 printf("music set == %s\n\n", artwork.mus_current_identifier);
2654 sortTreeInfo(&artwork.gfx_first);
2655 sortTreeInfo(&artwork.snd_first);
2656 sortTreeInfo(&artwork.mus_first);
2659 dumpTreeInfo(artwork.gfx_first, 0);
2660 dumpTreeInfo(artwork.snd_first, 0);
2661 dumpTreeInfo(artwork.mus_first, 0);
2665 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2666 LevelDirTree *level_node)
2668 /* recursively check all level directories for artwork sub-directories */
2672 /* check all tree entries for artwork, but skip parent link entries */
2673 if (!level_node->parent_link)
2675 TreeInfo *topnode_last = *artwork_node;
2676 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2677 ARTWORK_DIRECTORY((*artwork_node)->type));
2680 printf("::: looking in directory '%s' for '%s' ...\n",
2681 path, ARTWORK_DIRECTORY((*artwork_node)->type));
2685 char *type_string = ARTWORK_DIRECTORY((*artwork_node)->type);
2686 char *identifier = level_node->subdir;
2687 char *type_identifier = getStringCat2WithSeparator(type_string,
2689 char *cache_entry = getHashEntry(level_artwork_info_hash,
2691 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2697 printf("::: LOADING existing hash entry for '%s' ...\n",
2700 char *type_dir = ARTWORK_DIRECTORY((*artwork_node)->type);
2701 TreeInfo *artwork_new = newTreeInfo();
2703 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
2705 /* set all structure fields according to the token/value pairs */
2707 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2709 char *token = getStringCat3WithSeparator(type_dir, identifier,
2710 artworkinfo_tokens[i].text,
2712 char *value = getHashEntry(level_artwork_info_hash, token);
2714 printf("::: - '%s' => '%s'\n", token, value);
2716 setSetupInfo(artworkinfo_tokens, i, value);
2718 /* check if cache entry for this item is invalid or incomplete */
2721 printf("::: - WARNING: cache entry '%s' invalid\n", token);
2726 checked_free(token);
2731 if (artwork_new->name_sorting == NULL)
2733 printf("::: BOOOM!\n");
2739 pushTreeInfo(artwork_node, artwork_new);
2741 freeTreeInfo(artwork_new);
2745 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2746 (*artwork_node)->type);
2748 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2749 (*artwork_node)->type);
2753 if (!cached && topnode_last != *artwork_node)
2755 if (topnode_last != *artwork_node)
2758 free((*artwork_node)->identifier);
2759 free((*artwork_node)->name);
2760 free((*artwork_node)->name_sorting);
2762 (*artwork_node)->identifier = getStringCopy(level_node->subdir);
2763 (*artwork_node)->name = getStringCopy(level_node->name);
2764 (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2766 (*artwork_node)->sort_priority = level_node->sort_priority;
2767 (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2773 printf("::: adding hash entry for set '%s' ...\n", type_identifier);
2775 setHashEntry(level_artwork_info_hash, type_identifier, "true");
2777 ldi = **artwork_node;
2778 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2780 char *token = getStringCat2WithSeparator(type_identifier,
2781 artworkinfo_tokens[i].text,
2783 char *value = getSetupValue(artworkinfo_tokens[i].type,
2784 artworkinfo_tokens[i].value);
2787 setHashEntry(level_artwork_info_hash, token, value);
2789 printf("::: - setting '%s' => '%s'\n\n",
2793 if (strEqual(artworkinfo_tokens[i].text, "name_sorting"))
2794 printf("::: - '%s' => '%s' => '%s'\n",
2796 (*artwork_node)->name_sorting);
2798 checked_free(token);
2805 free(type_identifier);
2808 if (level_node->node_group != NULL)
2809 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2811 level_node = level_node->next;
2815 void LoadLevelArtworkInfo()
2817 DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2819 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
2820 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
2821 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
2824 char *filename = getPath2(getSetupDir(), "test.conf");
2826 saveSetupFileHash(level_artwork_info_hash, filename);
2831 /* needed for reloading level artwork not known at ealier stage */
2833 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
2835 artwork.gfx_current =
2836 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2837 if (artwork.gfx_current == NULL)
2838 artwork.gfx_current =
2839 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2840 if (artwork.gfx_current == NULL)
2841 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2844 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
2846 artwork.snd_current =
2847 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2848 if (artwork.snd_current == NULL)
2849 artwork.snd_current =
2850 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2851 if (artwork.snd_current == NULL)
2852 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2855 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
2857 artwork.mus_current =
2858 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2859 if (artwork.mus_current == NULL)
2860 artwork.mus_current =
2861 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2862 if (artwork.mus_current == NULL)
2863 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2866 sortTreeInfo(&artwork.gfx_first);
2867 sortTreeInfo(&artwork.snd_first);
2868 sortTreeInfo(&artwork.mus_first);
2871 dumpTreeInfo(artwork.gfx_first, 0);
2872 dumpTreeInfo(artwork.snd_first, 0);
2873 dumpTreeInfo(artwork.mus_first, 0);
2877 static void SaveUserLevelInfo()
2879 LevelDirTree *level_info;
2884 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2886 if (!(file = fopen(filename, MODE_WRITE)))
2888 Error(ERR_WARN, "cannot write level info file '%s'", filename);
2893 level_info = newTreeInfo();
2895 /* always start with reliable default values */
2896 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
2898 setString(&level_info->name, getLoginName());
2899 setString(&level_info->author, getRealName());
2900 level_info->levels = 100;
2901 level_info->first_level = 1;
2903 token_value_position = TOKEN_VALUE_POSITION_SHORT;
2905 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2906 getCookie("LEVELINFO")));
2909 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2911 if (i == LEVELINFO_TOKEN_NAME ||
2912 i == LEVELINFO_TOKEN_AUTHOR ||
2913 i == LEVELINFO_TOKEN_LEVELS ||
2914 i == LEVELINFO_TOKEN_FIRST_LEVEL)
2915 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2917 /* just to make things nicer :) */
2918 if (i == LEVELINFO_TOKEN_AUTHOR)
2919 fprintf(file, "\n");
2922 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
2926 SetFilePermissions(filename, PERMS_PRIVATE);
2928 freeTreeInfo(level_info);
2932 char *getSetupValue(int type, void *value)
2934 static char value_string[MAX_LINE_LEN];
2942 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2946 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2950 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2954 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
2958 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2962 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2966 sprintf(value_string, "%d", *(int *)value);
2970 if (*(char **)value == NULL)
2973 strcpy(value_string, *(char **)value);
2977 value_string[0] = '\0';
2981 if (type & TYPE_GHOSTED)
2982 strcpy(value_string, "n/a");
2984 return value_string;
2987 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2991 static char token_string[MAX_LINE_LEN];
2992 int token_type = token_info[token_nr].type;
2993 void *setup_value = token_info[token_nr].value;
2994 char *token_text = token_info[token_nr].text;
2995 char *value_string = getSetupValue(token_type, setup_value);
2997 /* build complete token string */
2998 sprintf(token_string, "%s%s", prefix, token_text);
3000 /* build setup entry line */
3001 line = getFormattedSetupEntry(token_string, value_string);
3003 if (token_type == TYPE_KEY_X11)
3005 Key key = *(Key *)setup_value;
3006 char *keyname = getKeyNameFromKey(key);
3008 /* add comment, if useful */
3009 if (!strEqual(keyname, "(undefined)") &&
3010 !strEqual(keyname, "(unknown)"))
3012 /* add at least one whitespace */
3014 for (i = strlen(line); i < token_comment_position; i++)
3018 strcat(line, keyname);
3025 void LoadLevelSetup_LastSeries()
3027 /* ----------------------------------------------------------------------- */
3028 /* ~/.<program>/levelsetup.conf */
3029 /* ----------------------------------------------------------------------- */
3031 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3032 SetupFileHash *level_setup_hash = NULL;
3034 /* always start with reliable default values */
3035 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3037 if ((level_setup_hash = loadSetupFileHash(filename)))
3039 char *last_level_series =
3040 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3042 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3044 if (leveldir_current == NULL)
3045 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3047 checkSetupFileHashIdentifier(level_setup_hash, filename,
3048 getCookie("LEVELSETUP"));
3050 freeSetupFileHash(level_setup_hash);
3053 Error(ERR_WARN, "using default setup values");
3058 void SaveLevelSetup_LastSeries()
3060 /* ----------------------------------------------------------------------- */
3061 /* ~/.<program>/levelsetup.conf */
3062 /* ----------------------------------------------------------------------- */
3064 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3065 char *level_subdir = leveldir_current->subdir;
3068 InitUserDataDirectory();
3070 if (!(file = fopen(filename, MODE_WRITE)))
3072 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3077 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3078 getCookie("LEVELSETUP")));
3079 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3084 SetFilePermissions(filename, PERMS_PRIVATE);
3089 static void checkSeriesInfo()
3091 static char *level_directory = NULL;
3093 struct dirent *dir_entry;
3095 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3097 level_directory = getPath2((leveldir_current->in_user_dir ?
3098 getUserLevelDir(NULL) :
3099 options.level_directory),
3100 leveldir_current->fullpath);
3102 if ((dir = opendir(level_directory)) == NULL)
3104 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3108 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3110 if (strlen(dir_entry->d_name) > 4 &&
3111 dir_entry->d_name[3] == '.' &&
3112 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3114 char levelnum_str[4];
3117 strncpy(levelnum_str, dir_entry->d_name, 3);
3118 levelnum_str[3] = '\0';
3120 levelnum_value = atoi(levelnum_str);
3123 if (levelnum_value < leveldir_current->first_level)
3125 Error(ERR_WARN, "additional level %d found", levelnum_value);
3126 leveldir_current->first_level = levelnum_value;
3128 else if (levelnum_value > leveldir_current->last_level)
3130 Error(ERR_WARN, "additional level %d found", levelnum_value);
3131 leveldir_current->last_level = levelnum_value;
3140 void LoadLevelSetup_SeriesInfo()
3143 SetupFileHash *level_setup_hash = NULL;
3144 char *level_subdir = leveldir_current->subdir;
3146 /* always start with reliable default values */
3147 level_nr = leveldir_current->first_level;
3149 checkSeriesInfo(leveldir_current);
3151 /* ----------------------------------------------------------------------- */
3152 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3153 /* ----------------------------------------------------------------------- */
3155 level_subdir = leveldir_current->subdir;
3157 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3159 if ((level_setup_hash = loadSetupFileHash(filename)))
3163 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3167 level_nr = atoi(token_value);
3169 if (level_nr < leveldir_current->first_level)
3170 level_nr = leveldir_current->first_level;
3171 if (level_nr > leveldir_current->last_level)
3172 level_nr = leveldir_current->last_level;
3175 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3179 int level_nr = atoi(token_value);
3181 if (level_nr < leveldir_current->first_level)
3182 level_nr = leveldir_current->first_level;
3183 if (level_nr > leveldir_current->last_level + 1)
3184 level_nr = leveldir_current->last_level;
3186 if (leveldir_current->user_defined || !leveldir_current->handicap)
3187 level_nr = leveldir_current->last_level;
3189 leveldir_current->handicap_level = level_nr;
3192 checkSetupFileHashIdentifier(level_setup_hash, filename,
3193 getCookie("LEVELSETUP"));
3195 freeSetupFileHash(level_setup_hash);
3198 Error(ERR_WARN, "using default setup values");
3203 void SaveLevelSetup_SeriesInfo()
3206 char *level_subdir = leveldir_current->subdir;
3207 char *level_nr_str = int2str(level_nr, 0);
3208 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3211 /* ----------------------------------------------------------------------- */
3212 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3213 /* ----------------------------------------------------------------------- */
3215 InitLevelSetupDirectory(level_subdir);
3217 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3219 if (!(file = fopen(filename, MODE_WRITE)))
3221 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3226 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3227 getCookie("LEVELSETUP")));
3228 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3230 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3231 handicap_level_str));
3235 SetFilePermissions(filename, PERMS_PRIVATE);