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 *getLevelSetMessageFilename()
503 static char *filename = NULL;
518 for (i = 0; basenames[i] != NULL; i++)
520 checked_free(filename);
521 filename = getPath2(getCurrentLevelDir(), basenames[i]);
523 if (fileExists(filename))
530 static char *getCorrectedArtworkBasename(char *basename)
532 char *basename_corrected = basename;
534 #if defined(PLATFORM_MSDOS)
535 if (program.filename_prefix != NULL)
537 int prefix_len = strlen(program.filename_prefix);
539 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
540 basename_corrected = &basename[prefix_len];
542 /* if corrected filename is still longer than standard MS-DOS filename
543 size (8 characters + 1 dot + 3 characters file extension), shorten
544 filename by writing file extension after 8th basename character */
545 if (strlen(basename_corrected) > 8 + 1 + 3)
547 static char *msdos_filename = NULL;
549 checked_free(msdos_filename);
551 msdos_filename = getStringCopy(basename_corrected);
552 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
554 basename_corrected = msdos_filename;
559 return basename_corrected;
562 char *getCustomImageFilename(char *basename)
564 static char *filename = NULL;
565 boolean skip_setup_artwork = FALSE;
567 checked_free(filename);
569 basename = getCorrectedArtworkBasename(basename);
571 if (!setup.override_level_graphics)
573 /* 1st try: look for special artwork in current level series directory */
574 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
575 if (fileExists(filename))
580 /* check if there is special artwork configured in level series config */
581 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
583 /* 2nd try: look for special artwork configured in level series config */
584 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
585 if (fileExists(filename))
590 /* take missing artwork configured in level set config from default */
591 skip_setup_artwork = TRUE;
595 if (!skip_setup_artwork)
597 /* 3rd try: look for special artwork in configured artwork directory */
598 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
599 if (fileExists(filename))
605 /* 4th try: look for default artwork in new default artwork directory */
606 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
607 if (fileExists(filename))
612 /* 5th try: look for default artwork in old default artwork directory */
613 filename = getPath2(options.graphics_directory, basename);
614 if (fileExists(filename))
617 return NULL; /* cannot find specified artwork file anywhere */
620 char *getCustomSoundFilename(char *basename)
622 static char *filename = NULL;
623 boolean skip_setup_artwork = FALSE;
625 checked_free(filename);
627 basename = getCorrectedArtworkBasename(basename);
629 if (!setup.override_level_sounds)
631 /* 1st try: look for special artwork in current level series directory */
632 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
633 if (fileExists(filename))
638 /* check if there is special artwork configured in level series config */
639 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
641 /* 2nd try: look for special artwork configured in level series config */
642 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
643 if (fileExists(filename))
648 /* take missing artwork configured in level set config from default */
649 skip_setup_artwork = TRUE;
653 if (!skip_setup_artwork)
655 /* 3rd try: look for special artwork in configured artwork directory */
656 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
657 if (fileExists(filename))
663 /* 4th try: look for default artwork in new default artwork directory */
664 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
665 if (fileExists(filename))
670 /* 5th try: look for default artwork in old default artwork directory */
671 filename = getPath2(options.sounds_directory, basename);
672 if (fileExists(filename))
675 return NULL; /* cannot find specified artwork file anywhere */
678 char *getCustomMusicFilename(char *basename)
680 static char *filename = NULL;
681 boolean skip_setup_artwork = FALSE;
683 checked_free(filename);
685 basename = getCorrectedArtworkBasename(basename);
687 if (!setup.override_level_music)
689 /* 1st try: look for special artwork in current level series directory */
690 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
691 if (fileExists(filename))
696 /* check if there is special artwork configured in level series config */
697 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
699 /* 2nd try: look for special artwork configured in level series config */
700 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
701 if (fileExists(filename))
706 /* take missing artwork configured in level set config from default */
707 skip_setup_artwork = TRUE;
711 if (!skip_setup_artwork)
713 /* 3rd try: look for special artwork in configured artwork directory */
714 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
715 if (fileExists(filename))
721 /* 4th try: look for default artwork in new default artwork directory */
722 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
723 if (fileExists(filename))
728 /* 5th try: look for default artwork in old default artwork directory */
729 filename = getPath2(options.music_directory, basename);
730 if (fileExists(filename))
733 return NULL; /* cannot find specified artwork file anywhere */
736 char *getCustomArtworkFilename(char *basename, int type)
738 if (type == ARTWORK_TYPE_GRAPHICS)
739 return getCustomImageFilename(basename);
740 else if (type == ARTWORK_TYPE_SOUNDS)
741 return getCustomSoundFilename(basename);
742 else if (type == ARTWORK_TYPE_MUSIC)
743 return getCustomMusicFilename(basename);
745 return UNDEFINED_FILENAME;
748 char *getCustomArtworkConfigFilename(int type)
750 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
753 char *getCustomArtworkLevelConfigFilename(int type)
755 static char *filename = NULL;
757 checked_free(filename);
759 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
764 char *getCustomMusicDirectory(void)
766 static char *directory = NULL;
767 boolean skip_setup_artwork = FALSE;
769 checked_free(directory);
771 if (!setup.override_level_music)
773 /* 1st try: look for special artwork in current level series directory */
774 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
775 if (fileExists(directory))
780 /* check if there is special artwork configured in level series config */
781 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
783 /* 2nd try: look for special artwork configured in level series config */
784 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
785 if (fileExists(directory))
790 /* take missing artwork configured in level set config from default */
791 skip_setup_artwork = TRUE;
795 if (!skip_setup_artwork)
797 /* 3rd try: look for special artwork in configured artwork directory */
798 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
799 if (fileExists(directory))
805 /* 4th try: look for default artwork in new default artwork directory */
806 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
807 if (fileExists(directory))
812 /* 5th try: look for default artwork in old default artwork directory */
813 directory = getStringCopy(options.music_directory);
814 if (fileExists(directory))
817 return NULL; /* cannot find specified artwork file anywhere */
820 void InitTapeDirectory(char *level_subdir)
822 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
823 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
824 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
827 void InitScoreDirectory(char *level_subdir)
829 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
830 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
831 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
834 static void SaveUserLevelInfo();
836 void InitUserLevelDirectory(char *level_subdir)
838 if (!fileExists(getUserLevelDir(level_subdir)))
840 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
841 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
842 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
848 void InitLevelSetupDirectory(char *level_subdir)
850 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
851 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
852 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
855 void InitCacheDirectory()
857 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
858 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
862 /* ------------------------------------------------------------------------- */
863 /* some functions to handle lists of level and artwork directories */
864 /* ------------------------------------------------------------------------- */
866 TreeInfo *newTreeInfo()
868 return checked_calloc(sizeof(TreeInfo));
871 TreeInfo *newTreeInfo_setDefaults(int type)
873 TreeInfo *ti = newTreeInfo();
875 setTreeInfoToDefaults(ti, type);
880 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
882 node_new->next = *node_first;
883 *node_first = node_new;
886 int numTreeInfo(TreeInfo *node)
899 boolean validLevelSeries(TreeInfo *node)
901 return (node != NULL && !node->node_group && !node->parent_link);
904 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
909 if (node->node_group) /* enter level group (step down into tree) */
910 return getFirstValidTreeInfoEntry(node->node_group);
911 else if (node->parent_link) /* skip start entry of level group */
913 if (node->next) /* get first real level series entry */
914 return getFirstValidTreeInfoEntry(node->next);
915 else /* leave empty level group and go on */
916 return getFirstValidTreeInfoEntry(node->node_parent->next);
918 else /* this seems to be a regular level series */
922 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
927 if (node->node_parent == NULL) /* top level group */
928 return *node->node_top;
929 else /* sub level group */
930 return node->node_parent->node_group;
933 int numTreeInfoInGroup(TreeInfo *node)
935 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
938 int posTreeInfo(TreeInfo *node)
940 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
945 if (node_cmp == node)
949 node_cmp = node_cmp->next;
955 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
957 TreeInfo *node_default = node;
972 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
974 if (identifier == NULL)
979 if (node->node_group)
981 TreeInfo *node_group;
983 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
988 else if (!node->parent_link)
990 if (strEqual(identifier, node->identifier))
1000 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1001 TreeInfo *node, boolean skip_sets_without_levels)
1008 if (!node->parent_link && !node->level_group &&
1009 skip_sets_without_levels && node->levels == 0)
1010 return cloneTreeNode(node_top, node_parent, node->next,
1011 skip_sets_without_levels);
1014 node_new = getTreeInfoCopy(node); /* copy complete node */
1016 node_new = newTreeInfo();
1018 *node_new = *node; /* copy complete node */
1021 node_new->node_top = node_top; /* correct top node link */
1022 node_new->node_parent = node_parent; /* correct parent node link */
1024 if (node->level_group)
1025 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1026 skip_sets_without_levels);
1028 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1029 skip_sets_without_levels);
1034 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1036 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1038 *ti_new = ti_cloned;
1041 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1043 boolean settings_changed = FALSE;
1047 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1048 !strEqual(node->graphics_set, node->graphics_set_ecs))
1050 setString(&node->graphics_set, node->graphics_set_ecs);
1051 settings_changed = TRUE;
1053 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1054 !strEqual(node->graphics_set, node->graphics_set_aga))
1056 setString(&node->graphics_set, node->graphics_set_aga);
1057 settings_changed = TRUE;
1060 if (node->node_group != NULL)
1061 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1066 return settings_changed;
1069 void dumpTreeInfo(TreeInfo *node, int depth)
1073 printf("Dumping TreeInfo:\n");
1077 for (i = 0; i < (depth + 1) * 3; i++)
1080 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1081 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1083 if (node->node_group != NULL)
1084 dumpTreeInfo(node->node_group, depth + 1);
1090 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1091 int (*compare_function)(const void *,
1094 int num_nodes = numTreeInfo(*node_first);
1095 TreeInfo **sort_array;
1096 TreeInfo *node = *node_first;
1102 /* allocate array for sorting structure pointers */
1103 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1105 /* writing structure pointers to sorting array */
1106 while (i < num_nodes && node) /* double boundary check... */
1108 sort_array[i] = node;
1114 /* sorting the structure pointers in the sorting array */
1115 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1118 /* update the linkage of list elements with the sorted node array */
1119 for (i = 0; i < num_nodes - 1; i++)
1120 sort_array[i]->next = sort_array[i + 1];
1121 sort_array[num_nodes - 1]->next = NULL;
1123 /* update the linkage of the main list anchor pointer */
1124 *node_first = sort_array[0];
1128 /* now recursively sort the level group structures */
1132 if (node->node_group != NULL)
1133 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1139 void sortTreeInfo(TreeInfo **node_first)
1141 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1145 /* ========================================================================= */
1146 /* some stuff from "files.c" */
1147 /* ========================================================================= */
1149 #if defined(PLATFORM_WIN32)
1151 #define S_IRGRP S_IRUSR
1154 #define S_IROTH S_IRUSR
1157 #define S_IWGRP S_IWUSR
1160 #define S_IWOTH S_IWUSR
1163 #define S_IXGRP S_IXUSR
1166 #define S_IXOTH S_IXUSR
1169 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1174 #endif /* PLATFORM_WIN32 */
1176 /* file permissions for newly written files */
1177 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1178 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1179 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1181 #define MODE_W_PRIVATE (S_IWUSR)
1182 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1183 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1185 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1186 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1188 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1189 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1193 static char *dir = NULL;
1195 #if defined(PLATFORM_WIN32)
1198 dir = checked_malloc(MAX_PATH + 1);
1200 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1203 #elif defined(PLATFORM_UNIX)
1206 if ((dir = getenv("HOME")) == NULL)
1210 if ((pwd = getpwuid(getuid())) != NULL)
1211 dir = getStringCopy(pwd->pw_dir);
1223 char *getCommonDataDir(void)
1225 static char *common_data_dir = NULL;
1227 #if defined(PLATFORM_WIN32)
1228 if (common_data_dir == NULL)
1230 char *dir = checked_malloc(MAX_PATH + 1);
1232 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1233 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1234 common_data_dir = getPath2(dir, program.userdata_subdir);
1236 common_data_dir = options.rw_base_directory;
1239 if (common_data_dir == NULL)
1240 common_data_dir = options.rw_base_directory;
1243 return common_data_dir;
1246 char *getPersonalDataDir(void)
1248 static char *personal_data_dir = NULL;
1250 #if defined(PLATFORM_MACOSX)
1251 if (personal_data_dir == NULL)
1252 personal_data_dir = getPath2(getHomeDir(), "Documents");
1254 if (personal_data_dir == NULL)
1255 personal_data_dir = getHomeDir();
1258 return personal_data_dir;
1261 char *getUserGameDataDir(void)
1263 static char *user_game_data_dir = NULL;
1265 if (user_game_data_dir == NULL)
1266 user_game_data_dir = getPath2(getPersonalDataDir(),
1267 program.userdata_subdir);
1269 return user_game_data_dir;
1272 void updateUserGameDataDir()
1274 #if defined(PLATFORM_MACOSX)
1275 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1276 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1278 /* convert old Unix style game data directory to Mac OS X style, if needed */
1279 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1281 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1283 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1284 userdata_dir_old, userdata_dir_new);
1286 /* continue using Unix style data directory -- this should not happen */
1287 program.userdata_path = getPath2(getPersonalDataDir(),
1288 program.userdata_subdir_unix);
1292 free(userdata_dir_old);
1298 return getUserGameDataDir();
1301 static mode_t posix_umask(mode_t mask)
1303 #if defined(PLATFORM_UNIX)
1310 static int posix_mkdir(const char *pathname, mode_t mode)
1312 #if defined(PLATFORM_WIN32)
1313 return mkdir(pathname);
1315 return mkdir(pathname, mode);
1319 void createDirectory(char *dir, char *text, int permission_class)
1321 /* leave "other" permissions in umask untouched, but ensure group parts
1322 of USERDATA_DIR_MODE are not masked */
1323 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1324 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1325 mode_t normal_umask = posix_umask(0);
1326 mode_t group_umask = ~(dir_mode & S_IRWXG);
1327 posix_umask(normal_umask & group_umask);
1329 if (!fileExists(dir))
1330 if (posix_mkdir(dir, dir_mode) != 0)
1331 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1333 posix_umask(normal_umask); /* reset normal umask */
1336 void InitUserDataDirectory()
1338 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1341 void SetFilePermissions(char *filename, int permission_class)
1343 chmod(filename, (permission_class == PERMS_PRIVATE ?
1344 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1347 char *getCookie(char *file_type)
1349 static char cookie[MAX_COOKIE_LEN + 1];
1351 if (strlen(program.cookie_prefix) + 1 +
1352 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1353 return "[COOKIE ERROR]"; /* should never happen */
1355 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1356 program.cookie_prefix, file_type,
1357 program.version_major, program.version_minor);
1362 int getFileVersionFromCookieString(const char *cookie)
1364 const char *ptr_cookie1, *ptr_cookie2;
1365 const char *pattern1 = "_FILE_VERSION_";
1366 const char *pattern2 = "?.?";
1367 const int len_cookie = strlen(cookie);
1368 const int len_pattern1 = strlen(pattern1);
1369 const int len_pattern2 = strlen(pattern2);
1370 const int len_pattern = len_pattern1 + len_pattern2;
1371 int version_major, version_minor;
1373 if (len_cookie <= len_pattern)
1376 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1377 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1379 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1382 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1383 ptr_cookie2[1] != '.' ||
1384 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1387 version_major = ptr_cookie2[0] - '0';
1388 version_minor = ptr_cookie2[2] - '0';
1390 return VERSION_IDENT(version_major, version_minor, 0, 0);
1393 boolean checkCookieString(const char *cookie, const char *template)
1395 const char *pattern = "_FILE_VERSION_?.?";
1396 const int len_cookie = strlen(cookie);
1397 const int len_template = strlen(template);
1398 const int len_pattern = strlen(pattern);
1400 if (len_cookie != len_template)
1403 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1409 /* ------------------------------------------------------------------------- */
1410 /* setup file list and hash handling functions */
1411 /* ------------------------------------------------------------------------- */
1413 char *getFormattedSetupEntry(char *token, char *value)
1416 static char entry[MAX_LINE_LEN];
1418 /* if value is an empty string, just return token without value */
1422 /* start with the token and some spaces to format output line */
1423 sprintf(entry, "%s:", token);
1424 for (i = strlen(entry); i < token_value_position; i++)
1427 /* continue with the token's value */
1428 strcat(entry, value);
1433 SetupFileList *newSetupFileList(char *token, char *value)
1435 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1437 new->token = getStringCopy(token);
1438 new->value = getStringCopy(value);
1445 void freeSetupFileList(SetupFileList *list)
1450 checked_free(list->token);
1451 checked_free(list->value);
1454 freeSetupFileList(list->next);
1459 char *getListEntry(SetupFileList *list, char *token)
1464 if (strEqual(list->token, token))
1467 return getListEntry(list->next, token);
1470 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1475 if (strEqual(list->token, token))
1477 checked_free(list->value);
1479 list->value = getStringCopy(value);
1483 else if (list->next == NULL)
1484 return (list->next = newSetupFileList(token, value));
1486 return setListEntry(list->next, token, value);
1489 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1494 if (list->next == NULL)
1495 return (list->next = newSetupFileList(token, value));
1497 return addListEntry(list->next, token, value);
1501 static void printSetupFileList(SetupFileList *list)
1506 printf("token: '%s'\n", list->token);
1507 printf("value: '%s'\n", list->value);
1509 printSetupFileList(list->next);
1514 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1515 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1516 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1517 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1519 #define insert_hash_entry hashtable_insert
1520 #define search_hash_entry hashtable_search
1521 #define change_hash_entry hashtable_change
1522 #define remove_hash_entry hashtable_remove
1525 static unsigned int get_hash_from_key(void *key)
1530 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1531 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1532 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1533 it works better than many other constants, prime or not) has never been
1534 adequately explained.
1536 If you just want to have a good hash function, and cannot wait, djb2
1537 is one of the best string hash functions i know. It has excellent
1538 distribution and speed on many different sets of keys and table sizes.
1539 You are not likely to do better with one of the "well known" functions
1540 such as PJW, K&R, etc.
1542 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1545 char *str = (char *)key;
1546 unsigned int hash = 5381;
1549 while ((c = *str++))
1550 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1555 static int keys_are_equal(void *key1, void *key2)
1557 return (strEqual((char *)key1, (char *)key2));
1560 SetupFileHash *newSetupFileHash()
1562 SetupFileHash *new_hash =
1563 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1565 if (new_hash == NULL)
1566 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1571 void freeSetupFileHash(SetupFileHash *hash)
1576 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1579 char *getHashEntry(SetupFileHash *hash, char *token)
1584 return search_hash_entry(hash, token);
1587 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1594 value_copy = getStringCopy(value);
1596 /* change value; if it does not exist, insert it as new */
1597 if (!change_hash_entry(hash, token, value_copy))
1598 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1599 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1602 char *removeHashEntry(SetupFileHash *hash, char *token)
1607 return remove_hash_entry(hash, token);
1611 static void printSetupFileHash(SetupFileHash *hash)
1613 BEGIN_HASH_ITERATION(hash, itr)
1615 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1616 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1618 END_HASH_ITERATION(hash, itr)
1622 static void *loadSetupFileData(char *filename, boolean use_hash)
1624 char line[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1625 char *token, *value, *line_ptr;
1626 void *setup_file_data, *insert_ptr = NULL;
1627 boolean read_continued_line = FALSE;
1628 boolean token_value_separator_found;
1630 boolean token_value_separator_warning = FALSE;
1634 if (!(file = fopen(filename, MODE_READ)))
1636 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1642 setup_file_data = newSetupFileHash();
1644 insert_ptr = setup_file_data = newSetupFileList("", "");
1648 /* read next line of input file */
1649 if (!fgets(line, MAX_LINE_LEN, file))
1652 /* cut trailing newline or carriage return */
1653 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1654 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1657 if (read_continued_line)
1659 /* cut leading whitespaces from input line */
1660 for (line_ptr = line; *line_ptr; line_ptr++)
1661 if (*line_ptr != ' ' && *line_ptr != '\t')
1664 /* append new line to existing line, if there is enough space */
1665 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1666 strcat(previous_line, line_ptr);
1668 strcpy(line, previous_line); /* copy storage buffer to line */
1670 read_continued_line = FALSE;
1673 /* if the last character is '\', continue at next line */
1674 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1676 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1677 strcpy(previous_line, line); /* copy line to storage buffer */
1679 read_continued_line = TRUE;
1684 /* cut trailing comment from input line */
1685 for (line_ptr = line; *line_ptr; line_ptr++)
1687 if (*line_ptr == '#')
1694 /* cut trailing whitespaces from input line */
1695 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1696 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1699 /* ignore empty lines */
1703 /* cut leading whitespaces from token */
1704 for (token = line; *token; token++)
1705 if (*token != ' ' && *token != '\t')
1708 /* start with empty value as reliable default */
1711 token_value_separator_found = FALSE;
1713 /* find end of token to determine start of value */
1714 for (line_ptr = token; *line_ptr; line_ptr++)
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;
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;
1749 if (token_value_separator_found)
1751 if (!token_value_separator_warning)
1753 Error(ERR_RETURN_LINE, "-");
1754 Error(ERR_WARN, "no valid token/value separator in config file:");
1755 Error(ERR_RETURN, "- config file: '%s'", filename);
1757 token_value_separator_warning = TRUE;
1760 Error(ERR_RETURN, "- no separator in line: '%s'", line);
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);
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);
2403 *artwork_info = ldi;
2408 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2409 LEVELINFO_FILENAME);
2410 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2411 ARTWORKINFO_FILENAME(type));
2413 /* check if corresponding "levelinfo.conf" file has changed */
2414 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2415 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2417 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2420 /* check if corresponding "<artworkinfo>.conf" file has changed */
2421 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2422 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2424 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2429 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2432 checked_free(filename_levelinfo);
2433 checked_free(filename_artworkinfo);
2436 if (!cached && artwork_info != NULL)
2438 freeTreeInfo(artwork_info);
2443 return artwork_info;
2446 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2447 LevelDirTree *level_node, int type)
2449 char *identifier = level_node->subdir;
2450 char *type_string = ARTWORK_DIRECTORY(type);
2451 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2452 char *token_main = getCacheToken(token_prefix, "CACHED");
2453 boolean set_cache_timestamps = TRUE;
2456 setHashEntry(artworkinfo_cache_new, token_main, "true");
2458 if (set_cache_timestamps)
2460 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2461 LEVELINFO_FILENAME);
2462 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2463 ARTWORKINFO_FILENAME(type));
2464 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2465 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2467 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2468 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2470 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2471 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2473 checked_free(filename_levelinfo);
2474 checked_free(filename_artworkinfo);
2475 checked_free(timestamp_levelinfo);
2476 checked_free(timestamp_artworkinfo);
2479 ldi = *artwork_info;
2480 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2482 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2483 char *value = getSetupValue(artworkinfo_tokens[i].type,
2484 artworkinfo_tokens[i].value);
2486 setHashEntry(artworkinfo_cache_new, token, value);
2491 /* -------------------------------------------------------------------------- */
2492 /* functions for loading level info and custom artwork info */
2493 /* -------------------------------------------------------------------------- */
2495 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2496 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2498 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2499 TreeInfo *node_parent,
2500 char *level_directory,
2501 char *directory_name)
2503 static unsigned long progress_delay = 0;
2504 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2505 char *directory_path = getPath2(level_directory, directory_name);
2506 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2507 SetupFileHash *setup_file_hash;
2508 LevelDirTree *leveldir_new = NULL;
2511 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2512 if (!options.debug && !fileExists(filename))
2514 free(directory_path);
2520 setup_file_hash = loadSetupFileHash(filename);
2522 if (setup_file_hash == NULL)
2524 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2526 free(directory_path);
2532 leveldir_new = newTreeInfo();
2535 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2537 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2539 leveldir_new->subdir = getStringCopy(directory_name);
2541 checkSetupFileHashIdentifier(setup_file_hash, filename,
2542 getCookie("LEVELINFO"));
2544 /* set all structure fields according to the token/value pairs */
2545 ldi = *leveldir_new;
2546 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2547 setSetupInfo(levelinfo_tokens, i,
2548 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2549 *leveldir_new = ldi;
2551 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2552 setString(&leveldir_new->name, leveldir_new->subdir);
2554 if (leveldir_new->identifier == NULL)
2555 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2557 if (leveldir_new->name_sorting == NULL)
2558 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2560 if (node_parent == NULL) /* top level group */
2562 leveldir_new->basepath = getStringCopy(level_directory);
2563 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2565 else /* sub level group */
2567 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2568 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2572 if (leveldir_new->levels < 1)
2573 leveldir_new->levels = 1;
2576 leveldir_new->last_level =
2577 leveldir_new->first_level + leveldir_new->levels - 1;
2579 leveldir_new->in_user_dir =
2580 (!strEqual(leveldir_new->basepath, options.level_directory));
2582 /* adjust some settings if user's private level directory was detected */
2583 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2584 leveldir_new->in_user_dir &&
2585 (strEqual(leveldir_new->subdir, getLoginName()) ||
2586 strEqual(leveldir_new->name, getLoginName()) ||
2587 strEqual(leveldir_new->author, getRealName())))
2589 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2590 leveldir_new->readonly = FALSE;
2593 leveldir_new->user_defined =
2594 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2596 leveldir_new->color = LEVELCOLOR(leveldir_new);
2598 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2600 leveldir_new->handicap_level = /* set handicap to default value */
2601 (leveldir_new->user_defined || !leveldir_new->handicap ?
2602 leveldir_new->last_level : leveldir_new->first_level);
2605 if (leveldir_new->level_group ||
2606 DelayReached(&progress_delay, progress_delay_value))
2607 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2609 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2613 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2615 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2617 /* skip level sets without levels (which are probably artwork base sets) */
2619 freeSetupFileHash(setup_file_hash);
2620 free(directory_path);
2628 pushTreeInfo(node_first, leveldir_new);
2630 freeSetupFileHash(setup_file_hash);
2632 if (leveldir_new->level_group)
2634 /* create node to link back to current level directory */
2635 createParentTreeInfoNode(leveldir_new);
2637 /* recursively step into sub-directory and look for more level series */
2638 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2639 leveldir_new, directory_path);
2642 free(directory_path);
2648 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2649 TreeInfo *node_parent,
2650 char *level_directory)
2653 struct dirent *dir_entry;
2654 boolean valid_entry_found = FALSE;
2656 if ((dir = opendir(level_directory)) == NULL)
2658 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2662 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2664 struct stat file_status;
2665 char *directory_name = dir_entry->d_name;
2666 char *directory_path = getPath2(level_directory, directory_name);
2668 /* skip entries for current and parent directory */
2669 if (strEqual(directory_name, ".") ||
2670 strEqual(directory_name, ".."))
2672 free(directory_path);
2676 /* find out if directory entry is itself a directory */
2677 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2678 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2680 free(directory_path);
2684 free(directory_path);
2686 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2687 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2688 strEqual(directory_name, MUSIC_DIRECTORY))
2691 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2698 /* special case: top level directory may directly contain "levelinfo.conf" */
2699 if (node_parent == NULL && !valid_entry_found)
2701 /* check if this directory directly contains a file "levelinfo.conf" */
2702 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2703 level_directory, ".");
2706 if (!valid_entry_found)
2707 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2711 boolean AdjustGraphicsForEMC()
2713 boolean settings_changed = FALSE;
2715 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2716 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2718 return settings_changed;
2721 void LoadLevelInfo()
2723 InitUserLevelDirectory(getLoginName());
2725 DrawInitText("Loading level series", 120, FC_GREEN);
2727 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2728 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2730 /* after loading all level set information, clone the level directory tree
2731 and remove all level sets without levels (these may still contain artwork
2732 to be offered in the setup menu as "custom artwork", and are therefore
2733 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2734 leveldir_first_all = leveldir_first;
2735 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2737 AdjustGraphicsForEMC();
2739 /* before sorting, the first entries will be from the user directory */
2740 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2742 if (leveldir_first == NULL)
2743 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2745 sortTreeInfo(&leveldir_first);
2748 dumpTreeInfo(leveldir_first, 0);
2752 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2753 TreeInfo *node_parent,
2754 char *base_directory,
2755 char *directory_name, int type)
2757 char *directory_path = getPath2(base_directory, directory_name);
2758 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2759 SetupFileHash *setup_file_hash = NULL;
2760 TreeInfo *artwork_new = NULL;
2763 if (fileExists(filename))
2764 setup_file_hash = loadSetupFileHash(filename);
2766 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2769 struct dirent *dir_entry;
2770 boolean valid_file_found = FALSE;
2772 if ((dir = opendir(directory_path)) != NULL)
2774 while ((dir_entry = readdir(dir)) != NULL)
2776 char *entry_name = dir_entry->d_name;
2778 if (FileIsArtworkType(entry_name, type))
2780 valid_file_found = TRUE;
2788 if (!valid_file_found)
2790 if (!strEqual(directory_name, "."))
2791 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2793 free(directory_path);
2800 artwork_new = newTreeInfo();
2803 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2805 setTreeInfoToDefaults(artwork_new, type);
2807 artwork_new->subdir = getStringCopy(directory_name);
2809 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2812 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2815 /* set all structure fields according to the token/value pairs */
2817 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2818 setSetupInfo(levelinfo_tokens, i,
2819 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2822 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2823 setString(&artwork_new->name, artwork_new->subdir);
2825 if (artwork_new->identifier == NULL)
2826 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2828 if (artwork_new->name_sorting == NULL)
2829 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2832 if (node_parent == NULL) /* top level group */
2834 artwork_new->basepath = getStringCopy(base_directory);
2835 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2837 else /* sub level group */
2839 artwork_new->basepath = getStringCopy(node_parent->basepath);
2840 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2843 artwork_new->in_user_dir =
2844 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2846 /* (may use ".sort_priority" from "setup_file_hash" above) */
2847 artwork_new->color = ARTWORKCOLOR(artwork_new);
2849 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2851 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2853 if (strEqual(artwork_new->subdir, "."))
2855 if (artwork_new->user_defined)
2857 setString(&artwork_new->identifier, "private");
2858 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2862 setString(&artwork_new->identifier, "classic");
2863 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2866 /* set to new values after changing ".sort_priority" */
2867 artwork_new->color = ARTWORKCOLOR(artwork_new);
2869 setString(&artwork_new->class_desc,
2870 getLevelClassDescription(artwork_new));
2874 setString(&artwork_new->identifier, artwork_new->subdir);
2877 setString(&artwork_new->name, artwork_new->identifier);
2878 setString(&artwork_new->name_sorting, artwork_new->name);
2882 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2885 pushTreeInfo(node_first, artwork_new);
2887 freeSetupFileHash(setup_file_hash);
2889 free(directory_path);
2895 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2896 TreeInfo *node_parent,
2897 char *base_directory, int type)
2900 struct dirent *dir_entry;
2901 boolean valid_entry_found = FALSE;
2903 if ((dir = opendir(base_directory)) == NULL)
2905 /* display error if directory is main "options.graphics_directory" etc. */
2906 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2907 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2912 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2914 struct stat file_status;
2915 char *directory_name = dir_entry->d_name;
2916 char *directory_path = getPath2(base_directory, directory_name);
2918 /* skip directory entries for current and parent directory */
2919 if (strEqual(directory_name, ".") ||
2920 strEqual(directory_name, ".."))
2922 free(directory_path);
2926 /* skip directory entries which are not a directory or are not accessible */
2927 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2928 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2930 free(directory_path);
2934 free(directory_path);
2936 /* check if this directory contains artwork with or without config file */
2937 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2939 directory_name, type);
2944 /* check if this directory directly contains artwork itself */
2945 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2946 base_directory, ".",
2948 if (!valid_entry_found)
2949 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2953 static TreeInfo *getDummyArtworkInfo(int type)
2955 /* this is only needed when there is completely no artwork available */
2956 TreeInfo *artwork_new = newTreeInfo();
2958 setTreeInfoToDefaults(artwork_new, type);
2960 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
2961 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2962 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2964 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
2965 setString(&artwork_new->name, UNDEFINED_FILENAME);
2966 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2971 void LoadArtworkInfo()
2973 LoadArtworkInfoCache();
2975 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
2977 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2978 options.graphics_directory,
2979 TREE_TYPE_GRAPHICS_DIR);
2980 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2981 getUserGraphicsDir(),
2982 TREE_TYPE_GRAPHICS_DIR);
2984 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2985 options.sounds_directory,
2986 TREE_TYPE_SOUNDS_DIR);
2987 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2989 TREE_TYPE_SOUNDS_DIR);
2991 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2992 options.music_directory,
2993 TREE_TYPE_MUSIC_DIR);
2994 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2996 TREE_TYPE_MUSIC_DIR);
2998 if (artwork.gfx_first == NULL)
2999 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3000 if (artwork.snd_first == NULL)
3001 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3002 if (artwork.mus_first == NULL)
3003 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3005 /* before sorting, the first entries will be from the user directory */
3006 artwork.gfx_current =
3007 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3008 if (artwork.gfx_current == NULL)
3009 artwork.gfx_current =
3010 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3011 if (artwork.gfx_current == NULL)
3012 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3014 artwork.snd_current =
3015 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3016 if (artwork.snd_current == NULL)
3017 artwork.snd_current =
3018 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3019 if (artwork.snd_current == NULL)
3020 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3022 artwork.mus_current =
3023 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3024 if (artwork.mus_current == NULL)
3025 artwork.mus_current =
3026 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3027 if (artwork.mus_current == NULL)
3028 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3030 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3031 artwork.snd_current_identifier = artwork.snd_current->identifier;
3032 artwork.mus_current_identifier = artwork.mus_current->identifier;
3035 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3036 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3037 printf("music set == %s\n\n", artwork.mus_current_identifier);
3040 sortTreeInfo(&artwork.gfx_first);
3041 sortTreeInfo(&artwork.snd_first);
3042 sortTreeInfo(&artwork.mus_first);
3045 dumpTreeInfo(artwork.gfx_first, 0);
3046 dumpTreeInfo(artwork.snd_first, 0);
3047 dumpTreeInfo(artwork.mus_first, 0);
3051 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3052 LevelDirTree *level_node)
3054 static unsigned long progress_delay = 0;
3055 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3056 int type = (*artwork_node)->type;
3058 /* recursively check all level directories for artwork sub-directories */
3062 /* check all tree entries for artwork, but skip parent link entries */
3063 if (!level_node->parent_link)
3065 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3066 boolean cached = (artwork_new != NULL);
3070 pushTreeInfo(artwork_node, artwork_new);
3074 TreeInfo *topnode_last = *artwork_node;
3075 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3076 ARTWORK_DIRECTORY(type));
3078 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3080 if (topnode_last != *artwork_node) /* check for newly added node */
3082 artwork_new = *artwork_node;
3084 setString(&artwork_new->identifier, level_node->subdir);
3085 setString(&artwork_new->name, level_node->name);
3086 setString(&artwork_new->name_sorting, level_node->name_sorting);
3088 artwork_new->sort_priority = level_node->sort_priority;
3089 artwork_new->color = LEVELCOLOR(artwork_new);
3095 /* insert artwork info (from old cache or filesystem) into new cache */
3096 if (artwork_new != NULL)
3097 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3101 if (level_node->level_group ||
3102 DelayReached(&progress_delay, progress_delay_value))
3103 DrawInitText(level_node->name, 150, FC_YELLOW);
3106 if (level_node->node_group != NULL)
3107 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3109 level_node = level_node->next;
3113 void LoadLevelArtworkInfo()
3115 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3117 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3118 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3119 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3121 SaveArtworkInfoCache();
3123 /* needed for reloading level artwork not known at ealier stage */
3125 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3127 artwork.gfx_current =
3128 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3129 if (artwork.gfx_current == NULL)
3130 artwork.gfx_current =
3131 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3132 if (artwork.gfx_current == NULL)
3133 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3136 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3138 artwork.snd_current =
3139 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3140 if (artwork.snd_current == NULL)
3141 artwork.snd_current =
3142 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3143 if (artwork.snd_current == NULL)
3144 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3147 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3149 artwork.mus_current =
3150 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3151 if (artwork.mus_current == NULL)
3152 artwork.mus_current =
3153 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3154 if (artwork.mus_current == NULL)
3155 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3158 sortTreeInfo(&artwork.gfx_first);
3159 sortTreeInfo(&artwork.snd_first);
3160 sortTreeInfo(&artwork.mus_first);
3163 dumpTreeInfo(artwork.gfx_first, 0);
3164 dumpTreeInfo(artwork.snd_first, 0);
3165 dumpTreeInfo(artwork.mus_first, 0);
3169 static void SaveUserLevelInfo()
3171 LevelDirTree *level_info;
3176 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3178 if (!(file = fopen(filename, MODE_WRITE)))
3180 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3185 level_info = newTreeInfo();
3187 /* always start with reliable default values */
3188 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3190 setString(&level_info->name, getLoginName());
3191 setString(&level_info->author, getRealName());
3192 level_info->levels = 100;
3193 level_info->first_level = 1;
3195 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3197 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3198 getCookie("LEVELINFO")));
3201 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3203 if (i == LEVELINFO_TOKEN_NAME ||
3204 i == LEVELINFO_TOKEN_AUTHOR ||
3205 i == LEVELINFO_TOKEN_LEVELS ||
3206 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3207 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3209 /* just to make things nicer :) */
3210 if (i == LEVELINFO_TOKEN_AUTHOR)
3211 fprintf(file, "\n");
3214 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3218 SetFilePermissions(filename, PERMS_PRIVATE);
3220 freeTreeInfo(level_info);
3224 char *getSetupValue(int type, void *value)
3226 static char value_string[MAX_LINE_LEN];
3234 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3238 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3242 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3246 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3250 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3254 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3258 sprintf(value_string, "%d", *(int *)value);
3262 if (*(char **)value == NULL)
3265 strcpy(value_string, *(char **)value);
3269 value_string[0] = '\0';
3273 if (type & TYPE_GHOSTED)
3274 strcpy(value_string, "n/a");
3276 return value_string;
3279 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3283 static char token_string[MAX_LINE_LEN];
3284 int token_type = token_info[token_nr].type;
3285 void *setup_value = token_info[token_nr].value;
3286 char *token_text = token_info[token_nr].text;
3287 char *value_string = getSetupValue(token_type, setup_value);
3289 /* build complete token string */
3290 sprintf(token_string, "%s%s", prefix, token_text);
3292 /* build setup entry line */
3293 line = getFormattedSetupEntry(token_string, value_string);
3295 if (token_type == TYPE_KEY_X11)
3297 Key key = *(Key *)setup_value;
3298 char *keyname = getKeyNameFromKey(key);
3300 /* add comment, if useful */
3301 if (!strEqual(keyname, "(undefined)") &&
3302 !strEqual(keyname, "(unknown)"))
3304 /* add at least one whitespace */
3306 for (i = strlen(line); i < token_comment_position; i++)
3310 strcat(line, keyname);
3317 void LoadLevelSetup_LastSeries()
3319 /* ----------------------------------------------------------------------- */
3320 /* ~/.<program>/levelsetup.conf */
3321 /* ----------------------------------------------------------------------- */
3323 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3324 SetupFileHash *level_setup_hash = NULL;
3326 /* always start with reliable default values */
3327 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3329 if ((level_setup_hash = loadSetupFileHash(filename)))
3331 char *last_level_series =
3332 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3334 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3336 if (leveldir_current == NULL)
3337 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3339 checkSetupFileHashIdentifier(level_setup_hash, filename,
3340 getCookie("LEVELSETUP"));
3342 freeSetupFileHash(level_setup_hash);
3345 Error(ERR_WARN, "using default setup values");
3350 void SaveLevelSetup_LastSeries()
3352 /* ----------------------------------------------------------------------- */
3353 /* ~/.<program>/levelsetup.conf */
3354 /* ----------------------------------------------------------------------- */
3356 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3357 char *level_subdir = leveldir_current->subdir;
3360 InitUserDataDirectory();
3362 if (!(file = fopen(filename, MODE_WRITE)))
3364 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3369 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3370 getCookie("LEVELSETUP")));
3371 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3376 SetFilePermissions(filename, PERMS_PRIVATE);
3381 static void checkSeriesInfo()
3383 static char *level_directory = NULL;
3385 struct dirent *dir_entry;
3387 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3389 level_directory = getPath2((leveldir_current->in_user_dir ?
3390 getUserLevelDir(NULL) :
3391 options.level_directory),
3392 leveldir_current->fullpath);
3394 if ((dir = opendir(level_directory)) == NULL)
3396 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3400 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3402 if (strlen(dir_entry->d_name) > 4 &&
3403 dir_entry->d_name[3] == '.' &&
3404 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3406 char levelnum_str[4];
3409 strncpy(levelnum_str, dir_entry->d_name, 3);
3410 levelnum_str[3] = '\0';
3412 levelnum_value = atoi(levelnum_str);
3415 if (levelnum_value < leveldir_current->first_level)
3417 Error(ERR_WARN, "additional level %d found", levelnum_value);
3418 leveldir_current->first_level = levelnum_value;
3420 else if (levelnum_value > leveldir_current->last_level)
3422 Error(ERR_WARN, "additional level %d found", levelnum_value);
3423 leveldir_current->last_level = levelnum_value;
3432 void LoadLevelSetup_SeriesInfo()
3435 SetupFileHash *level_setup_hash = NULL;
3436 char *level_subdir = leveldir_current->subdir;
3438 /* always start with reliable default values */
3439 level_nr = leveldir_current->first_level;
3441 checkSeriesInfo(leveldir_current);
3443 /* ----------------------------------------------------------------------- */
3444 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3445 /* ----------------------------------------------------------------------- */
3447 level_subdir = leveldir_current->subdir;
3449 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3451 if ((level_setup_hash = loadSetupFileHash(filename)))
3455 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3459 level_nr = atoi(token_value);
3461 if (level_nr < leveldir_current->first_level)
3462 level_nr = leveldir_current->first_level;
3463 if (level_nr > leveldir_current->last_level)
3464 level_nr = leveldir_current->last_level;
3467 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3471 int level_nr = atoi(token_value);
3473 if (level_nr < leveldir_current->first_level)
3474 level_nr = leveldir_current->first_level;
3475 if (level_nr > leveldir_current->last_level + 1)
3476 level_nr = leveldir_current->last_level;
3478 if (leveldir_current->user_defined || !leveldir_current->handicap)
3479 level_nr = leveldir_current->last_level;
3481 leveldir_current->handicap_level = level_nr;
3484 checkSetupFileHashIdentifier(level_setup_hash, filename,
3485 getCookie("LEVELSETUP"));
3487 freeSetupFileHash(level_setup_hash);
3490 Error(ERR_WARN, "using default setup values");
3495 void SaveLevelSetup_SeriesInfo()
3498 char *level_subdir = leveldir_current->subdir;
3499 char *level_nr_str = int2str(level_nr, 0);
3500 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3503 /* ----------------------------------------------------------------------- */
3504 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3505 /* ----------------------------------------------------------------------- */
3507 InitLevelSetupDirectory(level_subdir);
3509 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3511 if (!(file = fopen(filename, MODE_WRITE)))
3513 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3518 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3519 getCookie("LEVELSETUP")));
3520 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3522 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3523 handicap_level_str));
3527 SetFilePermissions(filename, PERMS_PRIVATE);