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 *artworkinfo_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);
2104 /* -------------------------------------------------------------------------- */
2105 /* functions for handling custom artwork info cache */
2106 /* -------------------------------------------------------------------------- */
2108 #define ARTWORKINFO_CACHE_FILENAME "cache.conf"
2110 static void LoadArtworkInfoCache()
2112 if (artworkinfo_hash == NULL)
2114 char *filename = getPath2(getSetupDir(), ARTWORKINFO_CACHE_FILENAME);
2116 /* try to load artwork info hash from already existing cache file */
2117 artworkinfo_hash = loadSetupFileHash(filename);
2119 /* if no artwork info cache file was found, start with empty hash */
2120 if (artworkinfo_hash == NULL)
2121 artworkinfo_hash = newSetupFileHash();
2127 static void SaveArtworkInfoCache()
2129 char *filename = getPath2(getSetupDir(), ARTWORKINFO_CACHE_FILENAME);
2131 saveSetupFileHash(artworkinfo_hash, filename);
2136 static TreeInfo *getArtworkInfoFromCache(char *identifier, int type)
2138 char *type_string = ARTWORK_DIRECTORY(type);
2139 char *token_prefix = getStringCat2WithSeparator(type_string, identifier, ".");
2140 char *cache_entry = getHashEntry(artworkinfo_hash, token_prefix);
2141 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2142 TreeInfo *artwork_new = NULL;
2148 printf("::: LOADING existing hash entry for '%s' ...\n", identifier);
2150 artwork_new = newTreeInfo();
2151 setTreeInfoToDefaults(artwork_new, type);
2153 /* set all structure fields according to the token/value pairs */
2155 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2157 char *token = getStringCat2WithSeparator(token_prefix,
2158 artworkinfo_tokens[i].text, ".");
2159 char *value = getHashEntry(artworkinfo_hash, token);
2161 printf("::: - setting '%s' => '%s'\n", token, value);
2163 setSetupInfo(artworkinfo_tokens, i, value);
2165 /* check if cache entry for this item is invalid or incomplete */
2168 printf("::: - WARNING: cache entry '%s' invalid\n", token);
2173 checked_free(token);
2178 freeTreeInfo(artwork_new);
2187 /* -------------------------------------------------------------------------- */
2188 /* functions for loading level info and custom artwork info */
2189 /* -------------------------------------------------------------------------- */
2191 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2192 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2194 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2195 TreeInfo *node_parent,
2196 char *level_directory,
2197 char *directory_name)
2199 char *directory_path = getPath2(level_directory, directory_name);
2200 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2201 SetupFileHash *setup_file_hash;
2202 LevelDirTree *leveldir_new = NULL;
2205 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2206 if (!options.debug && !fileExists(filename))
2208 free(directory_path);
2214 setup_file_hash = loadSetupFileHash(filename);
2216 if (setup_file_hash == NULL)
2218 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2220 free(directory_path);
2226 leveldir_new = newTreeInfo();
2229 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2231 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2233 leveldir_new->subdir = getStringCopy(directory_name);
2235 checkSetupFileHashIdentifier(setup_file_hash, filename,
2236 getCookie("LEVELINFO"));
2238 /* set all structure fields according to the token/value pairs */
2239 ldi = *leveldir_new;
2240 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2241 setSetupInfo(levelinfo_tokens, i,
2242 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2243 *leveldir_new = ldi;
2245 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2246 setString(&leveldir_new->name, leveldir_new->subdir);
2248 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2250 if (leveldir_new->identifier == NULL)
2251 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2253 if (leveldir_new->name_sorting == NULL)
2254 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2256 if (node_parent == NULL) /* top level group */
2258 leveldir_new->basepath = getStringCopy(level_directory);
2259 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2261 else /* sub level group */
2263 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2264 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2268 if (leveldir_new->levels < 1)
2269 leveldir_new->levels = 1;
2272 leveldir_new->last_level =
2273 leveldir_new->first_level + leveldir_new->levels - 1;
2275 leveldir_new->in_user_dir =
2276 (!strEqual(leveldir_new->basepath, options.level_directory));
2278 /* adjust some settings if user's private level directory was detected */
2279 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2280 leveldir_new->in_user_dir &&
2281 (strEqual(leveldir_new->subdir, getLoginName()) ||
2282 strEqual(leveldir_new->name, getLoginName()) ||
2283 strEqual(leveldir_new->author, getRealName())))
2285 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2286 leveldir_new->readonly = FALSE;
2289 leveldir_new->user_defined =
2290 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2292 leveldir_new->color = LEVELCOLOR(leveldir_new);
2294 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2296 leveldir_new->handicap_level = /* set handicap to default value */
2297 (leveldir_new->user_defined || !leveldir_new->handicap ?
2298 leveldir_new->last_level : leveldir_new->first_level);
2301 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2303 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2305 /* skip level sets without levels (which are probably artwork base sets) */
2307 freeSetupFileHash(setup_file_hash);
2308 free(directory_path);
2316 pushTreeInfo(node_first, leveldir_new);
2318 freeSetupFileHash(setup_file_hash);
2320 if (leveldir_new->level_group)
2322 /* create node to link back to current level directory */
2323 createParentTreeInfoNode(leveldir_new);
2325 /* step into sub-directory and look for more level series */
2326 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2327 leveldir_new, directory_path);
2330 free(directory_path);
2336 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2337 TreeInfo *node_parent,
2338 char *level_directory)
2341 struct dirent *dir_entry;
2342 boolean valid_entry_found = FALSE;
2344 if ((dir = opendir(level_directory)) == NULL)
2346 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2350 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2352 struct stat file_status;
2353 char *directory_name = dir_entry->d_name;
2354 char *directory_path = getPath2(level_directory, directory_name);
2356 /* skip entries for current and parent directory */
2357 if (strEqual(directory_name, ".") ||
2358 strEqual(directory_name, ".."))
2360 free(directory_path);
2364 /* find out if directory entry is itself a directory */
2365 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2366 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2368 free(directory_path);
2372 free(directory_path);
2374 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2375 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2376 strEqual(directory_name, MUSIC_DIRECTORY))
2379 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2386 /* special case: top level directory may directly contain "levelinfo.conf" */
2387 if (node_parent == NULL && !valid_entry_found)
2389 /* check if this directory directly contains a file "levelinfo.conf" */
2390 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2391 level_directory, ".");
2394 if (!valid_entry_found)
2395 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2399 boolean AdjustGraphicsForEMC()
2401 boolean settings_changed = FALSE;
2403 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2404 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2406 return settings_changed;
2409 void LoadLevelInfo()
2411 InitUserLevelDirectory(getLoginName());
2413 DrawInitText("Loading level series:", 120, FC_GREEN);
2415 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2416 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2418 /* after loading all level set information, clone the level directory tree
2419 and remove all level sets without levels (these may still contain artwork
2420 to be offered in the setup menu as "custom artwork", and are therefore
2421 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2422 leveldir_first_all = leveldir_first;
2423 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2425 AdjustGraphicsForEMC();
2427 /* before sorting, the first entries will be from the user directory */
2428 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2430 if (leveldir_first == NULL)
2431 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2433 sortTreeInfo(&leveldir_first);
2436 dumpTreeInfo(leveldir_first, 0);
2440 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2441 TreeInfo *node_parent,
2442 char *base_directory,
2443 char *directory_name, int type)
2445 char *directory_path = getPath2(base_directory, directory_name);
2446 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2447 SetupFileHash *setup_file_hash = NULL;
2448 TreeInfo *artwork_new = NULL;
2451 if (fileExists(filename))
2452 setup_file_hash = loadSetupFileHash(filename);
2454 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2457 struct dirent *dir_entry;
2458 boolean valid_file_found = FALSE;
2460 if ((dir = opendir(directory_path)) != NULL)
2462 while ((dir_entry = readdir(dir)) != NULL)
2464 char *entry_name = dir_entry->d_name;
2466 if (FileIsArtworkType(entry_name, type))
2468 valid_file_found = TRUE;
2476 if (!valid_file_found)
2478 if (!strEqual(directory_name, "."))
2479 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2481 free(directory_path);
2488 artwork_new = newTreeInfo();
2491 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2493 setTreeInfoToDefaults(artwork_new, type);
2495 artwork_new->subdir = getStringCopy(directory_name);
2497 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2500 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2503 /* set all structure fields according to the token/value pairs */
2505 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2506 setSetupInfo(levelinfo_tokens, i,
2507 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2510 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2511 setString(&artwork_new->name, artwork_new->subdir);
2514 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2517 if (artwork_new->identifier == NULL)
2518 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2520 if (artwork_new->name_sorting == NULL)
2521 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2524 if (node_parent == NULL) /* top level group */
2526 artwork_new->basepath = getStringCopy(base_directory);
2527 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2529 else /* sub level group */
2531 artwork_new->basepath = getStringCopy(node_parent->basepath);
2532 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2535 artwork_new->in_user_dir =
2536 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2538 /* (may use ".sort_priority" from "setup_file_hash" above) */
2539 artwork_new->color = ARTWORKCOLOR(artwork_new);
2541 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2543 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2545 if (strEqual(artwork_new->subdir, "."))
2547 if (artwork_new->user_defined)
2549 setString(&artwork_new->identifier, "private");
2550 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2554 setString(&artwork_new->identifier, "classic");
2555 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2558 /* set to new values after changing ".sort_priority" */
2559 artwork_new->color = ARTWORKCOLOR(artwork_new);
2561 setString(&artwork_new->class_desc,
2562 getLevelClassDescription(artwork_new));
2566 setString(&artwork_new->identifier, artwork_new->subdir);
2569 setString(&artwork_new->name, artwork_new->identifier);
2570 setString(&artwork_new->name_sorting, artwork_new->name);
2573 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2575 pushTreeInfo(node_first, artwork_new);
2577 freeSetupFileHash(setup_file_hash);
2579 free(directory_path);
2585 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2586 TreeInfo *node_parent,
2587 char *base_directory, int type)
2590 struct dirent *dir_entry;
2591 boolean valid_entry_found = FALSE;
2593 if ((dir = opendir(base_directory)) == NULL)
2595 /* display error if directory is main "options.graphics_directory" etc. */
2596 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2597 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2602 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2604 struct stat file_status;
2605 char *directory_name = dir_entry->d_name;
2606 char *directory_path = getPath2(base_directory, directory_name);
2608 /* skip directory entries for current and parent directory */
2609 if (strEqual(directory_name, ".") ||
2610 strEqual(directory_name, ".."))
2612 free(directory_path);
2616 /* skip directory entries which are not a directory or are not accessible */
2617 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2618 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2620 free(directory_path);
2624 free(directory_path);
2626 /* check if this directory contains artwork with or without config file */
2627 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2629 directory_name, type);
2634 /* check if this directory directly contains artwork itself */
2635 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2636 base_directory, ".",
2638 if (!valid_entry_found)
2639 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2643 static TreeInfo *getDummyArtworkInfo(int type)
2645 /* this is only needed when there is completely no artwork available */
2646 TreeInfo *artwork_new = newTreeInfo();
2648 setTreeInfoToDefaults(artwork_new, type);
2650 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
2651 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2652 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2654 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
2655 setString(&artwork_new->name, UNDEFINED_FILENAME);
2656 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2661 void LoadArtworkInfo()
2663 LoadArtworkInfoCache();
2665 DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2667 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2668 options.graphics_directory,
2669 TREE_TYPE_GRAPHICS_DIR);
2670 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2671 getUserGraphicsDir(),
2672 TREE_TYPE_GRAPHICS_DIR);
2674 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2675 options.sounds_directory,
2676 TREE_TYPE_SOUNDS_DIR);
2677 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2679 TREE_TYPE_SOUNDS_DIR);
2681 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2682 options.music_directory,
2683 TREE_TYPE_MUSIC_DIR);
2684 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2686 TREE_TYPE_MUSIC_DIR);
2688 if (artwork.gfx_first == NULL)
2689 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2690 if (artwork.snd_first == NULL)
2691 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2692 if (artwork.mus_first == NULL)
2693 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2695 /* before sorting, the first entries will be from the user directory */
2696 artwork.gfx_current =
2697 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2698 if (artwork.gfx_current == NULL)
2699 artwork.gfx_current =
2700 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2701 if (artwork.gfx_current == NULL)
2702 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2704 artwork.snd_current =
2705 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2706 if (artwork.snd_current == NULL)
2707 artwork.snd_current =
2708 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2709 if (artwork.snd_current == NULL)
2710 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2712 artwork.mus_current =
2713 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2714 if (artwork.mus_current == NULL)
2715 artwork.mus_current =
2716 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2717 if (artwork.mus_current == NULL)
2718 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2720 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2721 artwork.snd_current_identifier = artwork.snd_current->identifier;
2722 artwork.mus_current_identifier = artwork.mus_current->identifier;
2725 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2726 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2727 printf("music set == %s\n\n", artwork.mus_current_identifier);
2730 sortTreeInfo(&artwork.gfx_first);
2731 sortTreeInfo(&artwork.snd_first);
2732 sortTreeInfo(&artwork.mus_first);
2735 dumpTreeInfo(artwork.gfx_first, 0);
2736 dumpTreeInfo(artwork.snd_first, 0);
2737 dumpTreeInfo(artwork.mus_first, 0);
2741 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2742 LevelDirTree *level_node)
2744 /* recursively check all level directories for artwork sub-directories */
2748 /* check all tree entries for artwork, but skip parent link entries */
2749 if (!level_node->parent_link)
2751 TreeInfo *topnode_last = *artwork_node;
2752 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2753 ARTWORK_DIRECTORY((*artwork_node)->type));
2754 TreeInfo *artwork_new = getArtworkInfoFromCache(level_node->subdir,
2755 (*artwork_node)->type);
2756 boolean cached = FALSE;
2758 if (artwork_new != NULL)
2760 pushTreeInfo(artwork_node, artwork_new);
2765 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2766 (*artwork_node)->type);
2770 if (!cached && topnode_last != *artwork_node)
2772 if (topnode_last != *artwork_node)
2775 free((*artwork_node)->identifier);
2776 free((*artwork_node)->name);
2777 free((*artwork_node)->name_sorting);
2779 (*artwork_node)->identifier = getStringCopy(level_node->subdir);
2780 (*artwork_node)->name = getStringCopy(level_node->name);
2781 (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2783 (*artwork_node)->sort_priority = level_node->sort_priority;
2784 (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2788 char *identifier = level_node->subdir;
2789 char *type_string = ARTWORK_DIRECTORY((*artwork_node)->type);
2790 char *type_identifier =
2791 getStringCat2WithSeparator(type_string, identifier, ".");
2794 printf("::: adding hash entry for set '%s' ...\n", type_identifier);
2796 setHashEntry(artworkinfo_hash, type_identifier, "true");
2798 ldi = **artwork_node;
2799 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2801 char *token = getStringCat2WithSeparator(type_identifier,
2802 artworkinfo_tokens[i].text,
2804 char *value = getSetupValue(artworkinfo_tokens[i].type,
2805 artworkinfo_tokens[i].value);
2808 setHashEntry(artworkinfo_hash, token, value);
2810 printf("::: - setting '%s' => '%s'\n\n",
2814 checked_free(token);
2817 free(type_identifier);
2825 if (level_node->node_group != NULL)
2826 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2828 level_node = level_node->next;
2832 void LoadLevelArtworkInfo()
2834 DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2836 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
2837 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
2838 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
2840 SaveArtworkInfoCache();
2842 /* needed for reloading level artwork not known at ealier stage */
2844 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
2846 artwork.gfx_current =
2847 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2848 if (artwork.gfx_current == NULL)
2849 artwork.gfx_current =
2850 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2851 if (artwork.gfx_current == NULL)
2852 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2855 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
2857 artwork.snd_current =
2858 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2859 if (artwork.snd_current == NULL)
2860 artwork.snd_current =
2861 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2862 if (artwork.snd_current == NULL)
2863 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2866 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
2868 artwork.mus_current =
2869 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2870 if (artwork.mus_current == NULL)
2871 artwork.mus_current =
2872 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2873 if (artwork.mus_current == NULL)
2874 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2877 sortTreeInfo(&artwork.gfx_first);
2878 sortTreeInfo(&artwork.snd_first);
2879 sortTreeInfo(&artwork.mus_first);
2882 dumpTreeInfo(artwork.gfx_first, 0);
2883 dumpTreeInfo(artwork.snd_first, 0);
2884 dumpTreeInfo(artwork.mus_first, 0);
2888 static void SaveUserLevelInfo()
2890 LevelDirTree *level_info;
2895 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2897 if (!(file = fopen(filename, MODE_WRITE)))
2899 Error(ERR_WARN, "cannot write level info file '%s'", filename);
2904 level_info = newTreeInfo();
2906 /* always start with reliable default values */
2907 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
2909 setString(&level_info->name, getLoginName());
2910 setString(&level_info->author, getRealName());
2911 level_info->levels = 100;
2912 level_info->first_level = 1;
2914 token_value_position = TOKEN_VALUE_POSITION_SHORT;
2916 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2917 getCookie("LEVELINFO")));
2920 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2922 if (i == LEVELINFO_TOKEN_NAME ||
2923 i == LEVELINFO_TOKEN_AUTHOR ||
2924 i == LEVELINFO_TOKEN_LEVELS ||
2925 i == LEVELINFO_TOKEN_FIRST_LEVEL)
2926 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2928 /* just to make things nicer :) */
2929 if (i == LEVELINFO_TOKEN_AUTHOR)
2930 fprintf(file, "\n");
2933 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
2937 SetFilePermissions(filename, PERMS_PRIVATE);
2939 freeTreeInfo(level_info);
2943 char *getSetupValue(int type, void *value)
2945 static char value_string[MAX_LINE_LEN];
2953 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2957 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2961 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2965 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
2969 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2973 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2977 sprintf(value_string, "%d", *(int *)value);
2981 if (*(char **)value == NULL)
2984 strcpy(value_string, *(char **)value);
2988 value_string[0] = '\0';
2992 if (type & TYPE_GHOSTED)
2993 strcpy(value_string, "n/a");
2995 return value_string;
2998 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3002 static char token_string[MAX_LINE_LEN];
3003 int token_type = token_info[token_nr].type;
3004 void *setup_value = token_info[token_nr].value;
3005 char *token_text = token_info[token_nr].text;
3006 char *value_string = getSetupValue(token_type, setup_value);
3008 /* build complete token string */
3009 sprintf(token_string, "%s%s", prefix, token_text);
3011 /* build setup entry line */
3012 line = getFormattedSetupEntry(token_string, value_string);
3014 if (token_type == TYPE_KEY_X11)
3016 Key key = *(Key *)setup_value;
3017 char *keyname = getKeyNameFromKey(key);
3019 /* add comment, if useful */
3020 if (!strEqual(keyname, "(undefined)") &&
3021 !strEqual(keyname, "(unknown)"))
3023 /* add at least one whitespace */
3025 for (i = strlen(line); i < token_comment_position; i++)
3029 strcat(line, keyname);
3036 void LoadLevelSetup_LastSeries()
3038 /* ----------------------------------------------------------------------- */
3039 /* ~/.<program>/levelsetup.conf */
3040 /* ----------------------------------------------------------------------- */
3042 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3043 SetupFileHash *level_setup_hash = NULL;
3045 /* always start with reliable default values */
3046 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3048 if ((level_setup_hash = loadSetupFileHash(filename)))
3050 char *last_level_series =
3051 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3053 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3055 if (leveldir_current == NULL)
3056 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3058 checkSetupFileHashIdentifier(level_setup_hash, filename,
3059 getCookie("LEVELSETUP"));
3061 freeSetupFileHash(level_setup_hash);
3064 Error(ERR_WARN, "using default setup values");
3069 void SaveLevelSetup_LastSeries()
3071 /* ----------------------------------------------------------------------- */
3072 /* ~/.<program>/levelsetup.conf */
3073 /* ----------------------------------------------------------------------- */
3075 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3076 char *level_subdir = leveldir_current->subdir;
3079 InitUserDataDirectory();
3081 if (!(file = fopen(filename, MODE_WRITE)))
3083 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3088 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3089 getCookie("LEVELSETUP")));
3090 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3095 SetFilePermissions(filename, PERMS_PRIVATE);
3100 static void checkSeriesInfo()
3102 static char *level_directory = NULL;
3104 struct dirent *dir_entry;
3106 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3108 level_directory = getPath2((leveldir_current->in_user_dir ?
3109 getUserLevelDir(NULL) :
3110 options.level_directory),
3111 leveldir_current->fullpath);
3113 if ((dir = opendir(level_directory)) == NULL)
3115 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3119 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3121 if (strlen(dir_entry->d_name) > 4 &&
3122 dir_entry->d_name[3] == '.' &&
3123 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3125 char levelnum_str[4];
3128 strncpy(levelnum_str, dir_entry->d_name, 3);
3129 levelnum_str[3] = '\0';
3131 levelnum_value = atoi(levelnum_str);
3134 if (levelnum_value < leveldir_current->first_level)
3136 Error(ERR_WARN, "additional level %d found", levelnum_value);
3137 leveldir_current->first_level = levelnum_value;
3139 else if (levelnum_value > leveldir_current->last_level)
3141 Error(ERR_WARN, "additional level %d found", levelnum_value);
3142 leveldir_current->last_level = levelnum_value;
3151 void LoadLevelSetup_SeriesInfo()
3154 SetupFileHash *level_setup_hash = NULL;
3155 char *level_subdir = leveldir_current->subdir;
3157 /* always start with reliable default values */
3158 level_nr = leveldir_current->first_level;
3160 checkSeriesInfo(leveldir_current);
3162 /* ----------------------------------------------------------------------- */
3163 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3164 /* ----------------------------------------------------------------------- */
3166 level_subdir = leveldir_current->subdir;
3168 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3170 if ((level_setup_hash = loadSetupFileHash(filename)))
3174 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3178 level_nr = atoi(token_value);
3180 if (level_nr < leveldir_current->first_level)
3181 level_nr = leveldir_current->first_level;
3182 if (level_nr > leveldir_current->last_level)
3183 level_nr = leveldir_current->last_level;
3186 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3190 int level_nr = atoi(token_value);
3192 if (level_nr < leveldir_current->first_level)
3193 level_nr = leveldir_current->first_level;
3194 if (level_nr > leveldir_current->last_level + 1)
3195 level_nr = leveldir_current->last_level;
3197 if (leveldir_current->user_defined || !leveldir_current->handicap)
3198 level_nr = leveldir_current->last_level;
3200 leveldir_current->handicap_level = level_nr;
3203 checkSetupFileHashIdentifier(level_setup_hash, filename,
3204 getCookie("LEVELSETUP"));
3206 freeSetupFileHash(level_setup_hash);
3209 Error(ERR_WARN, "using default setup values");
3214 void SaveLevelSetup_SeriesInfo()
3217 char *level_subdir = leveldir_current->subdir;
3218 char *level_nr_str = int2str(level_nr, 0);
3219 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3222 /* ----------------------------------------------------------------------- */
3223 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3224 /* ----------------------------------------------------------------------- */
3226 InitLevelSetupDirectory(level_subdir);
3228 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3230 if (!(file = fopen(filename, MODE_WRITE)))
3232 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3237 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3238 getCookie("LEVELSETUP")));
3239 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3241 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3242 handicap_level_str));
3246 SetFilePermissions(filename, PERMS_PRIVATE);