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;
1630 if (!(file = fopen(filename, MODE_READ)))
1632 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1638 setup_file_data = newSetupFileHash();
1640 insert_ptr = setup_file_data = newSetupFileList("", "");
1644 /* read next line of input file */
1645 if (!fgets(line, MAX_LINE_LEN, file))
1648 /* cut trailing newline 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 if (read_continued_line)
1655 /* cut leading whitespaces from input line */
1656 for (line_ptr = line; *line_ptr; line_ptr++)
1657 if (*line_ptr != ' ' && *line_ptr != '\t')
1660 /* append new line to existing line, if there is enough space */
1661 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1662 strcat(previous_line, line_ptr);
1664 strcpy(line, previous_line); /* copy storage buffer to line */
1666 read_continued_line = FALSE;
1669 /* if the last character is '\', continue at next line */
1670 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1672 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1673 strcpy(previous_line, line); /* copy line to storage buffer */
1675 read_continued_line = TRUE;
1680 /* cut trailing comment from input line */
1681 for (line_ptr = line; *line_ptr; line_ptr++)
1683 if (*line_ptr == '#')
1690 /* cut trailing whitespaces from input line */
1691 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1692 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1695 /* ignore empty lines */
1699 /* cut leading whitespaces from token */
1700 for (token = line; *token; token++)
1701 if (*token != ' ' && *token != '\t')
1704 /* start with empty value as reliable default */
1707 /* find end of token to determine start of value */
1708 for (line_ptr = token; *line_ptr; line_ptr++)
1711 if (*line_ptr == ':' || *line_ptr == '=')
1713 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1716 *line_ptr = '\0'; /* terminate token string */
1717 value = line_ptr + 1; /* set beginning of value */
1723 /* cut trailing whitespaces from token */
1724 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1725 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1728 /* cut leading whitespaces from value */
1729 for (; *value; value++)
1730 if (*value != ' ' && *value != '\t')
1735 value = "true"; /* treat tokens without value as "true" */
1741 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1743 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1751 if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1752 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1756 SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1757 SetupFileList *first_valid_list_entry = setup_file_list->next;
1759 /* free empty list header */
1760 setup_file_list->next = NULL;
1761 freeSetupFileList(setup_file_list);
1762 setup_file_data = first_valid_list_entry;
1764 if (first_valid_list_entry == NULL)
1765 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1768 return setup_file_data;
1771 void saveSetupFileHash(SetupFileHash *hash, char *filename)
1775 if (!(file = fopen(filename, MODE_WRITE)))
1777 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
1782 BEGIN_HASH_ITERATION(hash, itr)
1784 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
1785 HASH_ITERATION_VALUE(itr)));
1787 END_HASH_ITERATION(hash, itr)
1792 SetupFileList *loadSetupFileList(char *filename)
1794 return (SetupFileList *)loadSetupFileData(filename, FALSE);
1797 SetupFileHash *loadSetupFileHash(char *filename)
1799 return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1802 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1803 char *filename, char *identifier)
1805 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1808 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
1809 else if (!checkCookieString(value, identifier))
1810 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
1814 /* ========================================================================= */
1815 /* setup file stuff */
1816 /* ========================================================================= */
1818 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1819 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1820 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1822 /* level directory info */
1823 #define LEVELINFO_TOKEN_IDENTIFIER 0
1824 #define LEVELINFO_TOKEN_NAME 1
1825 #define LEVELINFO_TOKEN_NAME_SORTING 2
1826 #define LEVELINFO_TOKEN_AUTHOR 3
1827 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
1828 #define LEVELINFO_TOKEN_IMPORTED_BY 5
1829 #define LEVELINFO_TOKEN_LEVELS 6
1830 #define LEVELINFO_TOKEN_FIRST_LEVEL 7
1831 #define LEVELINFO_TOKEN_SORT_PRIORITY 8
1832 #define LEVELINFO_TOKEN_LATEST_ENGINE 9
1833 #define LEVELINFO_TOKEN_LEVEL_GROUP 10
1834 #define LEVELINFO_TOKEN_READONLY 11
1835 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 12
1836 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 13
1837 #define LEVELINFO_TOKEN_GRAPHICS_SET 14
1838 #define LEVELINFO_TOKEN_SOUNDS_SET 15
1839 #define LEVELINFO_TOKEN_MUSIC_SET 16
1840 #define LEVELINFO_TOKEN_FILENAME 17
1841 #define LEVELINFO_TOKEN_FILETYPE 18
1842 #define LEVELINFO_TOKEN_HANDICAP 19
1843 #define LEVELINFO_TOKEN_SKIP_LEVELS 20
1845 #define NUM_LEVELINFO_TOKENS 21
1847 static LevelDirTree ldi;
1849 static struct TokenInfo levelinfo_tokens[] =
1851 /* level directory info */
1852 { TYPE_STRING, &ldi.identifier, "identifier" },
1853 { TYPE_STRING, &ldi.name, "name" },
1854 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1855 { TYPE_STRING, &ldi.author, "author" },
1856 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1857 { TYPE_STRING, &ldi.imported_by, "imported_by" },
1858 { TYPE_INTEGER, &ldi.levels, "levels" },
1859 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1860 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1861 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
1862 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1863 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1864 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
1865 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
1866 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1867 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1868 { TYPE_STRING, &ldi.music_set, "music_set" },
1869 { TYPE_STRING, &ldi.level_filename, "filename" },
1870 { TYPE_STRING, &ldi.level_filetype, "filetype" },
1871 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
1872 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
1875 static struct TokenInfo artworkinfo_tokens[] =
1877 /* artwork directory info */
1878 { TYPE_STRING, &ldi.identifier, "identifier" },
1879 { TYPE_STRING, &ldi.subdir, "subdir" },
1880 { TYPE_STRING, &ldi.name, "name" },
1881 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1882 { TYPE_STRING, &ldi.author, "author" },
1883 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1884 { TYPE_STRING, &ldi.basepath, "basepath" },
1885 { TYPE_STRING, &ldi.fullpath, "fullpath" },
1886 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
1887 { TYPE_INTEGER, &ldi.color, "color" },
1888 { TYPE_STRING, &ldi.class_desc, "class_desc" },
1893 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
1897 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1898 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1899 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1900 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1903 ti->node_parent = NULL;
1904 ti->node_group = NULL;
1911 ti->fullpath = NULL;
1912 ti->basepath = NULL;
1913 ti->identifier = NULL;
1914 ti->name = getStringCopy(ANONYMOUS_NAME);
1915 ti->name_sorting = NULL;
1916 ti->author = getStringCopy(ANONYMOUS_NAME);
1918 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1919 ti->latest_engine = FALSE; /* default: get from level */
1920 ti->parent_link = FALSE;
1921 ti->in_user_dir = FALSE;
1922 ti->user_defined = FALSE;
1924 ti->class_desc = NULL;
1926 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
1928 if (ti->type == TREE_TYPE_LEVEL_DIR)
1930 ti->imported_from = NULL;
1931 ti->imported_by = NULL;
1933 ti->graphics_set_ecs = NULL;
1934 ti->graphics_set_aga = NULL;
1935 ti->graphics_set = NULL;
1936 ti->sounds_set = NULL;
1937 ti->music_set = NULL;
1938 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1939 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1940 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
1942 ti->level_filename = NULL;
1943 ti->level_filetype = NULL;
1946 ti->first_level = 0;
1948 ti->level_group = FALSE;
1949 ti->handicap_level = 0;
1950 ti->readonly = TRUE;
1951 ti->handicap = TRUE;
1952 ti->skip_levels = FALSE;
1956 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
1960 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1962 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
1967 /* copy all values from the parent structure */
1969 ti->type = parent->type;
1971 ti->node_top = parent->node_top;
1972 ti->node_parent = parent;
1973 ti->node_group = NULL;
1980 ti->fullpath = NULL;
1981 ti->basepath = NULL;
1982 ti->identifier = NULL;
1983 ti->name = getStringCopy(ANONYMOUS_NAME);
1984 ti->name_sorting = NULL;
1985 ti->author = getStringCopy(parent->author);
1987 ti->sort_priority = parent->sort_priority;
1988 ti->latest_engine = parent->latest_engine;
1989 ti->parent_link = FALSE;
1990 ti->in_user_dir = parent->in_user_dir;
1991 ti->user_defined = parent->user_defined;
1992 ti->color = parent->color;
1993 ti->class_desc = getStringCopy(parent->class_desc);
1995 ti->infotext = getStringCopy(parent->infotext);
1997 if (ti->type == TREE_TYPE_LEVEL_DIR)
1999 ti->imported_from = getStringCopy(parent->imported_from);
2000 ti->imported_by = getStringCopy(parent->imported_by);
2002 ti->graphics_set_ecs = NULL;
2003 ti->graphics_set_aga = NULL;
2004 ti->graphics_set = NULL;
2005 ti->sounds_set = NULL;
2006 ti->music_set = NULL;
2007 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2008 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2009 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2011 ti->level_filename = NULL;
2012 ti->level_filetype = NULL;
2015 ti->first_level = 0;
2017 ti->level_group = FALSE;
2018 ti->handicap_level = 0;
2019 ti->readonly = TRUE;
2020 ti->handicap = TRUE;
2021 ti->skip_levels = FALSE;
2025 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2027 TreeInfo *ti_copy = newTreeInfo();
2029 /* copy all values from the original structure */
2031 ti_copy->type = ti->type;
2033 ti_copy->node_top = ti->node_top;
2034 ti_copy->node_parent = ti->node_parent;
2035 ti_copy->node_group = ti->node_group;
2036 ti_copy->next = ti->next;
2038 ti_copy->cl_first = ti->cl_first;
2039 ti_copy->cl_cursor = ti->cl_cursor;
2041 ti_copy->subdir = getStringCopy(ti->subdir);
2042 ti_copy->fullpath = getStringCopy(ti->fullpath);
2043 ti_copy->basepath = getStringCopy(ti->basepath);
2044 ti_copy->identifier = getStringCopy(ti->identifier);
2045 ti_copy->name = getStringCopy(ti->name);
2046 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2047 ti_copy->author = getStringCopy(ti->author);
2048 ti_copy->imported_from = getStringCopy(ti->imported_from);
2049 ti_copy->imported_by = getStringCopy(ti->imported_by);
2051 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2052 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2053 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2054 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2055 ti_copy->music_set = getStringCopy(ti->music_set);
2056 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2057 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2058 ti_copy->music_path = getStringCopy(ti->music_path);
2060 ti_copy->level_filename = getStringCopy(ti->level_filename);
2061 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2063 ti_copy->levels = ti->levels;
2064 ti_copy->first_level = ti->first_level;
2065 ti_copy->last_level = ti->last_level;
2066 ti_copy->sort_priority = ti->sort_priority;
2068 ti_copy->latest_engine = ti->latest_engine;
2070 ti_copy->level_group = ti->level_group;
2071 ti_copy->parent_link = ti->parent_link;
2072 ti_copy->in_user_dir = ti->in_user_dir;
2073 ti_copy->user_defined = ti->user_defined;
2074 ti_copy->readonly = ti->readonly;
2075 ti_copy->handicap = ti->handicap;
2076 ti_copy->skip_levels = ti->skip_levels;
2078 ti_copy->color = ti->color;
2079 ti_copy->class_desc = getStringCopy(ti->class_desc);
2080 ti_copy->handicap_level = ti->handicap_level;
2082 ti_copy->infotext = getStringCopy(ti->infotext);
2087 static void freeTreeInfo(TreeInfo *ti)
2092 checked_free(ti->subdir);
2093 checked_free(ti->fullpath);
2094 checked_free(ti->basepath);
2095 checked_free(ti->identifier);
2097 checked_free(ti->name);
2098 checked_free(ti->name_sorting);
2099 checked_free(ti->author);
2101 checked_free(ti->class_desc);
2103 checked_free(ti->infotext);
2105 if (ti->type == TREE_TYPE_LEVEL_DIR)
2107 checked_free(ti->imported_from);
2108 checked_free(ti->imported_by);
2110 checked_free(ti->graphics_set_ecs);
2111 checked_free(ti->graphics_set_aga);
2112 checked_free(ti->graphics_set);
2113 checked_free(ti->sounds_set);
2114 checked_free(ti->music_set);
2116 checked_free(ti->graphics_path);
2117 checked_free(ti->sounds_path);
2118 checked_free(ti->music_path);
2120 checked_free(ti->level_filename);
2121 checked_free(ti->level_filetype);
2127 void setSetupInfo(struct TokenInfo *token_info,
2128 int token_nr, char *token_value)
2130 int token_type = token_info[token_nr].type;
2131 void *setup_value = token_info[token_nr].value;
2133 if (token_value == NULL)
2136 /* set setup field to corresponding token value */
2141 *(boolean *)setup_value = get_boolean_from_string(token_value);
2145 *(Key *)setup_value = getKeyFromKeyName(token_value);
2149 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2153 *(int *)setup_value = get_integer_from_string(token_value);
2157 checked_free(*(char **)setup_value);
2158 *(char **)setup_value = getStringCopy(token_value);
2166 static int compareTreeInfoEntries(const void *object1, const void *object2)
2168 const TreeInfo *entry1 = *((TreeInfo **)object1);
2169 const TreeInfo *entry2 = *((TreeInfo **)object2);
2170 int class_sorting1, class_sorting2;
2173 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2175 class_sorting1 = LEVELSORTING(entry1);
2176 class_sorting2 = LEVELSORTING(entry2);
2180 class_sorting1 = ARTWORKSORTING(entry1);
2181 class_sorting2 = ARTWORKSORTING(entry2);
2184 if (entry1->parent_link || entry2->parent_link)
2185 compare_result = (entry1->parent_link ? -1 : +1);
2186 else if (entry1->sort_priority == entry2->sort_priority)
2188 char *name1 = getStringToLower(entry1->name_sorting);
2189 char *name2 = getStringToLower(entry2->name_sorting);
2191 compare_result = strcmp(name1, name2);
2196 else if (class_sorting1 == class_sorting2)
2197 compare_result = entry1->sort_priority - entry2->sort_priority;
2199 compare_result = class_sorting1 - class_sorting2;
2201 return compare_result;
2204 static void createParentTreeInfoNode(TreeInfo *node_parent)
2208 if (node_parent == NULL)
2211 ti_new = newTreeInfo();
2212 setTreeInfoToDefaults(ti_new, node_parent->type);
2214 ti_new->node_parent = node_parent;
2215 ti_new->parent_link = TRUE;
2217 setString(&ti_new->identifier, node_parent->identifier);
2218 setString(&ti_new->name, ".. (parent directory)");
2219 setString(&ti_new->name_sorting, ti_new->name);
2221 setString(&ti_new->subdir, "..");
2222 setString(&ti_new->fullpath, node_parent->fullpath);
2224 ti_new->sort_priority = node_parent->sort_priority;
2225 ti_new->latest_engine = node_parent->latest_engine;
2227 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2229 pushTreeInfo(&node_parent->node_group, ti_new);
2233 /* -------------------------------------------------------------------------- */
2234 /* functions for handling level and custom artwork info cache */
2235 /* -------------------------------------------------------------------------- */
2237 static void LoadArtworkInfoCache()
2239 InitCacheDirectory();
2241 if (artworkinfo_cache_old == NULL)
2243 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2245 /* try to load artwork info hash from already existing cache file */
2246 artworkinfo_cache_old = loadSetupFileHash(filename);
2248 /* if no artwork info cache file was found, start with empty hash */
2249 if (artworkinfo_cache_old == NULL)
2250 artworkinfo_cache_old = newSetupFileHash();
2255 if (artworkinfo_cache_new == NULL)
2256 artworkinfo_cache_new = newSetupFileHash();
2259 static void SaveArtworkInfoCache()
2261 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2263 InitCacheDirectory();
2265 saveSetupFileHash(artworkinfo_cache_new, filename);
2270 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2272 static char *prefix = NULL;
2274 checked_free(prefix);
2276 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2281 /* (identical to above function, but separate string buffer needed -- nasty) */
2282 static char *getCacheToken(char *prefix, char *suffix)
2284 static char *token = NULL;
2286 checked_free(token);
2288 token = getStringCat2WithSeparator(prefix, suffix, ".");
2293 static char *getFileTimestamp(char *filename)
2295 struct stat file_status;
2297 if (stat(filename, &file_status) != 0) /* cannot stat file */
2298 return getStringCopy(i_to_a(0));
2300 return getStringCopy(i_to_a(file_status.st_mtime));
2303 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2305 struct stat file_status;
2307 if (timestamp_string == NULL)
2310 if (stat(filename, &file_status) != 0) /* cannot stat file */
2313 return (file_status.st_mtime != atoi(timestamp_string));
2316 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2318 char *identifier = level_node->subdir;
2319 char *type_string = ARTWORK_DIRECTORY(type);
2320 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2321 char *token_main = getCacheToken(token_prefix, "CACHED");
2322 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2323 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2324 TreeInfo *artwork_info = NULL;
2326 if (!use_artworkinfo_cache)
2333 artwork_info = newTreeInfo();
2334 setTreeInfoToDefaults(artwork_info, type);
2336 /* set all structure fields according to the token/value pairs */
2337 ldi = *artwork_info;
2338 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2340 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2341 char *value = getHashEntry(artworkinfo_cache_old, token);
2343 setSetupInfo(artworkinfo_tokens, i, value);
2345 /* check if cache entry for this item is invalid or incomplete */
2349 Error(ERR_WARN, "cache entry '%s' invalid", token);
2355 *artwork_info = ldi;
2360 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2361 LEVELINFO_FILENAME);
2362 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2363 ARTWORKINFO_FILENAME(type));
2365 /* check if corresponding "levelinfo.conf" file has changed */
2366 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2367 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2369 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2372 /* check if corresponding "<artworkinfo>.conf" file has changed */
2373 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2374 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2376 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2381 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2384 checked_free(filename_levelinfo);
2385 checked_free(filename_artworkinfo);
2388 if (!cached && artwork_info != NULL)
2390 freeTreeInfo(artwork_info);
2395 return artwork_info;
2398 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2399 LevelDirTree *level_node, int type)
2401 char *identifier = level_node->subdir;
2402 char *type_string = ARTWORK_DIRECTORY(type);
2403 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2404 char *token_main = getCacheToken(token_prefix, "CACHED");
2405 boolean set_cache_timestamps = TRUE;
2408 setHashEntry(artworkinfo_cache_new, token_main, "true");
2410 if (set_cache_timestamps)
2412 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2413 LEVELINFO_FILENAME);
2414 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2415 ARTWORKINFO_FILENAME(type));
2416 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2417 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2419 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2420 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2422 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2423 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2425 checked_free(filename_levelinfo);
2426 checked_free(filename_artworkinfo);
2427 checked_free(timestamp_levelinfo);
2428 checked_free(timestamp_artworkinfo);
2431 ldi = *artwork_info;
2432 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2434 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2435 char *value = getSetupValue(artworkinfo_tokens[i].type,
2436 artworkinfo_tokens[i].value);
2438 setHashEntry(artworkinfo_cache_new, token, value);
2443 /* -------------------------------------------------------------------------- */
2444 /* functions for loading level info and custom artwork info */
2445 /* -------------------------------------------------------------------------- */
2447 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2448 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2450 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2451 TreeInfo *node_parent,
2452 char *level_directory,
2453 char *directory_name)
2455 static unsigned long progress_delay = 0;
2456 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2457 char *directory_path = getPath2(level_directory, directory_name);
2458 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2459 SetupFileHash *setup_file_hash;
2460 LevelDirTree *leveldir_new = NULL;
2463 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2464 if (!options.debug && !fileExists(filename))
2466 free(directory_path);
2472 setup_file_hash = loadSetupFileHash(filename);
2474 if (setup_file_hash == NULL)
2476 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2478 free(directory_path);
2484 leveldir_new = newTreeInfo();
2487 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2489 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2491 leveldir_new->subdir = getStringCopy(directory_name);
2493 checkSetupFileHashIdentifier(setup_file_hash, filename,
2494 getCookie("LEVELINFO"));
2496 /* set all structure fields according to the token/value pairs */
2497 ldi = *leveldir_new;
2498 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2499 setSetupInfo(levelinfo_tokens, i,
2500 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2501 *leveldir_new = ldi;
2503 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2504 setString(&leveldir_new->name, leveldir_new->subdir);
2506 if (leveldir_new->identifier == NULL)
2507 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2509 if (leveldir_new->name_sorting == NULL)
2510 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2512 if (node_parent == NULL) /* top level group */
2514 leveldir_new->basepath = getStringCopy(level_directory);
2515 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2517 else /* sub level group */
2519 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2520 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2524 if (leveldir_new->levels < 1)
2525 leveldir_new->levels = 1;
2528 leveldir_new->last_level =
2529 leveldir_new->first_level + leveldir_new->levels - 1;
2531 leveldir_new->in_user_dir =
2532 (!strEqual(leveldir_new->basepath, options.level_directory));
2534 /* adjust some settings if user's private level directory was detected */
2535 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2536 leveldir_new->in_user_dir &&
2537 (strEqual(leveldir_new->subdir, getLoginName()) ||
2538 strEqual(leveldir_new->name, getLoginName()) ||
2539 strEqual(leveldir_new->author, getRealName())))
2541 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2542 leveldir_new->readonly = FALSE;
2545 leveldir_new->user_defined =
2546 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2548 leveldir_new->color = LEVELCOLOR(leveldir_new);
2550 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2552 leveldir_new->handicap_level = /* set handicap to default value */
2553 (leveldir_new->user_defined || !leveldir_new->handicap ?
2554 leveldir_new->last_level : leveldir_new->first_level);
2557 if (leveldir_new->level_group ||
2558 DelayReached(&progress_delay, progress_delay_value))
2559 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2561 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2565 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2567 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2569 /* skip level sets without levels (which are probably artwork base sets) */
2571 freeSetupFileHash(setup_file_hash);
2572 free(directory_path);
2580 pushTreeInfo(node_first, leveldir_new);
2582 freeSetupFileHash(setup_file_hash);
2584 if (leveldir_new->level_group)
2586 /* create node to link back to current level directory */
2587 createParentTreeInfoNode(leveldir_new);
2589 /* recursively step into sub-directory and look for more level series */
2590 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2591 leveldir_new, directory_path);
2594 free(directory_path);
2600 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2601 TreeInfo *node_parent,
2602 char *level_directory)
2605 struct dirent *dir_entry;
2606 boolean valid_entry_found = FALSE;
2608 if ((dir = opendir(level_directory)) == NULL)
2610 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2614 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2616 struct stat file_status;
2617 char *directory_name = dir_entry->d_name;
2618 char *directory_path = getPath2(level_directory, directory_name);
2620 /* skip entries for current and parent directory */
2621 if (strEqual(directory_name, ".") ||
2622 strEqual(directory_name, ".."))
2624 free(directory_path);
2628 /* find out if directory entry is itself a directory */
2629 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2630 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2632 free(directory_path);
2636 free(directory_path);
2638 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2639 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2640 strEqual(directory_name, MUSIC_DIRECTORY))
2643 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2650 /* special case: top level directory may directly contain "levelinfo.conf" */
2651 if (node_parent == NULL && !valid_entry_found)
2653 /* check if this directory directly contains a file "levelinfo.conf" */
2654 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2655 level_directory, ".");
2658 if (!valid_entry_found)
2659 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2663 boolean AdjustGraphicsForEMC()
2665 boolean settings_changed = FALSE;
2667 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2668 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2670 return settings_changed;
2673 void LoadLevelInfo()
2675 InitUserLevelDirectory(getLoginName());
2677 DrawInitText("Loading level series", 120, FC_GREEN);
2679 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2680 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2682 /* after loading all level set information, clone the level directory tree
2683 and remove all level sets without levels (these may still contain artwork
2684 to be offered in the setup menu as "custom artwork", and are therefore
2685 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2686 leveldir_first_all = leveldir_first;
2687 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2689 AdjustGraphicsForEMC();
2691 /* before sorting, the first entries will be from the user directory */
2692 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2694 if (leveldir_first == NULL)
2695 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2697 sortTreeInfo(&leveldir_first);
2700 dumpTreeInfo(leveldir_first, 0);
2704 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2705 TreeInfo *node_parent,
2706 char *base_directory,
2707 char *directory_name, int type)
2709 char *directory_path = getPath2(base_directory, directory_name);
2710 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2711 SetupFileHash *setup_file_hash = NULL;
2712 TreeInfo *artwork_new = NULL;
2715 if (fileExists(filename))
2716 setup_file_hash = loadSetupFileHash(filename);
2718 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2721 struct dirent *dir_entry;
2722 boolean valid_file_found = FALSE;
2724 if ((dir = opendir(directory_path)) != NULL)
2726 while ((dir_entry = readdir(dir)) != NULL)
2728 char *entry_name = dir_entry->d_name;
2730 if (FileIsArtworkType(entry_name, type))
2732 valid_file_found = TRUE;
2740 if (!valid_file_found)
2742 if (!strEqual(directory_name, "."))
2743 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2745 free(directory_path);
2752 artwork_new = newTreeInfo();
2755 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2757 setTreeInfoToDefaults(artwork_new, type);
2759 artwork_new->subdir = getStringCopy(directory_name);
2761 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2764 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2767 /* set all structure fields according to the token/value pairs */
2769 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2770 setSetupInfo(levelinfo_tokens, i,
2771 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2774 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2775 setString(&artwork_new->name, artwork_new->subdir);
2777 if (artwork_new->identifier == NULL)
2778 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2780 if (artwork_new->name_sorting == NULL)
2781 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2784 if (node_parent == NULL) /* top level group */
2786 artwork_new->basepath = getStringCopy(base_directory);
2787 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2789 else /* sub level group */
2791 artwork_new->basepath = getStringCopy(node_parent->basepath);
2792 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2795 artwork_new->in_user_dir =
2796 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2798 /* (may use ".sort_priority" from "setup_file_hash" above) */
2799 artwork_new->color = ARTWORKCOLOR(artwork_new);
2801 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2803 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2805 if (strEqual(artwork_new->subdir, "."))
2807 if (artwork_new->user_defined)
2809 setString(&artwork_new->identifier, "private");
2810 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2814 setString(&artwork_new->identifier, "classic");
2815 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2818 /* set to new values after changing ".sort_priority" */
2819 artwork_new->color = ARTWORKCOLOR(artwork_new);
2821 setString(&artwork_new->class_desc,
2822 getLevelClassDescription(artwork_new));
2826 setString(&artwork_new->identifier, artwork_new->subdir);
2829 setString(&artwork_new->name, artwork_new->identifier);
2830 setString(&artwork_new->name_sorting, artwork_new->name);
2834 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2837 pushTreeInfo(node_first, artwork_new);
2839 freeSetupFileHash(setup_file_hash);
2841 free(directory_path);
2847 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2848 TreeInfo *node_parent,
2849 char *base_directory, int type)
2852 struct dirent *dir_entry;
2853 boolean valid_entry_found = FALSE;
2855 if ((dir = opendir(base_directory)) == NULL)
2857 /* display error if directory is main "options.graphics_directory" etc. */
2858 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2859 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2864 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2866 struct stat file_status;
2867 char *directory_name = dir_entry->d_name;
2868 char *directory_path = getPath2(base_directory, directory_name);
2870 /* skip directory entries for current and parent directory */
2871 if (strEqual(directory_name, ".") ||
2872 strEqual(directory_name, ".."))
2874 free(directory_path);
2878 /* skip directory entries which are not a directory or are not accessible */
2879 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2880 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2882 free(directory_path);
2886 free(directory_path);
2888 /* check if this directory contains artwork with or without config file */
2889 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2891 directory_name, type);
2896 /* check if this directory directly contains artwork itself */
2897 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2898 base_directory, ".",
2900 if (!valid_entry_found)
2901 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2905 static TreeInfo *getDummyArtworkInfo(int type)
2907 /* this is only needed when there is completely no artwork available */
2908 TreeInfo *artwork_new = newTreeInfo();
2910 setTreeInfoToDefaults(artwork_new, type);
2912 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
2913 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2914 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2916 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
2917 setString(&artwork_new->name, UNDEFINED_FILENAME);
2918 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2923 void LoadArtworkInfo()
2925 LoadArtworkInfoCache();
2927 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
2929 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2930 options.graphics_directory,
2931 TREE_TYPE_GRAPHICS_DIR);
2932 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2933 getUserGraphicsDir(),
2934 TREE_TYPE_GRAPHICS_DIR);
2936 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2937 options.sounds_directory,
2938 TREE_TYPE_SOUNDS_DIR);
2939 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2941 TREE_TYPE_SOUNDS_DIR);
2943 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2944 options.music_directory,
2945 TREE_TYPE_MUSIC_DIR);
2946 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2948 TREE_TYPE_MUSIC_DIR);
2950 if (artwork.gfx_first == NULL)
2951 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2952 if (artwork.snd_first == NULL)
2953 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2954 if (artwork.mus_first == NULL)
2955 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2957 /* before sorting, the first entries will be from the user directory */
2958 artwork.gfx_current =
2959 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2960 if (artwork.gfx_current == NULL)
2961 artwork.gfx_current =
2962 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2963 if (artwork.gfx_current == NULL)
2964 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2966 artwork.snd_current =
2967 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2968 if (artwork.snd_current == NULL)
2969 artwork.snd_current =
2970 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2971 if (artwork.snd_current == NULL)
2972 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2974 artwork.mus_current =
2975 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2976 if (artwork.mus_current == NULL)
2977 artwork.mus_current =
2978 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2979 if (artwork.mus_current == NULL)
2980 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2982 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2983 artwork.snd_current_identifier = artwork.snd_current->identifier;
2984 artwork.mus_current_identifier = artwork.mus_current->identifier;
2987 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2988 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2989 printf("music set == %s\n\n", artwork.mus_current_identifier);
2992 sortTreeInfo(&artwork.gfx_first);
2993 sortTreeInfo(&artwork.snd_first);
2994 sortTreeInfo(&artwork.mus_first);
2997 dumpTreeInfo(artwork.gfx_first, 0);
2998 dumpTreeInfo(artwork.snd_first, 0);
2999 dumpTreeInfo(artwork.mus_first, 0);
3003 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3004 LevelDirTree *level_node)
3006 static unsigned long progress_delay = 0;
3007 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3008 int type = (*artwork_node)->type;
3010 /* recursively check all level directories for artwork sub-directories */
3014 /* check all tree entries for artwork, but skip parent link entries */
3015 if (!level_node->parent_link)
3017 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3018 boolean cached = (artwork_new != NULL);
3022 pushTreeInfo(artwork_node, artwork_new);
3026 TreeInfo *topnode_last = *artwork_node;
3027 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3028 ARTWORK_DIRECTORY(type));
3030 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3032 if (topnode_last != *artwork_node) /* check for newly added node */
3034 artwork_new = *artwork_node;
3036 setString(&artwork_new->identifier, level_node->subdir);
3037 setString(&artwork_new->name, level_node->name);
3038 setString(&artwork_new->name_sorting, level_node->name_sorting);
3040 artwork_new->sort_priority = level_node->sort_priority;
3041 artwork_new->color = LEVELCOLOR(artwork_new);
3047 /* insert artwork info (from old cache or filesystem) into new cache */
3048 if (artwork_new != NULL)
3049 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3053 if (level_node->level_group ||
3054 DelayReached(&progress_delay, progress_delay_value))
3055 DrawInitText(level_node->name, 150, FC_YELLOW);
3058 if (level_node->node_group != NULL)
3059 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3061 level_node = level_node->next;
3065 void LoadLevelArtworkInfo()
3067 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3069 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3070 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3071 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3073 SaveArtworkInfoCache();
3075 /* needed for reloading level artwork not known at ealier stage */
3077 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3079 artwork.gfx_current =
3080 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3081 if (artwork.gfx_current == NULL)
3082 artwork.gfx_current =
3083 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3084 if (artwork.gfx_current == NULL)
3085 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3088 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3090 artwork.snd_current =
3091 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3092 if (artwork.snd_current == NULL)
3093 artwork.snd_current =
3094 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3095 if (artwork.snd_current == NULL)
3096 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3099 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3101 artwork.mus_current =
3102 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3103 if (artwork.mus_current == NULL)
3104 artwork.mus_current =
3105 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3106 if (artwork.mus_current == NULL)
3107 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3110 sortTreeInfo(&artwork.gfx_first);
3111 sortTreeInfo(&artwork.snd_first);
3112 sortTreeInfo(&artwork.mus_first);
3115 dumpTreeInfo(artwork.gfx_first, 0);
3116 dumpTreeInfo(artwork.snd_first, 0);
3117 dumpTreeInfo(artwork.mus_first, 0);
3121 static void SaveUserLevelInfo()
3123 LevelDirTree *level_info;
3128 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3130 if (!(file = fopen(filename, MODE_WRITE)))
3132 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3137 level_info = newTreeInfo();
3139 /* always start with reliable default values */
3140 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3142 setString(&level_info->name, getLoginName());
3143 setString(&level_info->author, getRealName());
3144 level_info->levels = 100;
3145 level_info->first_level = 1;
3147 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3149 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3150 getCookie("LEVELINFO")));
3153 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3155 if (i == LEVELINFO_TOKEN_NAME ||
3156 i == LEVELINFO_TOKEN_AUTHOR ||
3157 i == LEVELINFO_TOKEN_LEVELS ||
3158 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3159 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3161 /* just to make things nicer :) */
3162 if (i == LEVELINFO_TOKEN_AUTHOR)
3163 fprintf(file, "\n");
3166 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3170 SetFilePermissions(filename, PERMS_PRIVATE);
3172 freeTreeInfo(level_info);
3176 char *getSetupValue(int type, void *value)
3178 static char value_string[MAX_LINE_LEN];
3186 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3190 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3194 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3198 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3202 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3206 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3210 sprintf(value_string, "%d", *(int *)value);
3214 if (*(char **)value == NULL)
3217 strcpy(value_string, *(char **)value);
3221 value_string[0] = '\0';
3225 if (type & TYPE_GHOSTED)
3226 strcpy(value_string, "n/a");
3228 return value_string;
3231 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3235 static char token_string[MAX_LINE_LEN];
3236 int token_type = token_info[token_nr].type;
3237 void *setup_value = token_info[token_nr].value;
3238 char *token_text = token_info[token_nr].text;
3239 char *value_string = getSetupValue(token_type, setup_value);
3241 /* build complete token string */
3242 sprintf(token_string, "%s%s", prefix, token_text);
3244 /* build setup entry line */
3245 line = getFormattedSetupEntry(token_string, value_string);
3247 if (token_type == TYPE_KEY_X11)
3249 Key key = *(Key *)setup_value;
3250 char *keyname = getKeyNameFromKey(key);
3252 /* add comment, if useful */
3253 if (!strEqual(keyname, "(undefined)") &&
3254 !strEqual(keyname, "(unknown)"))
3256 /* add at least one whitespace */
3258 for (i = strlen(line); i < token_comment_position; i++)
3262 strcat(line, keyname);
3269 void LoadLevelSetup_LastSeries()
3271 /* ----------------------------------------------------------------------- */
3272 /* ~/.<program>/levelsetup.conf */
3273 /* ----------------------------------------------------------------------- */
3275 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3276 SetupFileHash *level_setup_hash = NULL;
3278 /* always start with reliable default values */
3279 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3281 if ((level_setup_hash = loadSetupFileHash(filename)))
3283 char *last_level_series =
3284 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3286 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3288 if (leveldir_current == NULL)
3289 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3291 checkSetupFileHashIdentifier(level_setup_hash, filename,
3292 getCookie("LEVELSETUP"));
3294 freeSetupFileHash(level_setup_hash);
3297 Error(ERR_WARN, "using default setup values");
3302 void SaveLevelSetup_LastSeries()
3304 /* ----------------------------------------------------------------------- */
3305 /* ~/.<program>/levelsetup.conf */
3306 /* ----------------------------------------------------------------------- */
3308 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3309 char *level_subdir = leveldir_current->subdir;
3312 InitUserDataDirectory();
3314 if (!(file = fopen(filename, MODE_WRITE)))
3316 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3321 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3322 getCookie("LEVELSETUP")));
3323 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3328 SetFilePermissions(filename, PERMS_PRIVATE);
3333 static void checkSeriesInfo()
3335 static char *level_directory = NULL;
3337 struct dirent *dir_entry;
3339 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3341 level_directory = getPath2((leveldir_current->in_user_dir ?
3342 getUserLevelDir(NULL) :
3343 options.level_directory),
3344 leveldir_current->fullpath);
3346 if ((dir = opendir(level_directory)) == NULL)
3348 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3352 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3354 if (strlen(dir_entry->d_name) > 4 &&
3355 dir_entry->d_name[3] == '.' &&
3356 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3358 char levelnum_str[4];
3361 strncpy(levelnum_str, dir_entry->d_name, 3);
3362 levelnum_str[3] = '\0';
3364 levelnum_value = atoi(levelnum_str);
3367 if (levelnum_value < leveldir_current->first_level)
3369 Error(ERR_WARN, "additional level %d found", levelnum_value);
3370 leveldir_current->first_level = levelnum_value;
3372 else if (levelnum_value > leveldir_current->last_level)
3374 Error(ERR_WARN, "additional level %d found", levelnum_value);
3375 leveldir_current->last_level = levelnum_value;
3384 void LoadLevelSetup_SeriesInfo()
3387 SetupFileHash *level_setup_hash = NULL;
3388 char *level_subdir = leveldir_current->subdir;
3390 /* always start with reliable default values */
3391 level_nr = leveldir_current->first_level;
3393 checkSeriesInfo(leveldir_current);
3395 /* ----------------------------------------------------------------------- */
3396 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3397 /* ----------------------------------------------------------------------- */
3399 level_subdir = leveldir_current->subdir;
3401 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3403 if ((level_setup_hash = loadSetupFileHash(filename)))
3407 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3411 level_nr = atoi(token_value);
3413 if (level_nr < leveldir_current->first_level)
3414 level_nr = leveldir_current->first_level;
3415 if (level_nr > leveldir_current->last_level)
3416 level_nr = leveldir_current->last_level;
3419 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3423 int level_nr = atoi(token_value);
3425 if (level_nr < leveldir_current->first_level)
3426 level_nr = leveldir_current->first_level;
3427 if (level_nr > leveldir_current->last_level + 1)
3428 level_nr = leveldir_current->last_level;
3430 if (leveldir_current->user_defined || !leveldir_current->handicap)
3431 level_nr = leveldir_current->last_level;
3433 leveldir_current->handicap_level = level_nr;
3436 checkSetupFileHashIdentifier(level_setup_hash, filename,
3437 getCookie("LEVELSETUP"));
3439 freeSetupFileHash(level_setup_hash);
3442 Error(ERR_WARN, "using default setup values");
3447 void SaveLevelSetup_SeriesInfo()
3450 char *level_subdir = leveldir_current->subdir;
3451 char *level_nr_str = int2str(level_nr, 0);
3452 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3455 /* ----------------------------------------------------------------------- */
3456 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3457 /* ----------------------------------------------------------------------- */
3459 InitLevelSetupDirectory(level_subdir);
3461 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3463 if (!(file = fopen(filename, MODE_WRITE)))
3465 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3470 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3471 getCookie("LEVELSETUP")));
3472 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3474 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3475 handicap_level_str));
3479 SetFilePermissions(filename, PERMS_PRIVATE);