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 printf("::: - WARNING: cache entry '%s' invalid\n", 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 char *directory_path = getPath2(level_directory, directory_name);
2456 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2457 SetupFileHash *setup_file_hash;
2458 LevelDirTree *leveldir_new = NULL;
2461 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2462 if (!options.debug && !fileExists(filename))
2464 free(directory_path);
2470 setup_file_hash = loadSetupFileHash(filename);
2472 if (setup_file_hash == NULL)
2474 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2476 free(directory_path);
2482 leveldir_new = newTreeInfo();
2485 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2487 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2489 leveldir_new->subdir = getStringCopy(directory_name);
2491 checkSetupFileHashIdentifier(setup_file_hash, filename,
2492 getCookie("LEVELINFO"));
2494 /* set all structure fields according to the token/value pairs */
2495 ldi = *leveldir_new;
2496 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2497 setSetupInfo(levelinfo_tokens, i,
2498 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2499 *leveldir_new = ldi;
2501 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2502 setString(&leveldir_new->name, leveldir_new->subdir);
2504 if (leveldir_new->identifier == NULL)
2505 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2507 if (leveldir_new->name_sorting == NULL)
2508 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2510 if (node_parent == NULL) /* top level group */
2512 leveldir_new->basepath = getStringCopy(level_directory);
2513 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2515 else /* sub level group */
2517 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2518 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2522 if (leveldir_new->levels < 1)
2523 leveldir_new->levels = 1;
2526 leveldir_new->last_level =
2527 leveldir_new->first_level + leveldir_new->levels - 1;
2529 leveldir_new->in_user_dir =
2530 (!strEqual(leveldir_new->basepath, options.level_directory));
2532 /* adjust some settings if user's private level directory was detected */
2533 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2534 leveldir_new->in_user_dir &&
2535 (strEqual(leveldir_new->subdir, getLoginName()) ||
2536 strEqual(leveldir_new->name, getLoginName()) ||
2537 strEqual(leveldir_new->author, getRealName())))
2539 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2540 leveldir_new->readonly = FALSE;
2543 leveldir_new->user_defined =
2544 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2546 leveldir_new->color = LEVELCOLOR(leveldir_new);
2548 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2550 leveldir_new->handicap_level = /* set handicap to default value */
2551 (leveldir_new->user_defined || !leveldir_new->handicap ?
2552 leveldir_new->last_level : leveldir_new->first_level);
2555 if (leveldir_new->level_group)
2556 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2558 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2562 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2564 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2566 /* skip level sets without levels (which are probably artwork base sets) */
2568 freeSetupFileHash(setup_file_hash);
2569 free(directory_path);
2577 pushTreeInfo(node_first, leveldir_new);
2579 freeSetupFileHash(setup_file_hash);
2581 if (leveldir_new->level_group)
2583 /* create node to link back to current level directory */
2584 createParentTreeInfoNode(leveldir_new);
2586 /* recursively step into sub-directory and look for more level series */
2587 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2588 leveldir_new, directory_path);
2591 free(directory_path);
2597 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2598 TreeInfo *node_parent,
2599 char *level_directory)
2602 struct dirent *dir_entry;
2603 boolean valid_entry_found = FALSE;
2605 if ((dir = opendir(level_directory)) == NULL)
2607 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2611 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2613 struct stat file_status;
2614 char *directory_name = dir_entry->d_name;
2615 char *directory_path = getPath2(level_directory, directory_name);
2617 /* skip entries for current and parent directory */
2618 if (strEqual(directory_name, ".") ||
2619 strEqual(directory_name, ".."))
2621 free(directory_path);
2625 /* find out if directory entry is itself a directory */
2626 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2627 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2629 free(directory_path);
2633 free(directory_path);
2635 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2636 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2637 strEqual(directory_name, MUSIC_DIRECTORY))
2640 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2647 /* special case: top level directory may directly contain "levelinfo.conf" */
2648 if (node_parent == NULL && !valid_entry_found)
2650 /* check if this directory directly contains a file "levelinfo.conf" */
2651 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2652 level_directory, ".");
2655 if (!valid_entry_found)
2656 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2660 boolean AdjustGraphicsForEMC()
2662 boolean settings_changed = FALSE;
2664 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2665 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2667 return settings_changed;
2670 void LoadLevelInfo()
2672 InitUserLevelDirectory(getLoginName());
2674 DrawInitText("Loading level series", 120, FC_GREEN);
2676 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2677 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2679 /* after loading all level set information, clone the level directory tree
2680 and remove all level sets without levels (these may still contain artwork
2681 to be offered in the setup menu as "custom artwork", and are therefore
2682 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2683 leveldir_first_all = leveldir_first;
2684 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2686 AdjustGraphicsForEMC();
2688 /* before sorting, the first entries will be from the user directory */
2689 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2691 if (leveldir_first == NULL)
2692 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2694 sortTreeInfo(&leveldir_first);
2697 dumpTreeInfo(leveldir_first, 0);
2701 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2702 TreeInfo *node_parent,
2703 char *base_directory,
2704 char *directory_name, int type)
2706 char *directory_path = getPath2(base_directory, directory_name);
2707 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2708 SetupFileHash *setup_file_hash = NULL;
2709 TreeInfo *artwork_new = NULL;
2712 if (fileExists(filename))
2713 setup_file_hash = loadSetupFileHash(filename);
2715 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2718 struct dirent *dir_entry;
2719 boolean valid_file_found = FALSE;
2721 if ((dir = opendir(directory_path)) != NULL)
2723 while ((dir_entry = readdir(dir)) != NULL)
2725 char *entry_name = dir_entry->d_name;
2727 if (FileIsArtworkType(entry_name, type))
2729 valid_file_found = TRUE;
2737 if (!valid_file_found)
2739 if (!strEqual(directory_name, "."))
2740 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2742 free(directory_path);
2749 artwork_new = newTreeInfo();
2752 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2754 setTreeInfoToDefaults(artwork_new, type);
2756 artwork_new->subdir = getStringCopy(directory_name);
2758 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2761 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2764 /* set all structure fields according to the token/value pairs */
2766 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2767 setSetupInfo(levelinfo_tokens, i,
2768 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2771 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2772 setString(&artwork_new->name, artwork_new->subdir);
2774 if (artwork_new->identifier == NULL)
2775 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2777 if (artwork_new->name_sorting == NULL)
2778 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2781 if (node_parent == NULL) /* top level group */
2783 artwork_new->basepath = getStringCopy(base_directory);
2784 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2786 else /* sub level group */
2788 artwork_new->basepath = getStringCopy(node_parent->basepath);
2789 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2792 artwork_new->in_user_dir =
2793 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2795 /* (may use ".sort_priority" from "setup_file_hash" above) */
2796 artwork_new->color = ARTWORKCOLOR(artwork_new);
2798 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2800 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2802 if (strEqual(artwork_new->subdir, "."))
2804 if (artwork_new->user_defined)
2806 setString(&artwork_new->identifier, "private");
2807 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2811 setString(&artwork_new->identifier, "classic");
2812 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2815 /* set to new values after changing ".sort_priority" */
2816 artwork_new->color = ARTWORKCOLOR(artwork_new);
2818 setString(&artwork_new->class_desc,
2819 getLevelClassDescription(artwork_new));
2823 setString(&artwork_new->identifier, artwork_new->subdir);
2826 setString(&artwork_new->name, artwork_new->identifier);
2827 setString(&artwork_new->name_sorting, artwork_new->name);
2831 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2834 pushTreeInfo(node_first, artwork_new);
2836 freeSetupFileHash(setup_file_hash);
2838 free(directory_path);
2844 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2845 TreeInfo *node_parent,
2846 char *base_directory, int type)
2849 struct dirent *dir_entry;
2850 boolean valid_entry_found = FALSE;
2852 if ((dir = opendir(base_directory)) == NULL)
2854 /* display error if directory is main "options.graphics_directory" etc. */
2855 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2856 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2861 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2863 struct stat file_status;
2864 char *directory_name = dir_entry->d_name;
2865 char *directory_path = getPath2(base_directory, directory_name);
2867 /* skip directory entries for current and parent directory */
2868 if (strEqual(directory_name, ".") ||
2869 strEqual(directory_name, ".."))
2871 free(directory_path);
2875 /* skip directory entries which are not a directory or are not accessible */
2876 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2877 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2879 free(directory_path);
2883 free(directory_path);
2885 /* check if this directory contains artwork with or without config file */
2886 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2888 directory_name, type);
2893 /* check if this directory directly contains artwork itself */
2894 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2895 base_directory, ".",
2897 if (!valid_entry_found)
2898 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2902 static TreeInfo *getDummyArtworkInfo(int type)
2904 /* this is only needed when there is completely no artwork available */
2905 TreeInfo *artwork_new = newTreeInfo();
2907 setTreeInfoToDefaults(artwork_new, type);
2909 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
2910 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2911 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2913 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
2914 setString(&artwork_new->name, UNDEFINED_FILENAME);
2915 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2920 void LoadArtworkInfo()
2922 LoadArtworkInfoCache();
2924 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
2926 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2927 options.graphics_directory,
2928 TREE_TYPE_GRAPHICS_DIR);
2929 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2930 getUserGraphicsDir(),
2931 TREE_TYPE_GRAPHICS_DIR);
2933 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2934 options.sounds_directory,
2935 TREE_TYPE_SOUNDS_DIR);
2936 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2938 TREE_TYPE_SOUNDS_DIR);
2940 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2941 options.music_directory,
2942 TREE_TYPE_MUSIC_DIR);
2943 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2945 TREE_TYPE_MUSIC_DIR);
2947 if (artwork.gfx_first == NULL)
2948 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2949 if (artwork.snd_first == NULL)
2950 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2951 if (artwork.mus_first == NULL)
2952 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2954 /* before sorting, the first entries will be from the user directory */
2955 artwork.gfx_current =
2956 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2957 if (artwork.gfx_current == NULL)
2958 artwork.gfx_current =
2959 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2960 if (artwork.gfx_current == NULL)
2961 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2963 artwork.snd_current =
2964 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2965 if (artwork.snd_current == NULL)
2966 artwork.snd_current =
2967 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2968 if (artwork.snd_current == NULL)
2969 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2971 artwork.mus_current =
2972 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2973 if (artwork.mus_current == NULL)
2974 artwork.mus_current =
2975 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2976 if (artwork.mus_current == NULL)
2977 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2979 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2980 artwork.snd_current_identifier = artwork.snd_current->identifier;
2981 artwork.mus_current_identifier = artwork.mus_current->identifier;
2984 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2985 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2986 printf("music set == %s\n\n", artwork.mus_current_identifier);
2989 sortTreeInfo(&artwork.gfx_first);
2990 sortTreeInfo(&artwork.snd_first);
2991 sortTreeInfo(&artwork.mus_first);
2994 dumpTreeInfo(artwork.gfx_first, 0);
2995 dumpTreeInfo(artwork.snd_first, 0);
2996 dumpTreeInfo(artwork.mus_first, 0);
3000 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3001 LevelDirTree *level_node)
3003 int type = (*artwork_node)->type;
3005 /* recursively check all level directories for artwork sub-directories */
3009 /* check all tree entries for artwork, but skip parent link entries */
3010 if (!level_node->parent_link)
3012 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3013 boolean cached = (artwork_new != NULL);
3017 pushTreeInfo(artwork_node, artwork_new);
3021 TreeInfo *topnode_last = *artwork_node;
3022 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3023 ARTWORK_DIRECTORY(type));
3025 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3027 if (topnode_last != *artwork_node) /* check for newly added node */
3029 artwork_new = *artwork_node;
3031 setString(&artwork_new->identifier, level_node->subdir);
3032 setString(&artwork_new->name, level_node->name);
3033 setString(&artwork_new->name_sorting, level_node->name_sorting);
3035 artwork_new->sort_priority = level_node->sort_priority;
3036 artwork_new->color = LEVELCOLOR(artwork_new);
3042 /* insert artwork info (from old cache or filesystem) into new cache */
3043 if (artwork_new != NULL)
3044 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3048 if (level_node->level_group)
3049 DrawInitText(level_node->name, 150, FC_YELLOW);
3052 if (level_node->node_group != NULL)
3053 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3055 level_node = level_node->next;
3059 void LoadLevelArtworkInfo()
3061 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3063 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3064 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3065 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3067 SaveArtworkInfoCache();
3069 /* needed for reloading level artwork not known at ealier stage */
3071 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3073 artwork.gfx_current =
3074 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3075 if (artwork.gfx_current == NULL)
3076 artwork.gfx_current =
3077 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3078 if (artwork.gfx_current == NULL)
3079 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3082 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3084 artwork.snd_current =
3085 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3086 if (artwork.snd_current == NULL)
3087 artwork.snd_current =
3088 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3089 if (artwork.snd_current == NULL)
3090 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3093 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3095 artwork.mus_current =
3096 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3097 if (artwork.mus_current == NULL)
3098 artwork.mus_current =
3099 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3100 if (artwork.mus_current == NULL)
3101 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3104 sortTreeInfo(&artwork.gfx_first);
3105 sortTreeInfo(&artwork.snd_first);
3106 sortTreeInfo(&artwork.mus_first);
3109 dumpTreeInfo(artwork.gfx_first, 0);
3110 dumpTreeInfo(artwork.snd_first, 0);
3111 dumpTreeInfo(artwork.mus_first, 0);
3115 static void SaveUserLevelInfo()
3117 LevelDirTree *level_info;
3122 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3124 if (!(file = fopen(filename, MODE_WRITE)))
3126 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3131 level_info = newTreeInfo();
3133 /* always start with reliable default values */
3134 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3136 setString(&level_info->name, getLoginName());
3137 setString(&level_info->author, getRealName());
3138 level_info->levels = 100;
3139 level_info->first_level = 1;
3141 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3143 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3144 getCookie("LEVELINFO")));
3147 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3149 if (i == LEVELINFO_TOKEN_NAME ||
3150 i == LEVELINFO_TOKEN_AUTHOR ||
3151 i == LEVELINFO_TOKEN_LEVELS ||
3152 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3153 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3155 /* just to make things nicer :) */
3156 if (i == LEVELINFO_TOKEN_AUTHOR)
3157 fprintf(file, "\n");
3160 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3164 SetFilePermissions(filename, PERMS_PRIVATE);
3166 freeTreeInfo(level_info);
3170 char *getSetupValue(int type, void *value)
3172 static char value_string[MAX_LINE_LEN];
3180 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3184 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3188 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3192 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3196 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3200 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3204 sprintf(value_string, "%d", *(int *)value);
3208 if (*(char **)value == NULL)
3211 strcpy(value_string, *(char **)value);
3215 value_string[0] = '\0';
3219 if (type & TYPE_GHOSTED)
3220 strcpy(value_string, "n/a");
3222 return value_string;
3225 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3229 static char token_string[MAX_LINE_LEN];
3230 int token_type = token_info[token_nr].type;
3231 void *setup_value = token_info[token_nr].value;
3232 char *token_text = token_info[token_nr].text;
3233 char *value_string = getSetupValue(token_type, setup_value);
3235 /* build complete token string */
3236 sprintf(token_string, "%s%s", prefix, token_text);
3238 /* build setup entry line */
3239 line = getFormattedSetupEntry(token_string, value_string);
3241 if (token_type == TYPE_KEY_X11)
3243 Key key = *(Key *)setup_value;
3244 char *keyname = getKeyNameFromKey(key);
3246 /* add comment, if useful */
3247 if (!strEqual(keyname, "(undefined)") &&
3248 !strEqual(keyname, "(unknown)"))
3250 /* add at least one whitespace */
3252 for (i = strlen(line); i < token_comment_position; i++)
3256 strcat(line, keyname);
3263 void LoadLevelSetup_LastSeries()
3265 /* ----------------------------------------------------------------------- */
3266 /* ~/.<program>/levelsetup.conf */
3267 /* ----------------------------------------------------------------------- */
3269 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3270 SetupFileHash *level_setup_hash = NULL;
3272 /* always start with reliable default values */
3273 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3275 if ((level_setup_hash = loadSetupFileHash(filename)))
3277 char *last_level_series =
3278 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3280 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3282 if (leveldir_current == NULL)
3283 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3285 checkSetupFileHashIdentifier(level_setup_hash, filename,
3286 getCookie("LEVELSETUP"));
3288 freeSetupFileHash(level_setup_hash);
3291 Error(ERR_WARN, "using default setup values");
3296 void SaveLevelSetup_LastSeries()
3298 /* ----------------------------------------------------------------------- */
3299 /* ~/.<program>/levelsetup.conf */
3300 /* ----------------------------------------------------------------------- */
3302 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3303 char *level_subdir = leveldir_current->subdir;
3306 InitUserDataDirectory();
3308 if (!(file = fopen(filename, MODE_WRITE)))
3310 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3315 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3316 getCookie("LEVELSETUP")));
3317 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3322 SetFilePermissions(filename, PERMS_PRIVATE);
3327 static void checkSeriesInfo()
3329 static char *level_directory = NULL;
3331 struct dirent *dir_entry;
3333 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3335 level_directory = getPath2((leveldir_current->in_user_dir ?
3336 getUserLevelDir(NULL) :
3337 options.level_directory),
3338 leveldir_current->fullpath);
3340 if ((dir = opendir(level_directory)) == NULL)
3342 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3346 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3348 if (strlen(dir_entry->d_name) > 4 &&
3349 dir_entry->d_name[3] == '.' &&
3350 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3352 char levelnum_str[4];
3355 strncpy(levelnum_str, dir_entry->d_name, 3);
3356 levelnum_str[3] = '\0';
3358 levelnum_value = atoi(levelnum_str);
3361 if (levelnum_value < leveldir_current->first_level)
3363 Error(ERR_WARN, "additional level %d found", levelnum_value);
3364 leveldir_current->first_level = levelnum_value;
3366 else if (levelnum_value > leveldir_current->last_level)
3368 Error(ERR_WARN, "additional level %d found", levelnum_value);
3369 leveldir_current->last_level = levelnum_value;
3378 void LoadLevelSetup_SeriesInfo()
3381 SetupFileHash *level_setup_hash = NULL;
3382 char *level_subdir = leveldir_current->subdir;
3384 /* always start with reliable default values */
3385 level_nr = leveldir_current->first_level;
3387 checkSeriesInfo(leveldir_current);
3389 /* ----------------------------------------------------------------------- */
3390 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3391 /* ----------------------------------------------------------------------- */
3393 level_subdir = leveldir_current->subdir;
3395 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3397 if ((level_setup_hash = loadSetupFileHash(filename)))
3401 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3405 level_nr = atoi(token_value);
3407 if (level_nr < leveldir_current->first_level)
3408 level_nr = leveldir_current->first_level;
3409 if (level_nr > leveldir_current->last_level)
3410 level_nr = leveldir_current->last_level;
3413 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3417 int level_nr = atoi(token_value);
3419 if (level_nr < leveldir_current->first_level)
3420 level_nr = leveldir_current->first_level;
3421 if (level_nr > leveldir_current->last_level + 1)
3422 level_nr = leveldir_current->last_level;
3424 if (leveldir_current->user_defined || !leveldir_current->handicap)
3425 level_nr = leveldir_current->last_level;
3427 leveldir_current->handicap_level = level_nr;
3430 checkSetupFileHashIdentifier(level_setup_hash, filename,
3431 getCookie("LEVELSETUP"));
3433 freeSetupFileHash(level_setup_hash);
3436 Error(ERR_WARN, "using default setup values");
3441 void SaveLevelSetup_SeriesInfo()
3444 char *level_subdir = leveldir_current->subdir;
3445 char *level_nr_str = int2str(level_nr, 0);
3446 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3449 /* ----------------------------------------------------------------------- */
3450 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3451 /* ----------------------------------------------------------------------- */
3453 InitLevelSetupDirectory(level_subdir);
3455 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3457 if (!(file = fopen(filename, MODE_WRITE)))
3459 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3464 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3465 getCookie("LEVELSETUP")));
3466 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3468 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3469 handicap_level_str));
3473 SetFilePermissions(filename, PERMS_PRIVATE);