1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
22 #if !defined(PLATFORM_WIN32)
24 #include <sys/param.h>
34 #define NUM_LEVELCLASS_DESC 8
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
49 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
50 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
51 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
57 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
61 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
62 IS_LEVELCLASS_BD(n) ? 2 : \
63 IS_LEVELCLASS_EM(n) ? 3 : \
64 IS_LEVELCLASS_SP(n) ? 4 : \
65 IS_LEVELCLASS_DX(n) ? 5 : \
66 IS_LEVELCLASS_SB(n) ? 6 : \
67 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
68 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
72 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
73 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
74 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
78 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
79 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
80 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
83 #define TOKEN_VALUE_POSITION_SHORT 32
84 #define TOKEN_VALUE_POSITION_DEFAULT 40
85 #define TOKEN_COMMENT_POSITION_DEFAULT 60
87 #define MAX_COOKIE_LEN 256
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
94 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
102 /* ------------------------------------------------------------------------- */
104 /* ------------------------------------------------------------------------- */
106 static char *getLevelClassDescription(TreeInfo *ti)
108 int position = ti->sort_priority / 100;
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
113 return "Unknown Level Class";
116 static char *getUserLevelDir(char *level_subdir)
118 static char *userlevel_dir = NULL;
119 char *data_dir = getUserGameDataDir();
120 char *userlevel_subdir = LEVELS_DIRECTORY;
122 checked_free(userlevel_dir);
124 if (level_subdir != NULL)
125 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
127 userlevel_dir = getPath2(data_dir, userlevel_subdir);
129 return userlevel_dir;
132 static char *getScoreDir(char *level_subdir)
134 static char *score_dir = NULL;
135 char *data_dir = getCommonDataDir();
136 char *score_subdir = SCORES_DIRECTORY;
138 checked_free(score_dir);
140 if (level_subdir != NULL)
141 score_dir = getPath3(data_dir, score_subdir, level_subdir);
143 score_dir = getPath2(data_dir, score_subdir);
148 static char *getLevelSetupDir(char *level_subdir)
150 static char *levelsetup_dir = NULL;
151 char *data_dir = getUserGameDataDir();
152 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
154 checked_free(levelsetup_dir);
156 if (level_subdir != NULL)
157 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
159 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
161 return levelsetup_dir;
164 static char *getCacheDir()
166 static char *cache_dir = NULL;
168 if (cache_dir == NULL)
169 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
176 static char *level_dir = NULL;
179 return options.level_directory;
181 checked_free(level_dir);
183 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184 options.level_directory), node->fullpath);
189 char *getCurrentLevelDir()
191 return getLevelDirFromTreeInfo(leveldir_current);
194 static char *getTapeDir(char *level_subdir)
196 static char *tape_dir = NULL;
197 char *data_dir = getUserGameDataDir();
198 char *tape_subdir = TAPES_DIRECTORY;
200 checked_free(tape_dir);
202 if (level_subdir != NULL)
203 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
205 tape_dir = getPath2(data_dir, tape_subdir);
210 static char *getSolutionTapeDir()
212 static char *tape_dir = NULL;
213 char *data_dir = getCurrentLevelDir();
214 char *tape_subdir = TAPES_DIRECTORY;
216 checked_free(tape_dir);
218 tape_dir = getPath2(data_dir, tape_subdir);
223 static char *getDefaultGraphicsDir(char *graphics_subdir)
225 static char *graphics_dir = NULL;
227 if (graphics_subdir == NULL)
228 return options.graphics_directory;
230 checked_free(graphics_dir);
232 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
237 static char *getDefaultSoundsDir(char *sounds_subdir)
239 static char *sounds_dir = NULL;
241 if (sounds_subdir == NULL)
242 return options.sounds_directory;
244 checked_free(sounds_dir);
246 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
251 static char *getDefaultMusicDir(char *music_subdir)
253 static char *music_dir = NULL;
255 if (music_subdir == NULL)
256 return options.music_directory;
258 checked_free(music_dir);
260 music_dir = getPath2(options.music_directory, music_subdir);
265 static char *getDefaultArtworkSet(int type)
267 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
269 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
272 static char *getDefaultArtworkDir(int type)
274 return (type == TREE_TYPE_GRAPHICS_DIR ?
275 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276 type == TREE_TYPE_SOUNDS_DIR ?
277 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278 type == TREE_TYPE_MUSIC_DIR ?
279 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
282 static char *getUserGraphicsDir()
284 static char *usergraphics_dir = NULL;
286 if (usergraphics_dir == NULL)
287 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
289 return usergraphics_dir;
292 static char *getUserSoundsDir()
294 static char *usersounds_dir = NULL;
296 if (usersounds_dir == NULL)
297 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
299 return usersounds_dir;
302 static char *getUserMusicDir()
304 static char *usermusic_dir = NULL;
306 if (usermusic_dir == NULL)
307 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
309 return usermusic_dir;
312 static char *getSetupArtworkDir(TreeInfo *ti)
314 static char *artwork_dir = NULL;
316 checked_free(artwork_dir);
318 artwork_dir = getPath2(ti->basepath, ti->fullpath);
323 char *setLevelArtworkDir(TreeInfo *ti)
325 char **artwork_path_ptr, **artwork_set_ptr;
326 TreeInfo *level_artwork;
328 if (ti == NULL || leveldir_current == NULL)
331 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
334 checked_free(*artwork_path_ptr);
336 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
340 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
341 normally result in using the artwork configured in the setup menu. But
342 if an artwork subdirectory exists (which might contain custom artwork
343 or an artwork configuration file), this level artwork must be treated
344 as relative to the default "classic" artwork, not to the artwork that
345 is currently configured in the setup menu. */
347 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
349 checked_free(*artwork_set_ptr);
353 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
354 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
358 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
359 *artwork_set_ptr = NULL;
365 return *artwork_set_ptr;
368 inline static char *getLevelArtworkSet(int type)
370 if (leveldir_current == NULL)
373 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
376 inline static char *getLevelArtworkDir(int type)
378 if (leveldir_current == NULL)
379 return UNDEFINED_FILENAME;
381 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
384 char *getTapeFilename(int nr)
386 static char *filename = NULL;
387 char basename[MAX_FILENAME_LEN];
389 checked_free(filename);
391 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
392 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
397 char *getSolutionTapeFilename(int nr)
399 static char *filename = NULL;
400 char basename[MAX_FILENAME_LEN];
402 checked_free(filename);
404 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
405 filename = getPath2(getSolutionTapeDir(), basename);
410 char *getScoreFilename(int nr)
412 static char *filename = NULL;
413 char basename[MAX_FILENAME_LEN];
415 checked_free(filename);
417 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
418 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
423 char *getSetupFilename()
425 static char *filename = NULL;
427 checked_free(filename);
429 filename = getPath2(getSetupDir(), SETUP_FILENAME);
434 char *getEditorSetupFilename()
436 static char *filename = NULL;
438 checked_free(filename);
439 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
441 if (fileExists(filename))
444 checked_free(filename);
445 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
450 char *getHelpAnimFilename()
452 static char *filename = NULL;
454 checked_free(filename);
456 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
461 char *getHelpTextFilename()
463 static char *filename = NULL;
465 checked_free(filename);
467 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
472 char *getLevelSetInfoFilename()
474 static char *filename = NULL;
489 for (i = 0; basenames[i] != NULL; i++)
491 checked_free(filename);
492 filename = getPath2(getCurrentLevelDir(), basenames[i]);
494 if (fileExists(filename))
501 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
503 static char *filename = NULL;
506 sprintf(basename, "%s_%d.txt",
507 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
509 checked_free(filename);
510 filename = getPath2(getCurrentLevelDir(), basename);
512 if (fileExists(filename))
518 static char *getCorrectedArtworkBasename(char *basename)
520 char *basename_corrected = basename;
522 #if defined(PLATFORM_MSDOS)
523 if (program.filename_prefix != NULL)
525 int prefix_len = strlen(program.filename_prefix);
527 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
528 basename_corrected = &basename[prefix_len];
530 /* if corrected filename is still longer than standard MS-DOS filename
531 size (8 characters + 1 dot + 3 characters file extension), shorten
532 filename by writing file extension after 8th basename character */
533 if (strlen(basename_corrected) > 8 + 1 + 3)
535 static char *msdos_filename = NULL;
537 checked_free(msdos_filename);
539 msdos_filename = getStringCopy(basename_corrected);
540 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
542 basename_corrected = msdos_filename;
547 return basename_corrected;
550 char *getCustomImageFilename(char *basename)
552 static char *filename = NULL;
553 boolean skip_setup_artwork = FALSE;
555 checked_free(filename);
557 basename = getCorrectedArtworkBasename(basename);
559 if (!setup.override_level_graphics)
561 /* 1st try: look for special artwork in current level series directory */
562 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
563 if (fileExists(filename))
568 /* check if there is special artwork configured in level series config */
569 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
571 /* 2nd try: look for special artwork configured in level series config */
572 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
573 if (fileExists(filename))
578 /* take missing artwork configured in level set config from default */
579 skip_setup_artwork = TRUE;
583 if (!skip_setup_artwork)
585 /* 3rd try: look for special artwork in configured artwork directory */
586 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
587 if (fileExists(filename))
593 /* 4th try: look for default artwork in new default artwork directory */
594 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
595 if (fileExists(filename))
600 /* 5th try: look for default artwork in old default artwork directory */
601 filename = getPath2(options.graphics_directory, basename);
602 if (fileExists(filename))
605 return NULL; /* cannot find specified artwork file anywhere */
608 char *getCustomSoundFilename(char *basename)
610 static char *filename = NULL;
611 boolean skip_setup_artwork = FALSE;
613 checked_free(filename);
615 basename = getCorrectedArtworkBasename(basename);
617 if (!setup.override_level_sounds)
619 /* 1st try: look for special artwork in current level series directory */
620 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
621 if (fileExists(filename))
626 /* check if there is special artwork configured in level series config */
627 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
629 /* 2nd try: look for special artwork configured in level series config */
630 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
631 if (fileExists(filename))
636 /* take missing artwork configured in level set config from default */
637 skip_setup_artwork = TRUE;
641 if (!skip_setup_artwork)
643 /* 3rd try: look for special artwork in configured artwork directory */
644 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
645 if (fileExists(filename))
651 /* 4th try: look for default artwork in new default artwork directory */
652 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
653 if (fileExists(filename))
658 /* 5th try: look for default artwork in old default artwork directory */
659 filename = getPath2(options.sounds_directory, basename);
660 if (fileExists(filename))
663 return NULL; /* cannot find specified artwork file anywhere */
666 char *getCustomMusicFilename(char *basename)
668 static char *filename = NULL;
669 boolean skip_setup_artwork = FALSE;
671 checked_free(filename);
673 basename = getCorrectedArtworkBasename(basename);
675 if (!setup.override_level_music)
677 /* 1st try: look for special artwork in current level series directory */
678 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
679 if (fileExists(filename))
684 /* check if there is special artwork configured in level series config */
685 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
687 /* 2nd try: look for special artwork configured in level series config */
688 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
689 if (fileExists(filename))
694 /* take missing artwork configured in level set config from default */
695 skip_setup_artwork = TRUE;
699 if (!skip_setup_artwork)
701 /* 3rd try: look for special artwork in configured artwork directory */
702 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
703 if (fileExists(filename))
709 /* 4th try: look for default artwork in new default artwork directory */
710 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
711 if (fileExists(filename))
716 /* 5th try: look for default artwork in old default artwork directory */
717 filename = getPath2(options.music_directory, basename);
718 if (fileExists(filename))
721 return NULL; /* cannot find specified artwork file anywhere */
724 char *getCustomArtworkFilename(char *basename, int type)
726 if (type == ARTWORK_TYPE_GRAPHICS)
727 return getCustomImageFilename(basename);
728 else if (type == ARTWORK_TYPE_SOUNDS)
729 return getCustomSoundFilename(basename);
730 else if (type == ARTWORK_TYPE_MUSIC)
731 return getCustomMusicFilename(basename);
733 return UNDEFINED_FILENAME;
736 char *getCustomArtworkConfigFilename(int type)
738 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
741 char *getCustomArtworkLevelConfigFilename(int type)
743 static char *filename = NULL;
745 checked_free(filename);
747 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
752 char *getCustomMusicDirectory(void)
754 static char *directory = NULL;
755 boolean skip_setup_artwork = FALSE;
757 checked_free(directory);
759 if (!setup.override_level_music)
761 /* 1st try: look for special artwork in current level series directory */
762 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
763 if (fileExists(directory))
768 /* check if there is special artwork configured in level series config */
769 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
771 /* 2nd try: look for special artwork configured in level series config */
772 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
773 if (fileExists(directory))
778 /* take missing artwork configured in level set config from default */
779 skip_setup_artwork = TRUE;
783 if (!skip_setup_artwork)
785 /* 3rd try: look for special artwork in configured artwork directory */
786 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
787 if (fileExists(directory))
793 /* 4th try: look for default artwork in new default artwork directory */
794 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
795 if (fileExists(directory))
800 /* 5th try: look for default artwork in old default artwork directory */
801 directory = getStringCopy(options.music_directory);
802 if (fileExists(directory))
805 return NULL; /* cannot find specified artwork file anywhere */
808 void InitTapeDirectory(char *level_subdir)
810 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
811 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
812 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
815 void InitScoreDirectory(char *level_subdir)
817 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
818 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
819 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
822 static void SaveUserLevelInfo();
824 void InitUserLevelDirectory(char *level_subdir)
826 if (!fileExists(getUserLevelDir(level_subdir)))
828 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
829 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
830 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
836 void InitLevelSetupDirectory(char *level_subdir)
838 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
839 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
840 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
843 void InitCacheDirectory()
845 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
846 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
850 /* ------------------------------------------------------------------------- */
851 /* some functions to handle lists of level and artwork directories */
852 /* ------------------------------------------------------------------------- */
854 TreeInfo *newTreeInfo()
856 return checked_calloc(sizeof(TreeInfo));
859 TreeInfo *newTreeInfo_setDefaults(int type)
861 TreeInfo *ti = newTreeInfo();
863 setTreeInfoToDefaults(ti, type);
868 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
870 node_new->next = *node_first;
871 *node_first = node_new;
874 int numTreeInfo(TreeInfo *node)
887 boolean validLevelSeries(TreeInfo *node)
889 return (node != NULL && !node->node_group && !node->parent_link);
892 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
897 if (node->node_group) /* enter level group (step down into tree) */
898 return getFirstValidTreeInfoEntry(node->node_group);
899 else if (node->parent_link) /* skip start entry of level group */
901 if (node->next) /* get first real level series entry */
902 return getFirstValidTreeInfoEntry(node->next);
903 else /* leave empty level group and go on */
904 return getFirstValidTreeInfoEntry(node->node_parent->next);
906 else /* this seems to be a regular level series */
910 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
915 if (node->node_parent == NULL) /* top level group */
916 return *node->node_top;
917 else /* sub level group */
918 return node->node_parent->node_group;
921 int numTreeInfoInGroup(TreeInfo *node)
923 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
926 int posTreeInfo(TreeInfo *node)
928 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
933 if (node_cmp == node)
937 node_cmp = node_cmp->next;
943 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
945 TreeInfo *node_default = node;
960 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
962 if (identifier == NULL)
967 if (node->node_group)
969 TreeInfo *node_group;
971 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
976 else if (!node->parent_link)
978 if (strEqual(identifier, node->identifier))
988 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
989 TreeInfo *node, boolean skip_sets_without_levels)
996 if (!node->parent_link && !node->level_group &&
997 skip_sets_without_levels && node->levels == 0)
998 return cloneTreeNode(node_top, node_parent, node->next,
999 skip_sets_without_levels);
1002 node_new = getTreeInfoCopy(node); /* copy complete node */
1004 node_new = newTreeInfo();
1006 *node_new = *node; /* copy complete node */
1009 node_new->node_top = node_top; /* correct top node link */
1010 node_new->node_parent = node_parent; /* correct parent node link */
1012 if (node->level_group)
1013 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1014 skip_sets_without_levels);
1016 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1017 skip_sets_without_levels);
1022 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1024 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1026 *ti_new = ti_cloned;
1029 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1031 boolean settings_changed = FALSE;
1035 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1036 !strEqual(node->graphics_set, node->graphics_set_ecs))
1038 setString(&node->graphics_set, node->graphics_set_ecs);
1039 settings_changed = TRUE;
1041 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1042 !strEqual(node->graphics_set, node->graphics_set_aga))
1044 setString(&node->graphics_set, node->graphics_set_aga);
1045 settings_changed = TRUE;
1048 if (node->node_group != NULL)
1049 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1054 return settings_changed;
1057 void dumpTreeInfo(TreeInfo *node, int depth)
1061 printf("Dumping TreeInfo:\n");
1065 for (i = 0; i < (depth + 1) * 3; i++)
1068 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1069 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1071 if (node->node_group != NULL)
1072 dumpTreeInfo(node->node_group, depth + 1);
1078 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1079 int (*compare_function)(const void *,
1082 int num_nodes = numTreeInfo(*node_first);
1083 TreeInfo **sort_array;
1084 TreeInfo *node = *node_first;
1090 /* allocate array for sorting structure pointers */
1091 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1093 /* writing structure pointers to sorting array */
1094 while (i < num_nodes && node) /* double boundary check... */
1096 sort_array[i] = node;
1102 /* sorting the structure pointers in the sorting array */
1103 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1106 /* update the linkage of list elements with the sorted node array */
1107 for (i = 0; i < num_nodes - 1; i++)
1108 sort_array[i]->next = sort_array[i + 1];
1109 sort_array[num_nodes - 1]->next = NULL;
1111 /* update the linkage of the main list anchor pointer */
1112 *node_first = sort_array[0];
1116 /* now recursively sort the level group structures */
1120 if (node->node_group != NULL)
1121 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1127 void sortTreeInfo(TreeInfo **node_first)
1129 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1133 /* ========================================================================= */
1134 /* some stuff from "files.c" */
1135 /* ========================================================================= */
1137 #if defined(PLATFORM_WIN32)
1139 #define S_IRGRP S_IRUSR
1142 #define S_IROTH S_IRUSR
1145 #define S_IWGRP S_IWUSR
1148 #define S_IWOTH S_IWUSR
1151 #define S_IXGRP S_IXUSR
1154 #define S_IXOTH S_IXUSR
1157 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1162 #endif /* PLATFORM_WIN32 */
1164 /* file permissions for newly written files */
1165 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1166 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1167 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1169 #define MODE_W_PRIVATE (S_IWUSR)
1170 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1171 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1173 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1174 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1176 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1177 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1181 static char *dir = NULL;
1183 #if defined(PLATFORM_WIN32)
1186 dir = checked_malloc(MAX_PATH + 1);
1188 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1191 #elif defined(PLATFORM_UNIX)
1194 if ((dir = getenv("HOME")) == NULL)
1198 if ((pwd = getpwuid(getuid())) != NULL)
1199 dir = getStringCopy(pwd->pw_dir);
1211 char *getCommonDataDir(void)
1213 static char *common_data_dir = NULL;
1215 #if defined(PLATFORM_WIN32)
1216 if (common_data_dir == NULL)
1218 char *dir = checked_malloc(MAX_PATH + 1);
1220 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1221 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1222 common_data_dir = getPath2(dir, program.userdata_subdir);
1224 common_data_dir = options.rw_base_directory;
1227 if (common_data_dir == NULL)
1228 common_data_dir = options.rw_base_directory;
1231 return common_data_dir;
1234 char *getPersonalDataDir(void)
1236 static char *personal_data_dir = NULL;
1238 #if defined(PLATFORM_MACOSX)
1239 if (personal_data_dir == NULL)
1240 personal_data_dir = getPath2(getHomeDir(), "Documents");
1242 if (personal_data_dir == NULL)
1243 personal_data_dir = getHomeDir();
1246 return personal_data_dir;
1249 char *getUserGameDataDir(void)
1251 static char *user_game_data_dir = NULL;
1253 if (user_game_data_dir == NULL)
1254 user_game_data_dir = getPath2(getPersonalDataDir(),
1255 program.userdata_subdir);
1257 return user_game_data_dir;
1260 void updateUserGameDataDir()
1262 #if defined(PLATFORM_MACOSX)
1263 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1264 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1266 /* convert old Unix style game data directory to Mac OS X style, if needed */
1267 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1269 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1271 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1272 userdata_dir_old, userdata_dir_new);
1274 /* continue using Unix style data directory -- this should not happen */
1275 program.userdata_path = getPath2(getPersonalDataDir(),
1276 program.userdata_subdir_unix);
1280 free(userdata_dir_old);
1286 return getUserGameDataDir();
1289 static mode_t posix_umask(mode_t mask)
1291 #if defined(PLATFORM_UNIX)
1298 static int posix_mkdir(const char *pathname, mode_t mode)
1300 #if defined(PLATFORM_WIN32)
1301 return mkdir(pathname);
1303 return mkdir(pathname, mode);
1307 void createDirectory(char *dir, char *text, int permission_class)
1309 /* leave "other" permissions in umask untouched, but ensure group parts
1310 of USERDATA_DIR_MODE are not masked */
1311 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1312 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1313 mode_t normal_umask = posix_umask(0);
1314 mode_t group_umask = ~(dir_mode & S_IRWXG);
1315 posix_umask(normal_umask & group_umask);
1317 if (!fileExists(dir))
1318 if (posix_mkdir(dir, dir_mode) != 0)
1319 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1321 posix_umask(normal_umask); /* reset normal umask */
1324 void InitUserDataDirectory()
1326 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1329 void SetFilePermissions(char *filename, int permission_class)
1331 chmod(filename, (permission_class == PERMS_PRIVATE ?
1332 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1335 char *getCookie(char *file_type)
1337 static char cookie[MAX_COOKIE_LEN + 1];
1339 if (strlen(program.cookie_prefix) + 1 +
1340 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1341 return "[COOKIE ERROR]"; /* should never happen */
1343 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1344 program.cookie_prefix, file_type,
1345 program.version_major, program.version_minor);
1350 int getFileVersionFromCookieString(const char *cookie)
1352 const char *ptr_cookie1, *ptr_cookie2;
1353 const char *pattern1 = "_FILE_VERSION_";
1354 const char *pattern2 = "?.?";
1355 const int len_cookie = strlen(cookie);
1356 const int len_pattern1 = strlen(pattern1);
1357 const int len_pattern2 = strlen(pattern2);
1358 const int len_pattern = len_pattern1 + len_pattern2;
1359 int version_major, version_minor;
1361 if (len_cookie <= len_pattern)
1364 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1365 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1367 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1370 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1371 ptr_cookie2[1] != '.' ||
1372 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1375 version_major = ptr_cookie2[0] - '0';
1376 version_minor = ptr_cookie2[2] - '0';
1378 return VERSION_IDENT(version_major, version_minor, 0, 0);
1381 boolean checkCookieString(const char *cookie, const char *template)
1383 const char *pattern = "_FILE_VERSION_?.?";
1384 const int len_cookie = strlen(cookie);
1385 const int len_template = strlen(template);
1386 const int len_pattern = strlen(pattern);
1388 if (len_cookie != len_template)
1391 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1397 /* ------------------------------------------------------------------------- */
1398 /* setup file list and hash handling functions */
1399 /* ------------------------------------------------------------------------- */
1401 char *getFormattedSetupEntry(char *token, char *value)
1404 static char entry[MAX_LINE_LEN];
1406 /* if value is an empty string, just return token without value */
1410 /* start with the token and some spaces to format output line */
1411 sprintf(entry, "%s:", token);
1412 for (i = strlen(entry); i < token_value_position; i++)
1415 /* continue with the token's value */
1416 strcat(entry, value);
1421 SetupFileList *newSetupFileList(char *token, char *value)
1423 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1425 new->token = getStringCopy(token);
1426 new->value = getStringCopy(value);
1433 void freeSetupFileList(SetupFileList *list)
1438 checked_free(list->token);
1439 checked_free(list->value);
1442 freeSetupFileList(list->next);
1447 char *getListEntry(SetupFileList *list, char *token)
1452 if (strEqual(list->token, token))
1455 return getListEntry(list->next, token);
1458 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1463 if (strEqual(list->token, token))
1465 checked_free(list->value);
1467 list->value = getStringCopy(value);
1471 else if (list->next == NULL)
1472 return (list->next = newSetupFileList(token, value));
1474 return setListEntry(list->next, token, value);
1477 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1482 if (list->next == NULL)
1483 return (list->next = newSetupFileList(token, value));
1485 return addListEntry(list->next, token, value);
1489 static void printSetupFileList(SetupFileList *list)
1494 printf("token: '%s'\n", list->token);
1495 printf("value: '%s'\n", list->value);
1497 printSetupFileList(list->next);
1502 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1503 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1504 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1505 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1507 #define insert_hash_entry hashtable_insert
1508 #define search_hash_entry hashtable_search
1509 #define change_hash_entry hashtable_change
1510 #define remove_hash_entry hashtable_remove
1513 static unsigned int get_hash_from_key(void *key)
1518 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1519 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1520 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1521 it works better than many other constants, prime or not) has never been
1522 adequately explained.
1524 If you just want to have a good hash function, and cannot wait, djb2
1525 is one of the best string hash functions i know. It has excellent
1526 distribution and speed on many different sets of keys and table sizes.
1527 You are not likely to do better with one of the "well known" functions
1528 such as PJW, K&R, etc.
1530 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1533 char *str = (char *)key;
1534 unsigned int hash = 5381;
1537 while ((c = *str++))
1538 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1543 static int keys_are_equal(void *key1, void *key2)
1545 return (strEqual((char *)key1, (char *)key2));
1548 SetupFileHash *newSetupFileHash()
1550 SetupFileHash *new_hash =
1551 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1553 if (new_hash == NULL)
1554 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1559 void freeSetupFileHash(SetupFileHash *hash)
1564 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1567 char *getHashEntry(SetupFileHash *hash, char *token)
1572 return search_hash_entry(hash, token);
1575 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1582 value_copy = getStringCopy(value);
1584 /* change value; if it does not exist, insert it as new */
1585 if (!change_hash_entry(hash, token, value_copy))
1586 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1587 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1590 char *removeHashEntry(SetupFileHash *hash, char *token)
1595 return remove_hash_entry(hash, token);
1599 static void printSetupFileHash(SetupFileHash *hash)
1601 BEGIN_HASH_ITERATION(hash, itr)
1603 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1604 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1606 END_HASH_ITERATION(hash, itr)
1610 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1611 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1613 static void loadSetupFileData(void *setup_file_data, char *filename,
1614 boolean top_recursion_level, boolean is_hash)
1616 static SetupFileHash *include_filename_hash = NULL;
1617 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1618 char *token, *value, *line_ptr;
1619 void *insert_ptr = NULL;
1620 boolean read_continued_line = FALSE;
1621 boolean token_value_separator_found;
1622 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1623 boolean token_value_separator_warning = FALSE;
1627 int token_count = 0;
1629 if (!(file = fopen(filename, MODE_READ)))
1631 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1636 /* use "insert pointer" to store list end for constant insertion complexity */
1638 insert_ptr = setup_file_data;
1640 /* on top invocation, create hash to mark included files (to prevent loops) */
1641 if (top_recursion_level)
1642 include_filename_hash = newSetupFileHash();
1644 /* mark this file as already included (to prevent including it again) */
1645 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1649 /* read next line of input file */
1650 if (!fgets(line, MAX_LINE_LEN, file))
1653 /* check if line was completely read and is terminated by line break */
1654 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1657 /* cut trailing line break (this can be newline and/or carriage return) */
1658 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1659 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1662 /* copy raw input line for later use (mainly debugging output) */
1663 strcpy(line_raw, line);
1665 if (read_continued_line)
1667 /* cut leading whitespaces from input line */
1668 for (line_ptr = line; *line_ptr; line_ptr++)
1669 if (*line_ptr != ' ' && *line_ptr != '\t')
1672 /* append new line to existing line, if there is enough space */
1673 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1674 strcat(previous_line, line_ptr);
1676 strcpy(line, previous_line); /* copy storage buffer to line */
1678 read_continued_line = FALSE;
1681 /* if the last character is '\', continue at next line */
1682 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1684 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1685 strcpy(previous_line, line); /* copy line to storage buffer */
1687 read_continued_line = TRUE;
1692 /* cut trailing comment from input line */
1693 for (line_ptr = line; *line_ptr; line_ptr++)
1695 if (*line_ptr == '#')
1702 /* cut trailing whitespaces from input line */
1703 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1704 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1707 /* ignore empty lines */
1711 /* cut leading whitespaces from token */
1712 for (token = line; *token; token++)
1713 if (*token != ' ' && *token != '\t')
1716 /* start with empty value as reliable default */
1719 token_value_separator_found = FALSE;
1721 /* find end of token to determine start of value */
1722 for (line_ptr = token; *line_ptr; line_ptr++)
1725 /* first look for an explicit token/value separator, like ':' or '=' */
1726 if (*line_ptr == ':' || *line_ptr == '=')
1728 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1731 *line_ptr = '\0'; /* terminate token string */
1732 value = line_ptr + 1; /* set beginning of value */
1734 token_value_separator_found = TRUE;
1740 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1741 /* fallback: if no token/value separator found, also allow whitespaces */
1742 if (!token_value_separator_found)
1744 for (line_ptr = token; *line_ptr; line_ptr++)
1746 if (*line_ptr == ' ' || *line_ptr == '\t')
1748 *line_ptr = '\0'; /* terminate token string */
1749 value = line_ptr + 1; /* set beginning of value */
1751 token_value_separator_found = TRUE;
1757 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1758 if (token_value_separator_found)
1760 if (!token_value_separator_warning)
1762 Error(ERR_INFO_LINE, "-");
1763 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1764 Error(ERR_INFO, "- config file: '%s'", filename);
1766 token_value_separator_warning = TRUE;
1769 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1775 /* cut trailing whitespaces from token */
1776 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1777 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1780 /* cut leading whitespaces from value */
1781 for (; *value; value++)
1782 if (*value != ' ' && *value != '\t')
1787 value = "true"; /* treat tokens without value as "true" */
1792 if (strEqual(token, "include"))
1794 if (getHashEntry(include_filename_hash, value) == NULL)
1796 char *basepath = getBasePath(filename);
1797 char *basename = getBaseName(value);
1798 char *filename_include = getPath2(basepath, basename);
1801 Error(ERR_INFO, "[including file '%s']", filename_include);
1804 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1808 free(filename_include);
1812 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1818 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1820 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1829 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1830 if (token_value_separator_warning)
1831 Error(ERR_INFO_LINE, "-");
1834 if (token_count == 0)
1835 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1837 if (top_recursion_level)
1838 freeSetupFileHash(include_filename_hash);
1841 void saveSetupFileHash(SetupFileHash *hash, char *filename)
1845 if (!(file = fopen(filename, MODE_WRITE)))
1847 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
1852 BEGIN_HASH_ITERATION(hash, itr)
1854 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
1855 HASH_ITERATION_VALUE(itr)));
1857 END_HASH_ITERATION(hash, itr)
1862 SetupFileList *loadSetupFileList(char *filename)
1864 SetupFileList *setup_file_list = newSetupFileList("", "");
1865 SetupFileList *first_valid_list_entry;
1867 loadSetupFileData(setup_file_list, filename, TRUE, FALSE);
1869 first_valid_list_entry = setup_file_list->next;
1871 /* free empty list header */
1872 setup_file_list->next = NULL;
1873 freeSetupFileList(setup_file_list);
1875 return first_valid_list_entry;
1878 SetupFileHash *loadSetupFileHash(char *filename)
1880 SetupFileHash *setup_file_hash = newSetupFileHash();
1882 loadSetupFileData(setup_file_hash, filename, TRUE, TRUE);
1884 return setup_file_hash;
1887 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1888 char *filename, char *identifier)
1890 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1893 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
1894 else if (!checkCookieString(value, identifier))
1895 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
1899 /* ========================================================================= */
1900 /* setup file stuff */
1901 /* ========================================================================= */
1903 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1904 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1905 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1907 /* level directory info */
1908 #define LEVELINFO_TOKEN_IDENTIFIER 0
1909 #define LEVELINFO_TOKEN_NAME 1
1910 #define LEVELINFO_TOKEN_NAME_SORTING 2
1911 #define LEVELINFO_TOKEN_AUTHOR 3
1912 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
1913 #define LEVELINFO_TOKEN_IMPORTED_BY 5
1914 #define LEVELINFO_TOKEN_LEVELS 6
1915 #define LEVELINFO_TOKEN_FIRST_LEVEL 7
1916 #define LEVELINFO_TOKEN_SORT_PRIORITY 8
1917 #define LEVELINFO_TOKEN_LATEST_ENGINE 9
1918 #define LEVELINFO_TOKEN_LEVEL_GROUP 10
1919 #define LEVELINFO_TOKEN_READONLY 11
1920 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 12
1921 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 13
1922 #define LEVELINFO_TOKEN_GRAPHICS_SET 14
1923 #define LEVELINFO_TOKEN_SOUNDS_SET 15
1924 #define LEVELINFO_TOKEN_MUSIC_SET 16
1925 #define LEVELINFO_TOKEN_FILENAME 17
1926 #define LEVELINFO_TOKEN_FILETYPE 18
1927 #define LEVELINFO_TOKEN_HANDICAP 19
1928 #define LEVELINFO_TOKEN_SKIP_LEVELS 20
1930 #define NUM_LEVELINFO_TOKENS 21
1932 static LevelDirTree ldi;
1934 static struct TokenInfo levelinfo_tokens[] =
1936 /* level directory info */
1937 { TYPE_STRING, &ldi.identifier, "identifier" },
1938 { TYPE_STRING, &ldi.name, "name" },
1939 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1940 { TYPE_STRING, &ldi.author, "author" },
1941 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1942 { TYPE_STRING, &ldi.imported_by, "imported_by" },
1943 { TYPE_INTEGER, &ldi.levels, "levels" },
1944 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1945 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1946 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
1947 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1948 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1949 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
1950 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
1951 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1952 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1953 { TYPE_STRING, &ldi.music_set, "music_set" },
1954 { TYPE_STRING, &ldi.level_filename, "filename" },
1955 { TYPE_STRING, &ldi.level_filetype, "filetype" },
1956 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
1957 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
1960 static struct TokenInfo artworkinfo_tokens[] =
1962 /* artwork directory info */
1963 { TYPE_STRING, &ldi.identifier, "identifier" },
1964 { TYPE_STRING, &ldi.subdir, "subdir" },
1965 { TYPE_STRING, &ldi.name, "name" },
1966 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1967 { TYPE_STRING, &ldi.author, "author" },
1968 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1969 { TYPE_STRING, &ldi.basepath, "basepath" },
1970 { TYPE_STRING, &ldi.fullpath, "fullpath" },
1971 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
1972 { TYPE_INTEGER, &ldi.color, "color" },
1973 { TYPE_STRING, &ldi.class_desc, "class_desc" },
1978 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
1982 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1983 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1984 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1985 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1988 ti->node_parent = NULL;
1989 ti->node_group = NULL;
1996 ti->fullpath = NULL;
1997 ti->basepath = NULL;
1998 ti->identifier = NULL;
1999 ti->name = getStringCopy(ANONYMOUS_NAME);
2000 ti->name_sorting = NULL;
2001 ti->author = getStringCopy(ANONYMOUS_NAME);
2003 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2004 ti->latest_engine = FALSE; /* default: get from level */
2005 ti->parent_link = FALSE;
2006 ti->in_user_dir = FALSE;
2007 ti->user_defined = FALSE;
2009 ti->class_desc = NULL;
2011 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2013 if (ti->type == TREE_TYPE_LEVEL_DIR)
2015 ti->imported_from = NULL;
2016 ti->imported_by = NULL;
2018 ti->graphics_set_ecs = NULL;
2019 ti->graphics_set_aga = NULL;
2020 ti->graphics_set = NULL;
2021 ti->sounds_set = NULL;
2022 ti->music_set = NULL;
2023 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2024 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2025 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2027 ti->level_filename = NULL;
2028 ti->level_filetype = NULL;
2031 ti->first_level = 0;
2033 ti->level_group = FALSE;
2034 ti->handicap_level = 0;
2035 ti->readonly = TRUE;
2036 ti->handicap = TRUE;
2037 ti->skip_levels = FALSE;
2041 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2045 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2047 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2052 /* copy all values from the parent structure */
2054 ti->type = parent->type;
2056 ti->node_top = parent->node_top;
2057 ti->node_parent = parent;
2058 ti->node_group = NULL;
2065 ti->fullpath = NULL;
2066 ti->basepath = NULL;
2067 ti->identifier = NULL;
2068 ti->name = getStringCopy(ANONYMOUS_NAME);
2069 ti->name_sorting = NULL;
2070 ti->author = getStringCopy(parent->author);
2072 ti->sort_priority = parent->sort_priority;
2073 ti->latest_engine = parent->latest_engine;
2074 ti->parent_link = FALSE;
2075 ti->in_user_dir = parent->in_user_dir;
2076 ti->user_defined = parent->user_defined;
2077 ti->color = parent->color;
2078 ti->class_desc = getStringCopy(parent->class_desc);
2080 ti->infotext = getStringCopy(parent->infotext);
2082 if (ti->type == TREE_TYPE_LEVEL_DIR)
2084 ti->imported_from = getStringCopy(parent->imported_from);
2085 ti->imported_by = getStringCopy(parent->imported_by);
2087 ti->graphics_set_ecs = NULL;
2088 ti->graphics_set_aga = NULL;
2089 ti->graphics_set = NULL;
2090 ti->sounds_set = NULL;
2091 ti->music_set = NULL;
2092 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2093 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2094 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2096 ti->level_filename = NULL;
2097 ti->level_filetype = NULL;
2100 ti->first_level = 0;
2102 ti->level_group = FALSE;
2103 ti->handicap_level = 0;
2104 ti->readonly = TRUE;
2105 ti->handicap = TRUE;
2106 ti->skip_levels = FALSE;
2110 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2112 TreeInfo *ti_copy = newTreeInfo();
2114 /* copy all values from the original structure */
2116 ti_copy->type = ti->type;
2118 ti_copy->node_top = ti->node_top;
2119 ti_copy->node_parent = ti->node_parent;
2120 ti_copy->node_group = ti->node_group;
2121 ti_copy->next = ti->next;
2123 ti_copy->cl_first = ti->cl_first;
2124 ti_copy->cl_cursor = ti->cl_cursor;
2126 ti_copy->subdir = getStringCopy(ti->subdir);
2127 ti_copy->fullpath = getStringCopy(ti->fullpath);
2128 ti_copy->basepath = getStringCopy(ti->basepath);
2129 ti_copy->identifier = getStringCopy(ti->identifier);
2130 ti_copy->name = getStringCopy(ti->name);
2131 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2132 ti_copy->author = getStringCopy(ti->author);
2133 ti_copy->imported_from = getStringCopy(ti->imported_from);
2134 ti_copy->imported_by = getStringCopy(ti->imported_by);
2136 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2137 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2138 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2139 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2140 ti_copy->music_set = getStringCopy(ti->music_set);
2141 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2142 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2143 ti_copy->music_path = getStringCopy(ti->music_path);
2145 ti_copy->level_filename = getStringCopy(ti->level_filename);
2146 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2148 ti_copy->levels = ti->levels;
2149 ti_copy->first_level = ti->first_level;
2150 ti_copy->last_level = ti->last_level;
2151 ti_copy->sort_priority = ti->sort_priority;
2153 ti_copy->latest_engine = ti->latest_engine;
2155 ti_copy->level_group = ti->level_group;
2156 ti_copy->parent_link = ti->parent_link;
2157 ti_copy->in_user_dir = ti->in_user_dir;
2158 ti_copy->user_defined = ti->user_defined;
2159 ti_copy->readonly = ti->readonly;
2160 ti_copy->handicap = ti->handicap;
2161 ti_copy->skip_levels = ti->skip_levels;
2163 ti_copy->color = ti->color;
2164 ti_copy->class_desc = getStringCopy(ti->class_desc);
2165 ti_copy->handicap_level = ti->handicap_level;
2167 ti_copy->infotext = getStringCopy(ti->infotext);
2172 static void freeTreeInfo(TreeInfo *ti)
2177 checked_free(ti->subdir);
2178 checked_free(ti->fullpath);
2179 checked_free(ti->basepath);
2180 checked_free(ti->identifier);
2182 checked_free(ti->name);
2183 checked_free(ti->name_sorting);
2184 checked_free(ti->author);
2186 checked_free(ti->class_desc);
2188 checked_free(ti->infotext);
2190 if (ti->type == TREE_TYPE_LEVEL_DIR)
2192 checked_free(ti->imported_from);
2193 checked_free(ti->imported_by);
2195 checked_free(ti->graphics_set_ecs);
2196 checked_free(ti->graphics_set_aga);
2197 checked_free(ti->graphics_set);
2198 checked_free(ti->sounds_set);
2199 checked_free(ti->music_set);
2201 checked_free(ti->graphics_path);
2202 checked_free(ti->sounds_path);
2203 checked_free(ti->music_path);
2205 checked_free(ti->level_filename);
2206 checked_free(ti->level_filetype);
2212 void setSetupInfo(struct TokenInfo *token_info,
2213 int token_nr, char *token_value)
2215 int token_type = token_info[token_nr].type;
2216 void *setup_value = token_info[token_nr].value;
2218 if (token_value == NULL)
2221 /* set setup field to corresponding token value */
2226 *(boolean *)setup_value = get_boolean_from_string(token_value);
2230 *(Key *)setup_value = getKeyFromKeyName(token_value);
2234 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2238 *(int *)setup_value = get_integer_from_string(token_value);
2242 checked_free(*(char **)setup_value);
2243 *(char **)setup_value = getStringCopy(token_value);
2251 static int compareTreeInfoEntries(const void *object1, const void *object2)
2253 const TreeInfo *entry1 = *((TreeInfo **)object1);
2254 const TreeInfo *entry2 = *((TreeInfo **)object2);
2255 int class_sorting1, class_sorting2;
2258 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2260 class_sorting1 = LEVELSORTING(entry1);
2261 class_sorting2 = LEVELSORTING(entry2);
2265 class_sorting1 = ARTWORKSORTING(entry1);
2266 class_sorting2 = ARTWORKSORTING(entry2);
2269 if (entry1->parent_link || entry2->parent_link)
2270 compare_result = (entry1->parent_link ? -1 : +1);
2271 else if (entry1->sort_priority == entry2->sort_priority)
2273 char *name1 = getStringToLower(entry1->name_sorting);
2274 char *name2 = getStringToLower(entry2->name_sorting);
2276 compare_result = strcmp(name1, name2);
2281 else if (class_sorting1 == class_sorting2)
2282 compare_result = entry1->sort_priority - entry2->sort_priority;
2284 compare_result = class_sorting1 - class_sorting2;
2286 return compare_result;
2289 static void createParentTreeInfoNode(TreeInfo *node_parent)
2293 if (node_parent == NULL)
2296 ti_new = newTreeInfo();
2297 setTreeInfoToDefaults(ti_new, node_parent->type);
2299 ti_new->node_parent = node_parent;
2300 ti_new->parent_link = TRUE;
2302 setString(&ti_new->identifier, node_parent->identifier);
2303 setString(&ti_new->name, ".. (parent directory)");
2304 setString(&ti_new->name_sorting, ti_new->name);
2306 setString(&ti_new->subdir, "..");
2307 setString(&ti_new->fullpath, node_parent->fullpath);
2309 ti_new->sort_priority = node_parent->sort_priority;
2310 ti_new->latest_engine = node_parent->latest_engine;
2312 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2314 pushTreeInfo(&node_parent->node_group, ti_new);
2318 /* -------------------------------------------------------------------------- */
2319 /* functions for handling level and custom artwork info cache */
2320 /* -------------------------------------------------------------------------- */
2322 static void LoadArtworkInfoCache()
2324 InitCacheDirectory();
2326 if (artworkinfo_cache_old == NULL)
2328 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2330 /* try to load artwork info hash from already existing cache file */
2331 artworkinfo_cache_old = loadSetupFileHash(filename);
2333 /* if no artwork info cache file was found, start with empty hash */
2334 if (artworkinfo_cache_old == NULL)
2335 artworkinfo_cache_old = newSetupFileHash();
2340 if (artworkinfo_cache_new == NULL)
2341 artworkinfo_cache_new = newSetupFileHash();
2344 static void SaveArtworkInfoCache()
2346 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2348 InitCacheDirectory();
2350 saveSetupFileHash(artworkinfo_cache_new, filename);
2355 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2357 static char *prefix = NULL;
2359 checked_free(prefix);
2361 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2366 /* (identical to above function, but separate string buffer needed -- nasty) */
2367 static char *getCacheToken(char *prefix, char *suffix)
2369 static char *token = NULL;
2371 checked_free(token);
2373 token = getStringCat2WithSeparator(prefix, suffix, ".");
2378 static char *getFileTimestamp(char *filename)
2380 struct stat file_status;
2382 if (stat(filename, &file_status) != 0) /* cannot stat file */
2383 return getStringCopy(i_to_a(0));
2385 return getStringCopy(i_to_a(file_status.st_mtime));
2388 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2390 struct stat file_status;
2392 if (timestamp_string == NULL)
2395 if (stat(filename, &file_status) != 0) /* cannot stat file */
2398 return (file_status.st_mtime != atoi(timestamp_string));
2401 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2403 char *identifier = level_node->subdir;
2404 char *type_string = ARTWORK_DIRECTORY(type);
2405 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2406 char *token_main = getCacheToken(token_prefix, "CACHED");
2407 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2408 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2409 TreeInfo *artwork_info = NULL;
2411 if (!use_artworkinfo_cache)
2418 artwork_info = newTreeInfo();
2419 setTreeInfoToDefaults(artwork_info, type);
2421 /* set all structure fields according to the token/value pairs */
2422 ldi = *artwork_info;
2423 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2425 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2426 char *value = getHashEntry(artworkinfo_cache_old, token);
2428 setSetupInfo(artworkinfo_tokens, i, value);
2430 /* check if cache entry for this item is invalid or incomplete */
2434 Error(ERR_WARN, "cache entry '%s' invalid", token);
2441 *artwork_info = ldi;
2446 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2447 LEVELINFO_FILENAME);
2448 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2449 ARTWORKINFO_FILENAME(type));
2451 /* check if corresponding "levelinfo.conf" file has changed */
2452 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2453 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2455 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2458 /* check if corresponding "<artworkinfo>.conf" file has changed */
2459 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2460 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2462 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2467 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2470 checked_free(filename_levelinfo);
2471 checked_free(filename_artworkinfo);
2474 if (!cached && artwork_info != NULL)
2476 freeTreeInfo(artwork_info);
2481 return artwork_info;
2484 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2485 LevelDirTree *level_node, int type)
2487 char *identifier = level_node->subdir;
2488 char *type_string = ARTWORK_DIRECTORY(type);
2489 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2490 char *token_main = getCacheToken(token_prefix, "CACHED");
2491 boolean set_cache_timestamps = TRUE;
2494 setHashEntry(artworkinfo_cache_new, token_main, "true");
2496 if (set_cache_timestamps)
2498 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2499 LEVELINFO_FILENAME);
2500 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2501 ARTWORKINFO_FILENAME(type));
2502 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2503 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2505 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2506 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2508 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2509 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2511 checked_free(filename_levelinfo);
2512 checked_free(filename_artworkinfo);
2513 checked_free(timestamp_levelinfo);
2514 checked_free(timestamp_artworkinfo);
2517 ldi = *artwork_info;
2518 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2520 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2521 char *value = getSetupValue(artworkinfo_tokens[i].type,
2522 artworkinfo_tokens[i].value);
2524 setHashEntry(artworkinfo_cache_new, token, value);
2529 /* -------------------------------------------------------------------------- */
2530 /* functions for loading level info and custom artwork info */
2531 /* -------------------------------------------------------------------------- */
2533 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2534 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2536 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2537 TreeInfo *node_parent,
2538 char *level_directory,
2539 char *directory_name)
2541 static unsigned long progress_delay = 0;
2542 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2543 char *directory_path = getPath2(level_directory, directory_name);
2544 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2545 SetupFileHash *setup_file_hash;
2546 LevelDirTree *leveldir_new = NULL;
2549 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2550 if (!options.debug && !fileExists(filename))
2552 free(directory_path);
2558 setup_file_hash = loadSetupFileHash(filename);
2560 if (setup_file_hash == NULL)
2562 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2564 free(directory_path);
2570 leveldir_new = newTreeInfo();
2573 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2575 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2577 leveldir_new->subdir = getStringCopy(directory_name);
2579 checkSetupFileHashIdentifier(setup_file_hash, filename,
2580 getCookie("LEVELINFO"));
2582 /* set all structure fields according to the token/value pairs */
2583 ldi = *leveldir_new;
2584 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2585 setSetupInfo(levelinfo_tokens, i,
2586 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2587 *leveldir_new = ldi;
2589 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2590 setString(&leveldir_new->name, leveldir_new->subdir);
2592 if (leveldir_new->identifier == NULL)
2593 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2595 if (leveldir_new->name_sorting == NULL)
2596 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2598 if (node_parent == NULL) /* top level group */
2600 leveldir_new->basepath = getStringCopy(level_directory);
2601 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2603 else /* sub level group */
2605 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2606 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2610 if (leveldir_new->levels < 1)
2611 leveldir_new->levels = 1;
2614 leveldir_new->last_level =
2615 leveldir_new->first_level + leveldir_new->levels - 1;
2617 leveldir_new->in_user_dir =
2618 (!strEqual(leveldir_new->basepath, options.level_directory));
2620 /* adjust some settings if user's private level directory was detected */
2621 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2622 leveldir_new->in_user_dir &&
2623 (strEqual(leveldir_new->subdir, getLoginName()) ||
2624 strEqual(leveldir_new->name, getLoginName()) ||
2625 strEqual(leveldir_new->author, getRealName())))
2627 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2628 leveldir_new->readonly = FALSE;
2631 leveldir_new->user_defined =
2632 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2634 leveldir_new->color = LEVELCOLOR(leveldir_new);
2636 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2638 leveldir_new->handicap_level = /* set handicap to default value */
2639 (leveldir_new->user_defined || !leveldir_new->handicap ?
2640 leveldir_new->last_level : leveldir_new->first_level);
2643 if (leveldir_new->level_group ||
2644 DelayReached(&progress_delay, progress_delay_value))
2645 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2647 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2651 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2653 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2655 /* skip level sets without levels (which are probably artwork base sets) */
2657 freeSetupFileHash(setup_file_hash);
2658 free(directory_path);
2666 pushTreeInfo(node_first, leveldir_new);
2668 freeSetupFileHash(setup_file_hash);
2670 if (leveldir_new->level_group)
2672 /* create node to link back to current level directory */
2673 createParentTreeInfoNode(leveldir_new);
2675 /* recursively step into sub-directory and look for more level series */
2676 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2677 leveldir_new, directory_path);
2680 free(directory_path);
2686 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2687 TreeInfo *node_parent,
2688 char *level_directory)
2691 struct dirent *dir_entry;
2692 boolean valid_entry_found = FALSE;
2694 if ((dir = opendir(level_directory)) == NULL)
2696 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2700 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2702 struct stat file_status;
2703 char *directory_name = dir_entry->d_name;
2704 char *directory_path = getPath2(level_directory, directory_name);
2706 /* skip entries for current and parent directory */
2707 if (strEqual(directory_name, ".") ||
2708 strEqual(directory_name, ".."))
2710 free(directory_path);
2714 /* find out if directory entry is itself a directory */
2715 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2716 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2718 free(directory_path);
2722 free(directory_path);
2724 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2725 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2726 strEqual(directory_name, MUSIC_DIRECTORY))
2729 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2736 /* special case: top level directory may directly contain "levelinfo.conf" */
2737 if (node_parent == NULL && !valid_entry_found)
2739 /* check if this directory directly contains a file "levelinfo.conf" */
2740 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2741 level_directory, ".");
2744 if (!valid_entry_found)
2745 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2749 boolean AdjustGraphicsForEMC()
2751 boolean settings_changed = FALSE;
2753 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2754 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2756 return settings_changed;
2759 void LoadLevelInfo()
2761 InitUserLevelDirectory(getLoginName());
2763 DrawInitText("Loading level series", 120, FC_GREEN);
2765 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2766 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2768 /* after loading all level set information, clone the level directory tree
2769 and remove all level sets without levels (these may still contain artwork
2770 to be offered in the setup menu as "custom artwork", and are therefore
2771 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2772 leveldir_first_all = leveldir_first;
2773 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2775 AdjustGraphicsForEMC();
2777 /* before sorting, the first entries will be from the user directory */
2778 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2780 if (leveldir_first == NULL)
2781 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2783 sortTreeInfo(&leveldir_first);
2786 dumpTreeInfo(leveldir_first, 0);
2790 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2791 TreeInfo *node_parent,
2792 char *base_directory,
2793 char *directory_name, int type)
2795 char *directory_path = getPath2(base_directory, directory_name);
2796 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2797 SetupFileHash *setup_file_hash = NULL;
2798 TreeInfo *artwork_new = NULL;
2801 if (fileExists(filename))
2802 setup_file_hash = loadSetupFileHash(filename);
2804 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2807 struct dirent *dir_entry;
2808 boolean valid_file_found = FALSE;
2810 if ((dir = opendir(directory_path)) != NULL)
2812 while ((dir_entry = readdir(dir)) != NULL)
2814 char *entry_name = dir_entry->d_name;
2816 if (FileIsArtworkType(entry_name, type))
2818 valid_file_found = TRUE;
2826 if (!valid_file_found)
2828 if (!strEqual(directory_name, "."))
2829 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2831 free(directory_path);
2838 artwork_new = newTreeInfo();
2841 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2843 setTreeInfoToDefaults(artwork_new, type);
2845 artwork_new->subdir = getStringCopy(directory_name);
2847 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2850 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2853 /* set all structure fields according to the token/value pairs */
2855 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2856 setSetupInfo(levelinfo_tokens, i,
2857 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2860 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2861 setString(&artwork_new->name, artwork_new->subdir);
2863 if (artwork_new->identifier == NULL)
2864 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2866 if (artwork_new->name_sorting == NULL)
2867 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2870 if (node_parent == NULL) /* top level group */
2872 artwork_new->basepath = getStringCopy(base_directory);
2873 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2875 else /* sub level group */
2877 artwork_new->basepath = getStringCopy(node_parent->basepath);
2878 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2881 artwork_new->in_user_dir =
2882 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2884 /* (may use ".sort_priority" from "setup_file_hash" above) */
2885 artwork_new->color = ARTWORKCOLOR(artwork_new);
2887 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2889 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2891 if (strEqual(artwork_new->subdir, "."))
2893 if (artwork_new->user_defined)
2895 setString(&artwork_new->identifier, "private");
2896 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2900 setString(&artwork_new->identifier, "classic");
2901 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2904 /* set to new values after changing ".sort_priority" */
2905 artwork_new->color = ARTWORKCOLOR(artwork_new);
2907 setString(&artwork_new->class_desc,
2908 getLevelClassDescription(artwork_new));
2912 setString(&artwork_new->identifier, artwork_new->subdir);
2915 setString(&artwork_new->name, artwork_new->identifier);
2916 setString(&artwork_new->name_sorting, artwork_new->name);
2920 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2923 pushTreeInfo(node_first, artwork_new);
2925 freeSetupFileHash(setup_file_hash);
2927 free(directory_path);
2933 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2934 TreeInfo *node_parent,
2935 char *base_directory, int type)
2938 struct dirent *dir_entry;
2939 boolean valid_entry_found = FALSE;
2941 if ((dir = opendir(base_directory)) == NULL)
2943 /* display error if directory is main "options.graphics_directory" etc. */
2944 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2945 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2950 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2952 struct stat file_status;
2953 char *directory_name = dir_entry->d_name;
2954 char *directory_path = getPath2(base_directory, directory_name);
2956 /* skip directory entries for current and parent directory */
2957 if (strEqual(directory_name, ".") ||
2958 strEqual(directory_name, ".."))
2960 free(directory_path);
2964 /* skip directory entries which are not a directory or are not accessible */
2965 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2966 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2968 free(directory_path);
2972 free(directory_path);
2974 /* check if this directory contains artwork with or without config file */
2975 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2977 directory_name, type);
2982 /* check if this directory directly contains artwork itself */
2983 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2984 base_directory, ".",
2986 if (!valid_entry_found)
2987 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2991 static TreeInfo *getDummyArtworkInfo(int type)
2993 /* this is only needed when there is completely no artwork available */
2994 TreeInfo *artwork_new = newTreeInfo();
2996 setTreeInfoToDefaults(artwork_new, type);
2998 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
2999 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3000 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3002 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3003 setString(&artwork_new->name, UNDEFINED_FILENAME);
3004 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3009 void LoadArtworkInfo()
3011 LoadArtworkInfoCache();
3013 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3015 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3016 options.graphics_directory,
3017 TREE_TYPE_GRAPHICS_DIR);
3018 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3019 getUserGraphicsDir(),
3020 TREE_TYPE_GRAPHICS_DIR);
3022 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3023 options.sounds_directory,
3024 TREE_TYPE_SOUNDS_DIR);
3025 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3027 TREE_TYPE_SOUNDS_DIR);
3029 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3030 options.music_directory,
3031 TREE_TYPE_MUSIC_DIR);
3032 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3034 TREE_TYPE_MUSIC_DIR);
3036 if (artwork.gfx_first == NULL)
3037 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3038 if (artwork.snd_first == NULL)
3039 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3040 if (artwork.mus_first == NULL)
3041 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3043 /* before sorting, the first entries will be from the user directory */
3044 artwork.gfx_current =
3045 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3046 if (artwork.gfx_current == NULL)
3047 artwork.gfx_current =
3048 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3049 if (artwork.gfx_current == NULL)
3050 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3052 artwork.snd_current =
3053 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3054 if (artwork.snd_current == NULL)
3055 artwork.snd_current =
3056 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3057 if (artwork.snd_current == NULL)
3058 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3060 artwork.mus_current =
3061 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3062 if (artwork.mus_current == NULL)
3063 artwork.mus_current =
3064 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3065 if (artwork.mus_current == NULL)
3066 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3068 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3069 artwork.snd_current_identifier = artwork.snd_current->identifier;
3070 artwork.mus_current_identifier = artwork.mus_current->identifier;
3073 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3074 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3075 printf("music set == %s\n\n", artwork.mus_current_identifier);
3078 sortTreeInfo(&artwork.gfx_first);
3079 sortTreeInfo(&artwork.snd_first);
3080 sortTreeInfo(&artwork.mus_first);
3083 dumpTreeInfo(artwork.gfx_first, 0);
3084 dumpTreeInfo(artwork.snd_first, 0);
3085 dumpTreeInfo(artwork.mus_first, 0);
3089 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3090 LevelDirTree *level_node)
3092 static unsigned long progress_delay = 0;
3093 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3094 int type = (*artwork_node)->type;
3096 /* recursively check all level directories for artwork sub-directories */
3100 /* check all tree entries for artwork, but skip parent link entries */
3101 if (!level_node->parent_link)
3103 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3104 boolean cached = (artwork_new != NULL);
3108 pushTreeInfo(artwork_node, artwork_new);
3112 TreeInfo *topnode_last = *artwork_node;
3113 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3114 ARTWORK_DIRECTORY(type));
3116 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3118 if (topnode_last != *artwork_node) /* check for newly added node */
3120 artwork_new = *artwork_node;
3122 setString(&artwork_new->identifier, level_node->subdir);
3123 setString(&artwork_new->name, level_node->name);
3124 setString(&artwork_new->name_sorting, level_node->name_sorting);
3126 artwork_new->sort_priority = level_node->sort_priority;
3127 artwork_new->color = LEVELCOLOR(artwork_new);
3133 /* insert artwork info (from old cache or filesystem) into new cache */
3134 if (artwork_new != NULL)
3135 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3139 if (level_node->level_group ||
3140 DelayReached(&progress_delay, progress_delay_value))
3141 DrawInitText(level_node->name, 150, FC_YELLOW);
3144 if (level_node->node_group != NULL)
3145 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3147 level_node = level_node->next;
3151 void LoadLevelArtworkInfo()
3153 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3155 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3156 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3157 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3159 SaveArtworkInfoCache();
3161 /* needed for reloading level artwork not known at ealier stage */
3163 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3165 artwork.gfx_current =
3166 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3167 if (artwork.gfx_current == NULL)
3168 artwork.gfx_current =
3169 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3170 if (artwork.gfx_current == NULL)
3171 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3174 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3176 artwork.snd_current =
3177 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3178 if (artwork.snd_current == NULL)
3179 artwork.snd_current =
3180 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3181 if (artwork.snd_current == NULL)
3182 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3185 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3187 artwork.mus_current =
3188 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3189 if (artwork.mus_current == NULL)
3190 artwork.mus_current =
3191 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3192 if (artwork.mus_current == NULL)
3193 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3196 sortTreeInfo(&artwork.gfx_first);
3197 sortTreeInfo(&artwork.snd_first);
3198 sortTreeInfo(&artwork.mus_first);
3201 dumpTreeInfo(artwork.gfx_first, 0);
3202 dumpTreeInfo(artwork.snd_first, 0);
3203 dumpTreeInfo(artwork.mus_first, 0);
3207 static void SaveUserLevelInfo()
3209 LevelDirTree *level_info;
3214 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3216 if (!(file = fopen(filename, MODE_WRITE)))
3218 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3223 level_info = newTreeInfo();
3225 /* always start with reliable default values */
3226 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3228 setString(&level_info->name, getLoginName());
3229 setString(&level_info->author, getRealName());
3230 level_info->levels = 100;
3231 level_info->first_level = 1;
3233 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3235 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3236 getCookie("LEVELINFO")));
3239 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3241 if (i == LEVELINFO_TOKEN_NAME ||
3242 i == LEVELINFO_TOKEN_AUTHOR ||
3243 i == LEVELINFO_TOKEN_LEVELS ||
3244 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3245 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3247 /* just to make things nicer :) */
3248 if (i == LEVELINFO_TOKEN_AUTHOR)
3249 fprintf(file, "\n");
3252 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3256 SetFilePermissions(filename, PERMS_PRIVATE);
3258 freeTreeInfo(level_info);
3262 char *getSetupValue(int type, void *value)
3264 static char value_string[MAX_LINE_LEN];
3272 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3276 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3280 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3284 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3288 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3292 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3296 sprintf(value_string, "%d", *(int *)value);
3300 if (*(char **)value == NULL)
3303 strcpy(value_string, *(char **)value);
3307 value_string[0] = '\0';
3311 if (type & TYPE_GHOSTED)
3312 strcpy(value_string, "n/a");
3314 return value_string;
3317 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3321 static char token_string[MAX_LINE_LEN];
3322 int token_type = token_info[token_nr].type;
3323 void *setup_value = token_info[token_nr].value;
3324 char *token_text = token_info[token_nr].text;
3325 char *value_string = getSetupValue(token_type, setup_value);
3327 /* build complete token string */
3328 sprintf(token_string, "%s%s", prefix, token_text);
3330 /* build setup entry line */
3331 line = getFormattedSetupEntry(token_string, value_string);
3333 if (token_type == TYPE_KEY_X11)
3335 Key key = *(Key *)setup_value;
3336 char *keyname = getKeyNameFromKey(key);
3338 /* add comment, if useful */
3339 if (!strEqual(keyname, "(undefined)") &&
3340 !strEqual(keyname, "(unknown)"))
3342 /* add at least one whitespace */
3344 for (i = strlen(line); i < token_comment_position; i++)
3348 strcat(line, keyname);
3355 void LoadLevelSetup_LastSeries()
3357 /* ----------------------------------------------------------------------- */
3358 /* ~/.<program>/levelsetup.conf */
3359 /* ----------------------------------------------------------------------- */
3361 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3362 SetupFileHash *level_setup_hash = NULL;
3364 /* always start with reliable default values */
3365 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3367 if ((level_setup_hash = loadSetupFileHash(filename)))
3369 char *last_level_series =
3370 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3372 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3374 if (leveldir_current == NULL)
3375 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3377 checkSetupFileHashIdentifier(level_setup_hash, filename,
3378 getCookie("LEVELSETUP"));
3380 freeSetupFileHash(level_setup_hash);
3383 Error(ERR_WARN, "using default setup values");
3388 void SaveLevelSetup_LastSeries()
3390 /* ----------------------------------------------------------------------- */
3391 /* ~/.<program>/levelsetup.conf */
3392 /* ----------------------------------------------------------------------- */
3394 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3395 char *level_subdir = leveldir_current->subdir;
3398 InitUserDataDirectory();
3400 if (!(file = fopen(filename, MODE_WRITE)))
3402 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3407 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3408 getCookie("LEVELSETUP")));
3409 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3414 SetFilePermissions(filename, PERMS_PRIVATE);
3419 static void checkSeriesInfo()
3421 static char *level_directory = NULL;
3423 struct dirent *dir_entry;
3425 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3427 level_directory = getPath2((leveldir_current->in_user_dir ?
3428 getUserLevelDir(NULL) :
3429 options.level_directory),
3430 leveldir_current->fullpath);
3432 if ((dir = opendir(level_directory)) == NULL)
3434 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3438 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3440 if (strlen(dir_entry->d_name) > 4 &&
3441 dir_entry->d_name[3] == '.' &&
3442 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3444 char levelnum_str[4];
3447 strncpy(levelnum_str, dir_entry->d_name, 3);
3448 levelnum_str[3] = '\0';
3450 levelnum_value = atoi(levelnum_str);
3453 if (levelnum_value < leveldir_current->first_level)
3455 Error(ERR_WARN, "additional level %d found", levelnum_value);
3456 leveldir_current->first_level = levelnum_value;
3458 else if (levelnum_value > leveldir_current->last_level)
3460 Error(ERR_WARN, "additional level %d found", levelnum_value);
3461 leveldir_current->last_level = levelnum_value;
3470 void LoadLevelSetup_SeriesInfo()
3473 SetupFileHash *level_setup_hash = NULL;
3474 char *level_subdir = leveldir_current->subdir;
3476 /* always start with reliable default values */
3477 level_nr = leveldir_current->first_level;
3479 checkSeriesInfo(leveldir_current);
3481 /* ----------------------------------------------------------------------- */
3482 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3483 /* ----------------------------------------------------------------------- */
3485 level_subdir = leveldir_current->subdir;
3487 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3489 if ((level_setup_hash = loadSetupFileHash(filename)))
3493 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3497 level_nr = atoi(token_value);
3499 if (level_nr < leveldir_current->first_level)
3500 level_nr = leveldir_current->first_level;
3501 if (level_nr > leveldir_current->last_level)
3502 level_nr = leveldir_current->last_level;
3505 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3509 int level_nr = atoi(token_value);
3511 if (level_nr < leveldir_current->first_level)
3512 level_nr = leveldir_current->first_level;
3513 if (level_nr > leveldir_current->last_level + 1)
3514 level_nr = leveldir_current->last_level;
3516 if (leveldir_current->user_defined || !leveldir_current->handicap)
3517 level_nr = leveldir_current->last_level;
3519 leveldir_current->handicap_level = level_nr;
3522 checkSetupFileHashIdentifier(level_setup_hash, filename,
3523 getCookie("LEVELSETUP"));
3525 freeSetupFileHash(level_setup_hash);
3528 Error(ERR_WARN, "using default setup values");
3533 void SaveLevelSetup_SeriesInfo()
3536 char *level_subdir = leveldir_current->subdir;
3537 char *level_nr_str = int2str(level_nr, 0);
3538 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3541 /* ----------------------------------------------------------------------- */
3542 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3543 /* ----------------------------------------------------------------------- */
3545 InitLevelSetupDirectory(level_subdir);
3547 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3549 if (!(file = fopen(filename, MODE_WRITE)))
3551 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3556 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3557 getCookie("LEVELSETUP")));
3558 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3560 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3561 handicap_level_str));
3565 SetFilePermissions(filename, PERMS_PRIVATE);