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(char *filename, boolean use_hash)
1615 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1616 char *token, *value, *line_ptr;
1617 void *setup_file_data, *insert_ptr = NULL;
1618 boolean read_continued_line = FALSE;
1619 boolean token_value_separator_found;
1620 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1621 boolean token_value_separator_warning = FALSE;
1626 if (!(file = fopen(filename, MODE_READ)))
1628 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1634 setup_file_data = newSetupFileHash();
1636 insert_ptr = setup_file_data = newSetupFileList("", "");
1640 /* read next line of input file */
1641 if (!fgets(line, MAX_LINE_LEN, file))
1644 /* check if line was completely read and is terminated by line break */
1645 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1648 /* cut trailing line break (this can be newline and/or carriage return) */
1649 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1650 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1653 /* copy raw input line for later use (mainly debugging output) */
1654 strcpy(line_raw, line);
1656 if (read_continued_line)
1658 /* cut leading whitespaces from input line */
1659 for (line_ptr = line; *line_ptr; line_ptr++)
1660 if (*line_ptr != ' ' && *line_ptr != '\t')
1663 /* append new line to existing line, if there is enough space */
1664 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1665 strcat(previous_line, line_ptr);
1667 strcpy(line, previous_line); /* copy storage buffer to line */
1669 read_continued_line = FALSE;
1672 /* if the last character is '\', continue at next line */
1673 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1675 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1676 strcpy(previous_line, line); /* copy line to storage buffer */
1678 read_continued_line = TRUE;
1683 /* cut trailing comment from input line */
1684 for (line_ptr = line; *line_ptr; line_ptr++)
1686 if (*line_ptr == '#')
1693 /* cut trailing whitespaces from input line */
1694 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1695 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1698 /* ignore empty lines */
1702 /* cut leading whitespaces from token */
1703 for (token = line; *token; token++)
1704 if (*token != ' ' && *token != '\t')
1707 /* start with empty value as reliable default */
1710 token_value_separator_found = FALSE;
1712 /* find end of token to determine start of value */
1713 for (line_ptr = token; *line_ptr; line_ptr++)
1716 /* first look for an explicit token/value separator, like ':' or '=' */
1717 if (*line_ptr == ':' || *line_ptr == '=')
1719 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1722 *line_ptr = '\0'; /* terminate token string */
1723 value = line_ptr + 1; /* set beginning of value */
1725 token_value_separator_found = TRUE;
1731 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1732 /* fallback: if no token/value separator found, also allow whitespaces */
1733 if (!token_value_separator_found)
1735 for (line_ptr = token; *line_ptr; line_ptr++)
1737 if (*line_ptr == ' ' || *line_ptr == '\t')
1739 *line_ptr = '\0'; /* terminate token string */
1740 value = line_ptr + 1; /* set beginning of value */
1742 token_value_separator_found = TRUE;
1748 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1749 if (token_value_separator_found)
1751 if (!token_value_separator_warning)
1753 Error(ERR_RETURN_LINE, "-");
1754 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1755 Error(ERR_RETURN, "- config file: '%s'", filename);
1757 token_value_separator_warning = TRUE;
1760 Error(ERR_RETURN, "- line %d: '%s'", line_nr, line_raw);
1766 /* cut trailing whitespaces from token */
1767 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1768 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1771 /* cut leading whitespaces from value */
1772 for (; *value; value++)
1773 if (*value != ' ' && *value != '\t')
1778 value = "true"; /* treat tokens without value as "true" */
1784 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1786 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1792 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1793 if (token_value_separator_warning)
1794 Error(ERR_RETURN_LINE, "-");
1799 if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1800 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1804 SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1805 SetupFileList *first_valid_list_entry = setup_file_list->next;
1807 /* free empty list header */
1808 setup_file_list->next = NULL;
1809 freeSetupFileList(setup_file_list);
1810 setup_file_data = first_valid_list_entry;
1812 if (first_valid_list_entry == NULL)
1813 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1816 return setup_file_data;
1819 void saveSetupFileHash(SetupFileHash *hash, char *filename)
1823 if (!(file = fopen(filename, MODE_WRITE)))
1825 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
1830 BEGIN_HASH_ITERATION(hash, itr)
1832 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
1833 HASH_ITERATION_VALUE(itr)));
1835 END_HASH_ITERATION(hash, itr)
1840 SetupFileList *loadSetupFileList(char *filename)
1842 return (SetupFileList *)loadSetupFileData(filename, FALSE);
1845 SetupFileHash *loadSetupFileHash(char *filename)
1847 return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1850 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1851 char *filename, char *identifier)
1853 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1856 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
1857 else if (!checkCookieString(value, identifier))
1858 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
1862 /* ========================================================================= */
1863 /* setup file stuff */
1864 /* ========================================================================= */
1866 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1867 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1868 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1870 /* level directory info */
1871 #define LEVELINFO_TOKEN_IDENTIFIER 0
1872 #define LEVELINFO_TOKEN_NAME 1
1873 #define LEVELINFO_TOKEN_NAME_SORTING 2
1874 #define LEVELINFO_TOKEN_AUTHOR 3
1875 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
1876 #define LEVELINFO_TOKEN_IMPORTED_BY 5
1877 #define LEVELINFO_TOKEN_LEVELS 6
1878 #define LEVELINFO_TOKEN_FIRST_LEVEL 7
1879 #define LEVELINFO_TOKEN_SORT_PRIORITY 8
1880 #define LEVELINFO_TOKEN_LATEST_ENGINE 9
1881 #define LEVELINFO_TOKEN_LEVEL_GROUP 10
1882 #define LEVELINFO_TOKEN_READONLY 11
1883 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 12
1884 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 13
1885 #define LEVELINFO_TOKEN_GRAPHICS_SET 14
1886 #define LEVELINFO_TOKEN_SOUNDS_SET 15
1887 #define LEVELINFO_TOKEN_MUSIC_SET 16
1888 #define LEVELINFO_TOKEN_FILENAME 17
1889 #define LEVELINFO_TOKEN_FILETYPE 18
1890 #define LEVELINFO_TOKEN_HANDICAP 19
1891 #define LEVELINFO_TOKEN_SKIP_LEVELS 20
1893 #define NUM_LEVELINFO_TOKENS 21
1895 static LevelDirTree ldi;
1897 static struct TokenInfo levelinfo_tokens[] =
1899 /* level directory info */
1900 { TYPE_STRING, &ldi.identifier, "identifier" },
1901 { TYPE_STRING, &ldi.name, "name" },
1902 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1903 { TYPE_STRING, &ldi.author, "author" },
1904 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1905 { TYPE_STRING, &ldi.imported_by, "imported_by" },
1906 { TYPE_INTEGER, &ldi.levels, "levels" },
1907 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1908 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1909 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
1910 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1911 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1912 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
1913 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
1914 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1915 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1916 { TYPE_STRING, &ldi.music_set, "music_set" },
1917 { TYPE_STRING, &ldi.level_filename, "filename" },
1918 { TYPE_STRING, &ldi.level_filetype, "filetype" },
1919 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
1920 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
1923 static struct TokenInfo artworkinfo_tokens[] =
1925 /* artwork directory info */
1926 { TYPE_STRING, &ldi.identifier, "identifier" },
1927 { TYPE_STRING, &ldi.subdir, "subdir" },
1928 { TYPE_STRING, &ldi.name, "name" },
1929 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1930 { TYPE_STRING, &ldi.author, "author" },
1931 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1932 { TYPE_STRING, &ldi.basepath, "basepath" },
1933 { TYPE_STRING, &ldi.fullpath, "fullpath" },
1934 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
1935 { TYPE_INTEGER, &ldi.color, "color" },
1936 { TYPE_STRING, &ldi.class_desc, "class_desc" },
1941 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
1945 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1946 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1947 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1948 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1951 ti->node_parent = NULL;
1952 ti->node_group = NULL;
1959 ti->fullpath = NULL;
1960 ti->basepath = NULL;
1961 ti->identifier = NULL;
1962 ti->name = getStringCopy(ANONYMOUS_NAME);
1963 ti->name_sorting = NULL;
1964 ti->author = getStringCopy(ANONYMOUS_NAME);
1966 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1967 ti->latest_engine = FALSE; /* default: get from level */
1968 ti->parent_link = FALSE;
1969 ti->in_user_dir = FALSE;
1970 ti->user_defined = FALSE;
1972 ti->class_desc = NULL;
1974 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
1976 if (ti->type == TREE_TYPE_LEVEL_DIR)
1978 ti->imported_from = NULL;
1979 ti->imported_by = NULL;
1981 ti->graphics_set_ecs = NULL;
1982 ti->graphics_set_aga = NULL;
1983 ti->graphics_set = NULL;
1984 ti->sounds_set = NULL;
1985 ti->music_set = NULL;
1986 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1987 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1988 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
1990 ti->level_filename = NULL;
1991 ti->level_filetype = NULL;
1994 ti->first_level = 0;
1996 ti->level_group = FALSE;
1997 ti->handicap_level = 0;
1998 ti->readonly = TRUE;
1999 ti->handicap = TRUE;
2000 ti->skip_levels = FALSE;
2004 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2008 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2010 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2015 /* copy all values from the parent structure */
2017 ti->type = parent->type;
2019 ti->node_top = parent->node_top;
2020 ti->node_parent = parent;
2021 ti->node_group = NULL;
2028 ti->fullpath = NULL;
2029 ti->basepath = NULL;
2030 ti->identifier = NULL;
2031 ti->name = getStringCopy(ANONYMOUS_NAME);
2032 ti->name_sorting = NULL;
2033 ti->author = getStringCopy(parent->author);
2035 ti->sort_priority = parent->sort_priority;
2036 ti->latest_engine = parent->latest_engine;
2037 ti->parent_link = FALSE;
2038 ti->in_user_dir = parent->in_user_dir;
2039 ti->user_defined = parent->user_defined;
2040 ti->color = parent->color;
2041 ti->class_desc = getStringCopy(parent->class_desc);
2043 ti->infotext = getStringCopy(parent->infotext);
2045 if (ti->type == TREE_TYPE_LEVEL_DIR)
2047 ti->imported_from = getStringCopy(parent->imported_from);
2048 ti->imported_by = getStringCopy(parent->imported_by);
2050 ti->graphics_set_ecs = NULL;
2051 ti->graphics_set_aga = NULL;
2052 ti->graphics_set = NULL;
2053 ti->sounds_set = NULL;
2054 ti->music_set = NULL;
2055 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2056 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2057 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2059 ti->level_filename = NULL;
2060 ti->level_filetype = NULL;
2063 ti->first_level = 0;
2065 ti->level_group = FALSE;
2066 ti->handicap_level = 0;
2067 ti->readonly = TRUE;
2068 ti->handicap = TRUE;
2069 ti->skip_levels = FALSE;
2073 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2075 TreeInfo *ti_copy = newTreeInfo();
2077 /* copy all values from the original structure */
2079 ti_copy->type = ti->type;
2081 ti_copy->node_top = ti->node_top;
2082 ti_copy->node_parent = ti->node_parent;
2083 ti_copy->node_group = ti->node_group;
2084 ti_copy->next = ti->next;
2086 ti_copy->cl_first = ti->cl_first;
2087 ti_copy->cl_cursor = ti->cl_cursor;
2089 ti_copy->subdir = getStringCopy(ti->subdir);
2090 ti_copy->fullpath = getStringCopy(ti->fullpath);
2091 ti_copy->basepath = getStringCopy(ti->basepath);
2092 ti_copy->identifier = getStringCopy(ti->identifier);
2093 ti_copy->name = getStringCopy(ti->name);
2094 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2095 ti_copy->author = getStringCopy(ti->author);
2096 ti_copy->imported_from = getStringCopy(ti->imported_from);
2097 ti_copy->imported_by = getStringCopy(ti->imported_by);
2099 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2100 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2101 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2102 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2103 ti_copy->music_set = getStringCopy(ti->music_set);
2104 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2105 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2106 ti_copy->music_path = getStringCopy(ti->music_path);
2108 ti_copy->level_filename = getStringCopy(ti->level_filename);
2109 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2111 ti_copy->levels = ti->levels;
2112 ti_copy->first_level = ti->first_level;
2113 ti_copy->last_level = ti->last_level;
2114 ti_copy->sort_priority = ti->sort_priority;
2116 ti_copy->latest_engine = ti->latest_engine;
2118 ti_copy->level_group = ti->level_group;
2119 ti_copy->parent_link = ti->parent_link;
2120 ti_copy->in_user_dir = ti->in_user_dir;
2121 ti_copy->user_defined = ti->user_defined;
2122 ti_copy->readonly = ti->readonly;
2123 ti_copy->handicap = ti->handicap;
2124 ti_copy->skip_levels = ti->skip_levels;
2126 ti_copy->color = ti->color;
2127 ti_copy->class_desc = getStringCopy(ti->class_desc);
2128 ti_copy->handicap_level = ti->handicap_level;
2130 ti_copy->infotext = getStringCopy(ti->infotext);
2135 static void freeTreeInfo(TreeInfo *ti)
2140 checked_free(ti->subdir);
2141 checked_free(ti->fullpath);
2142 checked_free(ti->basepath);
2143 checked_free(ti->identifier);
2145 checked_free(ti->name);
2146 checked_free(ti->name_sorting);
2147 checked_free(ti->author);
2149 checked_free(ti->class_desc);
2151 checked_free(ti->infotext);
2153 if (ti->type == TREE_TYPE_LEVEL_DIR)
2155 checked_free(ti->imported_from);
2156 checked_free(ti->imported_by);
2158 checked_free(ti->graphics_set_ecs);
2159 checked_free(ti->graphics_set_aga);
2160 checked_free(ti->graphics_set);
2161 checked_free(ti->sounds_set);
2162 checked_free(ti->music_set);
2164 checked_free(ti->graphics_path);
2165 checked_free(ti->sounds_path);
2166 checked_free(ti->music_path);
2168 checked_free(ti->level_filename);
2169 checked_free(ti->level_filetype);
2175 void setSetupInfo(struct TokenInfo *token_info,
2176 int token_nr, char *token_value)
2178 int token_type = token_info[token_nr].type;
2179 void *setup_value = token_info[token_nr].value;
2181 if (token_value == NULL)
2184 /* set setup field to corresponding token value */
2189 *(boolean *)setup_value = get_boolean_from_string(token_value);
2193 *(Key *)setup_value = getKeyFromKeyName(token_value);
2197 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2201 *(int *)setup_value = get_integer_from_string(token_value);
2205 checked_free(*(char **)setup_value);
2206 *(char **)setup_value = getStringCopy(token_value);
2214 static int compareTreeInfoEntries(const void *object1, const void *object2)
2216 const TreeInfo *entry1 = *((TreeInfo **)object1);
2217 const TreeInfo *entry2 = *((TreeInfo **)object2);
2218 int class_sorting1, class_sorting2;
2221 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2223 class_sorting1 = LEVELSORTING(entry1);
2224 class_sorting2 = LEVELSORTING(entry2);
2228 class_sorting1 = ARTWORKSORTING(entry1);
2229 class_sorting2 = ARTWORKSORTING(entry2);
2232 if (entry1->parent_link || entry2->parent_link)
2233 compare_result = (entry1->parent_link ? -1 : +1);
2234 else if (entry1->sort_priority == entry2->sort_priority)
2236 char *name1 = getStringToLower(entry1->name_sorting);
2237 char *name2 = getStringToLower(entry2->name_sorting);
2239 compare_result = strcmp(name1, name2);
2244 else if (class_sorting1 == class_sorting2)
2245 compare_result = entry1->sort_priority - entry2->sort_priority;
2247 compare_result = class_sorting1 - class_sorting2;
2249 return compare_result;
2252 static void createParentTreeInfoNode(TreeInfo *node_parent)
2256 if (node_parent == NULL)
2259 ti_new = newTreeInfo();
2260 setTreeInfoToDefaults(ti_new, node_parent->type);
2262 ti_new->node_parent = node_parent;
2263 ti_new->parent_link = TRUE;
2265 setString(&ti_new->identifier, node_parent->identifier);
2266 setString(&ti_new->name, ".. (parent directory)");
2267 setString(&ti_new->name_sorting, ti_new->name);
2269 setString(&ti_new->subdir, "..");
2270 setString(&ti_new->fullpath, node_parent->fullpath);
2272 ti_new->sort_priority = node_parent->sort_priority;
2273 ti_new->latest_engine = node_parent->latest_engine;
2275 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2277 pushTreeInfo(&node_parent->node_group, ti_new);
2281 /* -------------------------------------------------------------------------- */
2282 /* functions for handling level and custom artwork info cache */
2283 /* -------------------------------------------------------------------------- */
2285 static void LoadArtworkInfoCache()
2287 InitCacheDirectory();
2289 if (artworkinfo_cache_old == NULL)
2291 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2293 /* try to load artwork info hash from already existing cache file */
2294 artworkinfo_cache_old = loadSetupFileHash(filename);
2296 /* if no artwork info cache file was found, start with empty hash */
2297 if (artworkinfo_cache_old == NULL)
2298 artworkinfo_cache_old = newSetupFileHash();
2303 if (artworkinfo_cache_new == NULL)
2304 artworkinfo_cache_new = newSetupFileHash();
2307 static void SaveArtworkInfoCache()
2309 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2311 InitCacheDirectory();
2313 saveSetupFileHash(artworkinfo_cache_new, filename);
2318 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2320 static char *prefix = NULL;
2322 checked_free(prefix);
2324 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2329 /* (identical to above function, but separate string buffer needed -- nasty) */
2330 static char *getCacheToken(char *prefix, char *suffix)
2332 static char *token = NULL;
2334 checked_free(token);
2336 token = getStringCat2WithSeparator(prefix, suffix, ".");
2341 static char *getFileTimestamp(char *filename)
2343 struct stat file_status;
2345 if (stat(filename, &file_status) != 0) /* cannot stat file */
2346 return getStringCopy(i_to_a(0));
2348 return getStringCopy(i_to_a(file_status.st_mtime));
2351 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2353 struct stat file_status;
2355 if (timestamp_string == NULL)
2358 if (stat(filename, &file_status) != 0) /* cannot stat file */
2361 return (file_status.st_mtime != atoi(timestamp_string));
2364 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2366 char *identifier = level_node->subdir;
2367 char *type_string = ARTWORK_DIRECTORY(type);
2368 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2369 char *token_main = getCacheToken(token_prefix, "CACHED");
2370 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2371 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2372 TreeInfo *artwork_info = NULL;
2374 if (!use_artworkinfo_cache)
2381 artwork_info = newTreeInfo();
2382 setTreeInfoToDefaults(artwork_info, type);
2384 /* set all structure fields according to the token/value pairs */
2385 ldi = *artwork_info;
2386 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2388 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2389 char *value = getHashEntry(artworkinfo_cache_old, token);
2391 setSetupInfo(artworkinfo_tokens, i, value);
2393 /* check if cache entry for this item is invalid or incomplete */
2397 Error(ERR_WARN, "cache entry '%s' invalid", token);
2404 *artwork_info = ldi;
2409 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2410 LEVELINFO_FILENAME);
2411 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2412 ARTWORKINFO_FILENAME(type));
2414 /* check if corresponding "levelinfo.conf" file has changed */
2415 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2416 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2418 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2421 /* check if corresponding "<artworkinfo>.conf" file has changed */
2422 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2423 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2425 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2430 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2433 checked_free(filename_levelinfo);
2434 checked_free(filename_artworkinfo);
2437 if (!cached && artwork_info != NULL)
2439 freeTreeInfo(artwork_info);
2444 return artwork_info;
2447 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2448 LevelDirTree *level_node, int type)
2450 char *identifier = level_node->subdir;
2451 char *type_string = ARTWORK_DIRECTORY(type);
2452 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2453 char *token_main = getCacheToken(token_prefix, "CACHED");
2454 boolean set_cache_timestamps = TRUE;
2457 setHashEntry(artworkinfo_cache_new, token_main, "true");
2459 if (set_cache_timestamps)
2461 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2462 LEVELINFO_FILENAME);
2463 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2464 ARTWORKINFO_FILENAME(type));
2465 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2466 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2468 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2469 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2471 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2472 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2474 checked_free(filename_levelinfo);
2475 checked_free(filename_artworkinfo);
2476 checked_free(timestamp_levelinfo);
2477 checked_free(timestamp_artworkinfo);
2480 ldi = *artwork_info;
2481 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2483 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2484 char *value = getSetupValue(artworkinfo_tokens[i].type,
2485 artworkinfo_tokens[i].value);
2487 setHashEntry(artworkinfo_cache_new, token, value);
2492 /* -------------------------------------------------------------------------- */
2493 /* functions for loading level info and custom artwork info */
2494 /* -------------------------------------------------------------------------- */
2496 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2497 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2499 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2500 TreeInfo *node_parent,
2501 char *level_directory,
2502 char *directory_name)
2504 static unsigned long progress_delay = 0;
2505 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2506 char *directory_path = getPath2(level_directory, directory_name);
2507 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2508 SetupFileHash *setup_file_hash;
2509 LevelDirTree *leveldir_new = NULL;
2512 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2513 if (!options.debug && !fileExists(filename))
2515 free(directory_path);
2521 setup_file_hash = loadSetupFileHash(filename);
2523 if (setup_file_hash == NULL)
2525 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2527 free(directory_path);
2533 leveldir_new = newTreeInfo();
2536 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2538 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2540 leveldir_new->subdir = getStringCopy(directory_name);
2542 checkSetupFileHashIdentifier(setup_file_hash, filename,
2543 getCookie("LEVELINFO"));
2545 /* set all structure fields according to the token/value pairs */
2546 ldi = *leveldir_new;
2547 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2548 setSetupInfo(levelinfo_tokens, i,
2549 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2550 *leveldir_new = ldi;
2552 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2553 setString(&leveldir_new->name, leveldir_new->subdir);
2555 if (leveldir_new->identifier == NULL)
2556 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2558 if (leveldir_new->name_sorting == NULL)
2559 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2561 if (node_parent == NULL) /* top level group */
2563 leveldir_new->basepath = getStringCopy(level_directory);
2564 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2566 else /* sub level group */
2568 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2569 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2573 if (leveldir_new->levels < 1)
2574 leveldir_new->levels = 1;
2577 leveldir_new->last_level =
2578 leveldir_new->first_level + leveldir_new->levels - 1;
2580 leveldir_new->in_user_dir =
2581 (!strEqual(leveldir_new->basepath, options.level_directory));
2583 /* adjust some settings if user's private level directory was detected */
2584 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2585 leveldir_new->in_user_dir &&
2586 (strEqual(leveldir_new->subdir, getLoginName()) ||
2587 strEqual(leveldir_new->name, getLoginName()) ||
2588 strEqual(leveldir_new->author, getRealName())))
2590 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2591 leveldir_new->readonly = FALSE;
2594 leveldir_new->user_defined =
2595 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2597 leveldir_new->color = LEVELCOLOR(leveldir_new);
2599 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2601 leveldir_new->handicap_level = /* set handicap to default value */
2602 (leveldir_new->user_defined || !leveldir_new->handicap ?
2603 leveldir_new->last_level : leveldir_new->first_level);
2606 if (leveldir_new->level_group ||
2607 DelayReached(&progress_delay, progress_delay_value))
2608 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2610 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2614 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2616 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2618 /* skip level sets without levels (which are probably artwork base sets) */
2620 freeSetupFileHash(setup_file_hash);
2621 free(directory_path);
2629 pushTreeInfo(node_first, leveldir_new);
2631 freeSetupFileHash(setup_file_hash);
2633 if (leveldir_new->level_group)
2635 /* create node to link back to current level directory */
2636 createParentTreeInfoNode(leveldir_new);
2638 /* recursively step into sub-directory and look for more level series */
2639 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2640 leveldir_new, directory_path);
2643 free(directory_path);
2649 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2650 TreeInfo *node_parent,
2651 char *level_directory)
2654 struct dirent *dir_entry;
2655 boolean valid_entry_found = FALSE;
2657 if ((dir = opendir(level_directory)) == NULL)
2659 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2663 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2665 struct stat file_status;
2666 char *directory_name = dir_entry->d_name;
2667 char *directory_path = getPath2(level_directory, directory_name);
2669 /* skip entries for current and parent directory */
2670 if (strEqual(directory_name, ".") ||
2671 strEqual(directory_name, ".."))
2673 free(directory_path);
2677 /* find out if directory entry is itself a directory */
2678 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2679 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2681 free(directory_path);
2685 free(directory_path);
2687 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2688 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2689 strEqual(directory_name, MUSIC_DIRECTORY))
2692 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2699 /* special case: top level directory may directly contain "levelinfo.conf" */
2700 if (node_parent == NULL && !valid_entry_found)
2702 /* check if this directory directly contains a file "levelinfo.conf" */
2703 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2704 level_directory, ".");
2707 if (!valid_entry_found)
2708 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2712 boolean AdjustGraphicsForEMC()
2714 boolean settings_changed = FALSE;
2716 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2717 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2719 return settings_changed;
2722 void LoadLevelInfo()
2724 InitUserLevelDirectory(getLoginName());
2726 DrawInitText("Loading level series", 120, FC_GREEN);
2728 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2729 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2731 /* after loading all level set information, clone the level directory tree
2732 and remove all level sets without levels (these may still contain artwork
2733 to be offered in the setup menu as "custom artwork", and are therefore
2734 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2735 leveldir_first_all = leveldir_first;
2736 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2738 AdjustGraphicsForEMC();
2740 /* before sorting, the first entries will be from the user directory */
2741 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2743 if (leveldir_first == NULL)
2744 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2746 sortTreeInfo(&leveldir_first);
2749 dumpTreeInfo(leveldir_first, 0);
2753 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2754 TreeInfo *node_parent,
2755 char *base_directory,
2756 char *directory_name, int type)
2758 char *directory_path = getPath2(base_directory, directory_name);
2759 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2760 SetupFileHash *setup_file_hash = NULL;
2761 TreeInfo *artwork_new = NULL;
2764 if (fileExists(filename))
2765 setup_file_hash = loadSetupFileHash(filename);
2767 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2770 struct dirent *dir_entry;
2771 boolean valid_file_found = FALSE;
2773 if ((dir = opendir(directory_path)) != NULL)
2775 while ((dir_entry = readdir(dir)) != NULL)
2777 char *entry_name = dir_entry->d_name;
2779 if (FileIsArtworkType(entry_name, type))
2781 valid_file_found = TRUE;
2789 if (!valid_file_found)
2791 if (!strEqual(directory_name, "."))
2792 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2794 free(directory_path);
2801 artwork_new = newTreeInfo();
2804 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2806 setTreeInfoToDefaults(artwork_new, type);
2808 artwork_new->subdir = getStringCopy(directory_name);
2810 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2813 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2816 /* set all structure fields according to the token/value pairs */
2818 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2819 setSetupInfo(levelinfo_tokens, i,
2820 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2823 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2824 setString(&artwork_new->name, artwork_new->subdir);
2826 if (artwork_new->identifier == NULL)
2827 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2829 if (artwork_new->name_sorting == NULL)
2830 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2833 if (node_parent == NULL) /* top level group */
2835 artwork_new->basepath = getStringCopy(base_directory);
2836 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2838 else /* sub level group */
2840 artwork_new->basepath = getStringCopy(node_parent->basepath);
2841 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2844 artwork_new->in_user_dir =
2845 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2847 /* (may use ".sort_priority" from "setup_file_hash" above) */
2848 artwork_new->color = ARTWORKCOLOR(artwork_new);
2850 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2852 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2854 if (strEqual(artwork_new->subdir, "."))
2856 if (artwork_new->user_defined)
2858 setString(&artwork_new->identifier, "private");
2859 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2863 setString(&artwork_new->identifier, "classic");
2864 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2867 /* set to new values after changing ".sort_priority" */
2868 artwork_new->color = ARTWORKCOLOR(artwork_new);
2870 setString(&artwork_new->class_desc,
2871 getLevelClassDescription(artwork_new));
2875 setString(&artwork_new->identifier, artwork_new->subdir);
2878 setString(&artwork_new->name, artwork_new->identifier);
2879 setString(&artwork_new->name_sorting, artwork_new->name);
2883 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2886 pushTreeInfo(node_first, artwork_new);
2888 freeSetupFileHash(setup_file_hash);
2890 free(directory_path);
2896 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2897 TreeInfo *node_parent,
2898 char *base_directory, int type)
2901 struct dirent *dir_entry;
2902 boolean valid_entry_found = FALSE;
2904 if ((dir = opendir(base_directory)) == NULL)
2906 /* display error if directory is main "options.graphics_directory" etc. */
2907 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2908 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2913 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2915 struct stat file_status;
2916 char *directory_name = dir_entry->d_name;
2917 char *directory_path = getPath2(base_directory, directory_name);
2919 /* skip directory entries for current and parent directory */
2920 if (strEqual(directory_name, ".") ||
2921 strEqual(directory_name, ".."))
2923 free(directory_path);
2927 /* skip directory entries which are not a directory or are not accessible */
2928 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2929 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2931 free(directory_path);
2935 free(directory_path);
2937 /* check if this directory contains artwork with or without config file */
2938 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2940 directory_name, type);
2945 /* check if this directory directly contains artwork itself */
2946 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2947 base_directory, ".",
2949 if (!valid_entry_found)
2950 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2954 static TreeInfo *getDummyArtworkInfo(int type)
2956 /* this is only needed when there is completely no artwork available */
2957 TreeInfo *artwork_new = newTreeInfo();
2959 setTreeInfoToDefaults(artwork_new, type);
2961 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
2962 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2963 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2965 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
2966 setString(&artwork_new->name, UNDEFINED_FILENAME);
2967 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2972 void LoadArtworkInfo()
2974 LoadArtworkInfoCache();
2976 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
2978 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2979 options.graphics_directory,
2980 TREE_TYPE_GRAPHICS_DIR);
2981 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2982 getUserGraphicsDir(),
2983 TREE_TYPE_GRAPHICS_DIR);
2985 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2986 options.sounds_directory,
2987 TREE_TYPE_SOUNDS_DIR);
2988 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2990 TREE_TYPE_SOUNDS_DIR);
2992 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2993 options.music_directory,
2994 TREE_TYPE_MUSIC_DIR);
2995 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2997 TREE_TYPE_MUSIC_DIR);
2999 if (artwork.gfx_first == NULL)
3000 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3001 if (artwork.snd_first == NULL)
3002 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3003 if (artwork.mus_first == NULL)
3004 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3006 /* before sorting, the first entries will be from the user directory */
3007 artwork.gfx_current =
3008 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3009 if (artwork.gfx_current == NULL)
3010 artwork.gfx_current =
3011 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3012 if (artwork.gfx_current == NULL)
3013 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3015 artwork.snd_current =
3016 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3017 if (artwork.snd_current == NULL)
3018 artwork.snd_current =
3019 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3020 if (artwork.snd_current == NULL)
3021 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3023 artwork.mus_current =
3024 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3025 if (artwork.mus_current == NULL)
3026 artwork.mus_current =
3027 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3028 if (artwork.mus_current == NULL)
3029 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3031 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3032 artwork.snd_current_identifier = artwork.snd_current->identifier;
3033 artwork.mus_current_identifier = artwork.mus_current->identifier;
3036 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3037 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3038 printf("music set == %s\n\n", artwork.mus_current_identifier);
3041 sortTreeInfo(&artwork.gfx_first);
3042 sortTreeInfo(&artwork.snd_first);
3043 sortTreeInfo(&artwork.mus_first);
3046 dumpTreeInfo(artwork.gfx_first, 0);
3047 dumpTreeInfo(artwork.snd_first, 0);
3048 dumpTreeInfo(artwork.mus_first, 0);
3052 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3053 LevelDirTree *level_node)
3055 static unsigned long progress_delay = 0;
3056 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3057 int type = (*artwork_node)->type;
3059 /* recursively check all level directories for artwork sub-directories */
3063 /* check all tree entries for artwork, but skip parent link entries */
3064 if (!level_node->parent_link)
3066 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3067 boolean cached = (artwork_new != NULL);
3071 pushTreeInfo(artwork_node, artwork_new);
3075 TreeInfo *topnode_last = *artwork_node;
3076 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3077 ARTWORK_DIRECTORY(type));
3079 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3081 if (topnode_last != *artwork_node) /* check for newly added node */
3083 artwork_new = *artwork_node;
3085 setString(&artwork_new->identifier, level_node->subdir);
3086 setString(&artwork_new->name, level_node->name);
3087 setString(&artwork_new->name_sorting, level_node->name_sorting);
3089 artwork_new->sort_priority = level_node->sort_priority;
3090 artwork_new->color = LEVELCOLOR(artwork_new);
3096 /* insert artwork info (from old cache or filesystem) into new cache */
3097 if (artwork_new != NULL)
3098 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3102 if (level_node->level_group ||
3103 DelayReached(&progress_delay, progress_delay_value))
3104 DrawInitText(level_node->name, 150, FC_YELLOW);
3107 if (level_node->node_group != NULL)
3108 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3110 level_node = level_node->next;
3114 void LoadLevelArtworkInfo()
3116 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3118 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3119 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3120 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3122 SaveArtworkInfoCache();
3124 /* needed for reloading level artwork not known at ealier stage */
3126 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3128 artwork.gfx_current =
3129 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3130 if (artwork.gfx_current == NULL)
3131 artwork.gfx_current =
3132 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3133 if (artwork.gfx_current == NULL)
3134 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3137 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3139 artwork.snd_current =
3140 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3141 if (artwork.snd_current == NULL)
3142 artwork.snd_current =
3143 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3144 if (artwork.snd_current == NULL)
3145 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3148 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3150 artwork.mus_current =
3151 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3152 if (artwork.mus_current == NULL)
3153 artwork.mus_current =
3154 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3155 if (artwork.mus_current == NULL)
3156 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3159 sortTreeInfo(&artwork.gfx_first);
3160 sortTreeInfo(&artwork.snd_first);
3161 sortTreeInfo(&artwork.mus_first);
3164 dumpTreeInfo(artwork.gfx_first, 0);
3165 dumpTreeInfo(artwork.snd_first, 0);
3166 dumpTreeInfo(artwork.mus_first, 0);
3170 static void SaveUserLevelInfo()
3172 LevelDirTree *level_info;
3177 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3179 if (!(file = fopen(filename, MODE_WRITE)))
3181 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3186 level_info = newTreeInfo();
3188 /* always start with reliable default values */
3189 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3191 setString(&level_info->name, getLoginName());
3192 setString(&level_info->author, getRealName());
3193 level_info->levels = 100;
3194 level_info->first_level = 1;
3196 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3198 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3199 getCookie("LEVELINFO")));
3202 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3204 if (i == LEVELINFO_TOKEN_NAME ||
3205 i == LEVELINFO_TOKEN_AUTHOR ||
3206 i == LEVELINFO_TOKEN_LEVELS ||
3207 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3208 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3210 /* just to make things nicer :) */
3211 if (i == LEVELINFO_TOKEN_AUTHOR)
3212 fprintf(file, "\n");
3215 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3219 SetFilePermissions(filename, PERMS_PRIVATE);
3221 freeTreeInfo(level_info);
3225 char *getSetupValue(int type, void *value)
3227 static char value_string[MAX_LINE_LEN];
3235 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3239 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3243 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3247 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3251 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3255 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3259 sprintf(value_string, "%d", *(int *)value);
3263 if (*(char **)value == NULL)
3266 strcpy(value_string, *(char **)value);
3270 value_string[0] = '\0';
3274 if (type & TYPE_GHOSTED)
3275 strcpy(value_string, "n/a");
3277 return value_string;
3280 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3284 static char token_string[MAX_LINE_LEN];
3285 int token_type = token_info[token_nr].type;
3286 void *setup_value = token_info[token_nr].value;
3287 char *token_text = token_info[token_nr].text;
3288 char *value_string = getSetupValue(token_type, setup_value);
3290 /* build complete token string */
3291 sprintf(token_string, "%s%s", prefix, token_text);
3293 /* build setup entry line */
3294 line = getFormattedSetupEntry(token_string, value_string);
3296 if (token_type == TYPE_KEY_X11)
3298 Key key = *(Key *)setup_value;
3299 char *keyname = getKeyNameFromKey(key);
3301 /* add comment, if useful */
3302 if (!strEqual(keyname, "(undefined)") &&
3303 !strEqual(keyname, "(unknown)"))
3305 /* add at least one whitespace */
3307 for (i = strlen(line); i < token_comment_position; i++)
3311 strcat(line, keyname);
3318 void LoadLevelSetup_LastSeries()
3320 /* ----------------------------------------------------------------------- */
3321 /* ~/.<program>/levelsetup.conf */
3322 /* ----------------------------------------------------------------------- */
3324 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3325 SetupFileHash *level_setup_hash = NULL;
3327 /* always start with reliable default values */
3328 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3330 if ((level_setup_hash = loadSetupFileHash(filename)))
3332 char *last_level_series =
3333 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3335 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3337 if (leveldir_current == NULL)
3338 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3340 checkSetupFileHashIdentifier(level_setup_hash, filename,
3341 getCookie("LEVELSETUP"));
3343 freeSetupFileHash(level_setup_hash);
3346 Error(ERR_WARN, "using default setup values");
3351 void SaveLevelSetup_LastSeries()
3353 /* ----------------------------------------------------------------------- */
3354 /* ~/.<program>/levelsetup.conf */
3355 /* ----------------------------------------------------------------------- */
3357 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3358 char *level_subdir = leveldir_current->subdir;
3361 InitUserDataDirectory();
3363 if (!(file = fopen(filename, MODE_WRITE)))
3365 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3370 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3371 getCookie("LEVELSETUP")));
3372 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3377 SetFilePermissions(filename, PERMS_PRIVATE);
3382 static void checkSeriesInfo()
3384 static char *level_directory = NULL;
3386 struct dirent *dir_entry;
3388 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3390 level_directory = getPath2((leveldir_current->in_user_dir ?
3391 getUserLevelDir(NULL) :
3392 options.level_directory),
3393 leveldir_current->fullpath);
3395 if ((dir = opendir(level_directory)) == NULL)
3397 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3401 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3403 if (strlen(dir_entry->d_name) > 4 &&
3404 dir_entry->d_name[3] == '.' &&
3405 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3407 char levelnum_str[4];
3410 strncpy(levelnum_str, dir_entry->d_name, 3);
3411 levelnum_str[3] = '\0';
3413 levelnum_value = atoi(levelnum_str);
3416 if (levelnum_value < leveldir_current->first_level)
3418 Error(ERR_WARN, "additional level %d found", levelnum_value);
3419 leveldir_current->first_level = levelnum_value;
3421 else if (levelnum_value > leveldir_current->last_level)
3423 Error(ERR_WARN, "additional level %d found", levelnum_value);
3424 leveldir_current->last_level = levelnum_value;
3433 void LoadLevelSetup_SeriesInfo()
3436 SetupFileHash *level_setup_hash = NULL;
3437 char *level_subdir = leveldir_current->subdir;
3439 /* always start with reliable default values */
3440 level_nr = leveldir_current->first_level;
3442 checkSeriesInfo(leveldir_current);
3444 /* ----------------------------------------------------------------------- */
3445 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3446 /* ----------------------------------------------------------------------- */
3448 level_subdir = leveldir_current->subdir;
3450 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3452 if ((level_setup_hash = loadSetupFileHash(filename)))
3456 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3460 level_nr = atoi(token_value);
3462 if (level_nr < leveldir_current->first_level)
3463 level_nr = leveldir_current->first_level;
3464 if (level_nr > leveldir_current->last_level)
3465 level_nr = leveldir_current->last_level;
3468 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3472 int level_nr = atoi(token_value);
3474 if (level_nr < leveldir_current->first_level)
3475 level_nr = leveldir_current->first_level;
3476 if (level_nr > leveldir_current->last_level + 1)
3477 level_nr = leveldir_current->last_level;
3479 if (leveldir_current->user_defined || !leveldir_current->handicap)
3480 level_nr = leveldir_current->last_level;
3482 leveldir_current->handicap_level = level_nr;
3485 checkSetupFileHashIdentifier(level_setup_hash, filename,
3486 getCookie("LEVELSETUP"));
3488 freeSetupFileHash(level_setup_hash);
3491 Error(ERR_WARN, "using default setup values");
3496 void SaveLevelSetup_SeriesInfo()
3499 char *level_subdir = leveldir_current->subdir;
3500 char *level_nr_str = int2str(level_nr, 0);
3501 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3504 /* ----------------------------------------------------------------------- */
3505 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3506 /* ----------------------------------------------------------------------- */
3508 InitLevelSetupDirectory(level_subdir);
3510 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3512 if (!(file = fopen(filename, MODE_WRITE)))
3514 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3519 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3520 getCookie("LEVELSETUP")));
3521 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3523 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3524 handicap_level_str));
3528 SetFilePermissions(filename, PERMS_PRIVATE);