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 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1623 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1625 static void *loadSetupFileData(char *filename, boolean use_hash)
1627 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1628 char *token, *value, *line_ptr;
1629 void *setup_file_data, *insert_ptr = NULL;
1630 boolean read_continued_line = FALSE;
1631 boolean token_value_separator_found;
1632 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1633 boolean token_value_separator_warning = FALSE;
1638 if (!(file = fopen(filename, MODE_READ)))
1640 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1646 setup_file_data = newSetupFileHash();
1648 insert_ptr = setup_file_data = newSetupFileList("", "");
1652 /* read next line of input file */
1653 if (!fgets(line, MAX_LINE_LEN, file))
1656 /* check if line was completely read and is terminated by line break */
1657 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1660 /* cut trailing line break (this can be newline and/or carriage return) */
1661 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1662 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1665 /* copy raw input line for later use (mainly debugging output) */
1666 strcpy(line_raw, line);
1668 if (read_continued_line)
1670 /* cut leading whitespaces from input line */
1671 for (line_ptr = line; *line_ptr; line_ptr++)
1672 if (*line_ptr != ' ' && *line_ptr != '\t')
1675 /* append new line to existing line, if there is enough space */
1676 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1677 strcat(previous_line, line_ptr);
1679 strcpy(line, previous_line); /* copy storage buffer to line */
1681 read_continued_line = FALSE;
1684 /* if the last character is '\', continue at next line */
1685 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1687 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1688 strcpy(previous_line, line); /* copy line to storage buffer */
1690 read_continued_line = TRUE;
1695 /* cut trailing comment from input line */
1696 for (line_ptr = line; *line_ptr; line_ptr++)
1698 if (*line_ptr == '#')
1705 /* cut trailing whitespaces from input line */
1706 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1707 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1710 /* ignore empty lines */
1714 /* cut leading whitespaces from token */
1715 for (token = line; *token; token++)
1716 if (*token != ' ' && *token != '\t')
1719 /* start with empty value as reliable default */
1722 token_value_separator_found = FALSE;
1724 /* find end of token to determine start of value */
1725 for (line_ptr = token; *line_ptr; line_ptr++)
1728 /* first look for an explicit token/value separator, like ':' or '=' */
1729 if (*line_ptr == ':' || *line_ptr == '=')
1731 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1734 *line_ptr = '\0'; /* terminate token string */
1735 value = line_ptr + 1; /* set beginning of value */
1737 token_value_separator_found = TRUE;
1743 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1744 /* fallback: if no token/value separator found, also allow whitespaces */
1745 if (!token_value_separator_found)
1747 for (line_ptr = token; *line_ptr; line_ptr++)
1749 if (*line_ptr == ' ' || *line_ptr == '\t')
1751 *line_ptr = '\0'; /* terminate token string */
1752 value = line_ptr + 1; /* set beginning of value */
1754 token_value_separator_found = TRUE;
1760 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1761 if (token_value_separator_found)
1763 if (!token_value_separator_warning)
1765 Error(ERR_RETURN_LINE, "-");
1766 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1767 Error(ERR_RETURN, "- config file: '%s'", filename);
1769 token_value_separator_warning = TRUE;
1772 Error(ERR_RETURN, "- line %d: '%s'", line_nr, line_raw);
1778 /* cut trailing whitespaces from token */
1779 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1780 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1783 /* cut leading whitespaces from value */
1784 for (; *value; value++)
1785 if (*value != ' ' && *value != '\t')
1790 value = "true"; /* treat tokens without value as "true" */
1796 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1798 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1804 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1805 if (token_value_separator_warning)
1806 Error(ERR_RETURN_LINE, "-");
1811 if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1812 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1816 SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1817 SetupFileList *first_valid_list_entry = setup_file_list->next;
1819 /* free empty list header */
1820 setup_file_list->next = NULL;
1821 freeSetupFileList(setup_file_list);
1822 setup_file_data = first_valid_list_entry;
1824 if (first_valid_list_entry == NULL)
1825 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1828 return setup_file_data;
1831 void saveSetupFileHash(SetupFileHash *hash, char *filename)
1835 if (!(file = fopen(filename, MODE_WRITE)))
1837 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
1842 BEGIN_HASH_ITERATION(hash, itr)
1844 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
1845 HASH_ITERATION_VALUE(itr)));
1847 END_HASH_ITERATION(hash, itr)
1852 SetupFileList *loadSetupFileList(char *filename)
1854 return (SetupFileList *)loadSetupFileData(filename, FALSE);
1857 SetupFileHash *loadSetupFileHash(char *filename)
1859 return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1862 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1863 char *filename, char *identifier)
1865 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1868 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
1869 else if (!checkCookieString(value, identifier))
1870 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
1874 /* ========================================================================= */
1875 /* setup file stuff */
1876 /* ========================================================================= */
1878 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1879 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1880 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1882 /* level directory info */
1883 #define LEVELINFO_TOKEN_IDENTIFIER 0
1884 #define LEVELINFO_TOKEN_NAME 1
1885 #define LEVELINFO_TOKEN_NAME_SORTING 2
1886 #define LEVELINFO_TOKEN_AUTHOR 3
1887 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
1888 #define LEVELINFO_TOKEN_IMPORTED_BY 5
1889 #define LEVELINFO_TOKEN_LEVELS 6
1890 #define LEVELINFO_TOKEN_FIRST_LEVEL 7
1891 #define LEVELINFO_TOKEN_SORT_PRIORITY 8
1892 #define LEVELINFO_TOKEN_LATEST_ENGINE 9
1893 #define LEVELINFO_TOKEN_LEVEL_GROUP 10
1894 #define LEVELINFO_TOKEN_READONLY 11
1895 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 12
1896 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 13
1897 #define LEVELINFO_TOKEN_GRAPHICS_SET 14
1898 #define LEVELINFO_TOKEN_SOUNDS_SET 15
1899 #define LEVELINFO_TOKEN_MUSIC_SET 16
1900 #define LEVELINFO_TOKEN_FILENAME 17
1901 #define LEVELINFO_TOKEN_FILETYPE 18
1902 #define LEVELINFO_TOKEN_HANDICAP 19
1903 #define LEVELINFO_TOKEN_SKIP_LEVELS 20
1905 #define NUM_LEVELINFO_TOKENS 21
1907 static LevelDirTree ldi;
1909 static struct TokenInfo levelinfo_tokens[] =
1911 /* level directory info */
1912 { TYPE_STRING, &ldi.identifier, "identifier" },
1913 { TYPE_STRING, &ldi.name, "name" },
1914 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1915 { TYPE_STRING, &ldi.author, "author" },
1916 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1917 { TYPE_STRING, &ldi.imported_by, "imported_by" },
1918 { TYPE_INTEGER, &ldi.levels, "levels" },
1919 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1920 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1921 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
1922 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1923 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1924 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
1925 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
1926 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1927 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1928 { TYPE_STRING, &ldi.music_set, "music_set" },
1929 { TYPE_STRING, &ldi.level_filename, "filename" },
1930 { TYPE_STRING, &ldi.level_filetype, "filetype" },
1931 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
1932 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
1935 static struct TokenInfo artworkinfo_tokens[] =
1937 /* artwork directory info */
1938 { TYPE_STRING, &ldi.identifier, "identifier" },
1939 { TYPE_STRING, &ldi.subdir, "subdir" },
1940 { TYPE_STRING, &ldi.name, "name" },
1941 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1942 { TYPE_STRING, &ldi.author, "author" },
1943 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1944 { TYPE_STRING, &ldi.basepath, "basepath" },
1945 { TYPE_STRING, &ldi.fullpath, "fullpath" },
1946 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
1947 { TYPE_INTEGER, &ldi.color, "color" },
1948 { TYPE_STRING, &ldi.class_desc, "class_desc" },
1953 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
1957 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1958 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1959 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1960 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1963 ti->node_parent = NULL;
1964 ti->node_group = NULL;
1971 ti->fullpath = NULL;
1972 ti->basepath = NULL;
1973 ti->identifier = NULL;
1974 ti->name = getStringCopy(ANONYMOUS_NAME);
1975 ti->name_sorting = NULL;
1976 ti->author = getStringCopy(ANONYMOUS_NAME);
1978 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1979 ti->latest_engine = FALSE; /* default: get from level */
1980 ti->parent_link = FALSE;
1981 ti->in_user_dir = FALSE;
1982 ti->user_defined = FALSE;
1984 ti->class_desc = NULL;
1986 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
1988 if (ti->type == TREE_TYPE_LEVEL_DIR)
1990 ti->imported_from = NULL;
1991 ti->imported_by = NULL;
1993 ti->graphics_set_ecs = NULL;
1994 ti->graphics_set_aga = NULL;
1995 ti->graphics_set = NULL;
1996 ti->sounds_set = NULL;
1997 ti->music_set = NULL;
1998 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1999 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2000 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2002 ti->level_filename = NULL;
2003 ti->level_filetype = NULL;
2006 ti->first_level = 0;
2008 ti->level_group = FALSE;
2009 ti->handicap_level = 0;
2010 ti->readonly = TRUE;
2011 ti->handicap = TRUE;
2012 ti->skip_levels = FALSE;
2016 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2020 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2022 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2027 /* copy all values from the parent structure */
2029 ti->type = parent->type;
2031 ti->node_top = parent->node_top;
2032 ti->node_parent = parent;
2033 ti->node_group = NULL;
2040 ti->fullpath = NULL;
2041 ti->basepath = NULL;
2042 ti->identifier = NULL;
2043 ti->name = getStringCopy(ANONYMOUS_NAME);
2044 ti->name_sorting = NULL;
2045 ti->author = getStringCopy(parent->author);
2047 ti->sort_priority = parent->sort_priority;
2048 ti->latest_engine = parent->latest_engine;
2049 ti->parent_link = FALSE;
2050 ti->in_user_dir = parent->in_user_dir;
2051 ti->user_defined = parent->user_defined;
2052 ti->color = parent->color;
2053 ti->class_desc = getStringCopy(parent->class_desc);
2055 ti->infotext = getStringCopy(parent->infotext);
2057 if (ti->type == TREE_TYPE_LEVEL_DIR)
2059 ti->imported_from = getStringCopy(parent->imported_from);
2060 ti->imported_by = getStringCopy(parent->imported_by);
2062 ti->graphics_set_ecs = NULL;
2063 ti->graphics_set_aga = NULL;
2064 ti->graphics_set = NULL;
2065 ti->sounds_set = NULL;
2066 ti->music_set = NULL;
2067 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2068 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2069 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2071 ti->level_filename = NULL;
2072 ti->level_filetype = NULL;
2075 ti->first_level = 0;
2077 ti->level_group = FALSE;
2078 ti->handicap_level = 0;
2079 ti->readonly = TRUE;
2080 ti->handicap = TRUE;
2081 ti->skip_levels = FALSE;
2085 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2087 TreeInfo *ti_copy = newTreeInfo();
2089 /* copy all values from the original structure */
2091 ti_copy->type = ti->type;
2093 ti_copy->node_top = ti->node_top;
2094 ti_copy->node_parent = ti->node_parent;
2095 ti_copy->node_group = ti->node_group;
2096 ti_copy->next = ti->next;
2098 ti_copy->cl_first = ti->cl_first;
2099 ti_copy->cl_cursor = ti->cl_cursor;
2101 ti_copy->subdir = getStringCopy(ti->subdir);
2102 ti_copy->fullpath = getStringCopy(ti->fullpath);
2103 ti_copy->basepath = getStringCopy(ti->basepath);
2104 ti_copy->identifier = getStringCopy(ti->identifier);
2105 ti_copy->name = getStringCopy(ti->name);
2106 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2107 ti_copy->author = getStringCopy(ti->author);
2108 ti_copy->imported_from = getStringCopy(ti->imported_from);
2109 ti_copy->imported_by = getStringCopy(ti->imported_by);
2111 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2112 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2113 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2114 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2115 ti_copy->music_set = getStringCopy(ti->music_set);
2116 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2117 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2118 ti_copy->music_path = getStringCopy(ti->music_path);
2120 ti_copy->level_filename = getStringCopy(ti->level_filename);
2121 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2123 ti_copy->levels = ti->levels;
2124 ti_copy->first_level = ti->first_level;
2125 ti_copy->last_level = ti->last_level;
2126 ti_copy->sort_priority = ti->sort_priority;
2128 ti_copy->latest_engine = ti->latest_engine;
2130 ti_copy->level_group = ti->level_group;
2131 ti_copy->parent_link = ti->parent_link;
2132 ti_copy->in_user_dir = ti->in_user_dir;
2133 ti_copy->user_defined = ti->user_defined;
2134 ti_copy->readonly = ti->readonly;
2135 ti_copy->handicap = ti->handicap;
2136 ti_copy->skip_levels = ti->skip_levels;
2138 ti_copy->color = ti->color;
2139 ti_copy->class_desc = getStringCopy(ti->class_desc);
2140 ti_copy->handicap_level = ti->handicap_level;
2142 ti_copy->infotext = getStringCopy(ti->infotext);
2147 static void freeTreeInfo(TreeInfo *ti)
2152 checked_free(ti->subdir);
2153 checked_free(ti->fullpath);
2154 checked_free(ti->basepath);
2155 checked_free(ti->identifier);
2157 checked_free(ti->name);
2158 checked_free(ti->name_sorting);
2159 checked_free(ti->author);
2161 checked_free(ti->class_desc);
2163 checked_free(ti->infotext);
2165 if (ti->type == TREE_TYPE_LEVEL_DIR)
2167 checked_free(ti->imported_from);
2168 checked_free(ti->imported_by);
2170 checked_free(ti->graphics_set_ecs);
2171 checked_free(ti->graphics_set_aga);
2172 checked_free(ti->graphics_set);
2173 checked_free(ti->sounds_set);
2174 checked_free(ti->music_set);
2176 checked_free(ti->graphics_path);
2177 checked_free(ti->sounds_path);
2178 checked_free(ti->music_path);
2180 checked_free(ti->level_filename);
2181 checked_free(ti->level_filetype);
2187 void setSetupInfo(struct TokenInfo *token_info,
2188 int token_nr, char *token_value)
2190 int token_type = token_info[token_nr].type;
2191 void *setup_value = token_info[token_nr].value;
2193 if (token_value == NULL)
2196 /* set setup field to corresponding token value */
2201 *(boolean *)setup_value = get_boolean_from_string(token_value);
2205 *(Key *)setup_value = getKeyFromKeyName(token_value);
2209 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2213 *(int *)setup_value = get_integer_from_string(token_value);
2217 checked_free(*(char **)setup_value);
2218 *(char **)setup_value = getStringCopy(token_value);
2226 static int compareTreeInfoEntries(const void *object1, const void *object2)
2228 const TreeInfo *entry1 = *((TreeInfo **)object1);
2229 const TreeInfo *entry2 = *((TreeInfo **)object2);
2230 int class_sorting1, class_sorting2;
2233 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2235 class_sorting1 = LEVELSORTING(entry1);
2236 class_sorting2 = LEVELSORTING(entry2);
2240 class_sorting1 = ARTWORKSORTING(entry1);
2241 class_sorting2 = ARTWORKSORTING(entry2);
2244 if (entry1->parent_link || entry2->parent_link)
2245 compare_result = (entry1->parent_link ? -1 : +1);
2246 else if (entry1->sort_priority == entry2->sort_priority)
2248 char *name1 = getStringToLower(entry1->name_sorting);
2249 char *name2 = getStringToLower(entry2->name_sorting);
2251 compare_result = strcmp(name1, name2);
2256 else if (class_sorting1 == class_sorting2)
2257 compare_result = entry1->sort_priority - entry2->sort_priority;
2259 compare_result = class_sorting1 - class_sorting2;
2261 return compare_result;
2264 static void createParentTreeInfoNode(TreeInfo *node_parent)
2268 if (node_parent == NULL)
2271 ti_new = newTreeInfo();
2272 setTreeInfoToDefaults(ti_new, node_parent->type);
2274 ti_new->node_parent = node_parent;
2275 ti_new->parent_link = TRUE;
2277 setString(&ti_new->identifier, node_parent->identifier);
2278 setString(&ti_new->name, ".. (parent directory)");
2279 setString(&ti_new->name_sorting, ti_new->name);
2281 setString(&ti_new->subdir, "..");
2282 setString(&ti_new->fullpath, node_parent->fullpath);
2284 ti_new->sort_priority = node_parent->sort_priority;
2285 ti_new->latest_engine = node_parent->latest_engine;
2287 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2289 pushTreeInfo(&node_parent->node_group, ti_new);
2293 /* -------------------------------------------------------------------------- */
2294 /* functions for handling level and custom artwork info cache */
2295 /* -------------------------------------------------------------------------- */
2297 static void LoadArtworkInfoCache()
2299 InitCacheDirectory();
2301 if (artworkinfo_cache_old == NULL)
2303 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2305 /* try to load artwork info hash from already existing cache file */
2306 artworkinfo_cache_old = loadSetupFileHash(filename);
2308 /* if no artwork info cache file was found, start with empty hash */
2309 if (artworkinfo_cache_old == NULL)
2310 artworkinfo_cache_old = newSetupFileHash();
2315 if (artworkinfo_cache_new == NULL)
2316 artworkinfo_cache_new = newSetupFileHash();
2319 static void SaveArtworkInfoCache()
2321 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2323 InitCacheDirectory();
2325 saveSetupFileHash(artworkinfo_cache_new, filename);
2330 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2332 static char *prefix = NULL;
2334 checked_free(prefix);
2336 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2341 /* (identical to above function, but separate string buffer needed -- nasty) */
2342 static char *getCacheToken(char *prefix, char *suffix)
2344 static char *token = NULL;
2346 checked_free(token);
2348 token = getStringCat2WithSeparator(prefix, suffix, ".");
2353 static char *getFileTimestamp(char *filename)
2355 struct stat file_status;
2357 if (stat(filename, &file_status) != 0) /* cannot stat file */
2358 return getStringCopy(i_to_a(0));
2360 return getStringCopy(i_to_a(file_status.st_mtime));
2363 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2365 struct stat file_status;
2367 if (timestamp_string == NULL)
2370 if (stat(filename, &file_status) != 0) /* cannot stat file */
2373 return (file_status.st_mtime != atoi(timestamp_string));
2376 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2378 char *identifier = level_node->subdir;
2379 char *type_string = ARTWORK_DIRECTORY(type);
2380 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2381 char *token_main = getCacheToken(token_prefix, "CACHED");
2382 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2383 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2384 TreeInfo *artwork_info = NULL;
2386 if (!use_artworkinfo_cache)
2393 artwork_info = newTreeInfo();
2394 setTreeInfoToDefaults(artwork_info, type);
2396 /* set all structure fields according to the token/value pairs */
2397 ldi = *artwork_info;
2398 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2400 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2401 char *value = getHashEntry(artworkinfo_cache_old, token);
2403 setSetupInfo(artworkinfo_tokens, i, value);
2405 /* check if cache entry for this item is invalid or incomplete */
2409 Error(ERR_WARN, "cache entry '%s' invalid", token);
2416 *artwork_info = ldi;
2421 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2422 LEVELINFO_FILENAME);
2423 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2424 ARTWORKINFO_FILENAME(type));
2426 /* check if corresponding "levelinfo.conf" file has changed */
2427 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2428 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2430 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2433 /* check if corresponding "<artworkinfo>.conf" file has changed */
2434 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2435 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2437 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2442 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2445 checked_free(filename_levelinfo);
2446 checked_free(filename_artworkinfo);
2449 if (!cached && artwork_info != NULL)
2451 freeTreeInfo(artwork_info);
2456 return artwork_info;
2459 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2460 LevelDirTree *level_node, int type)
2462 char *identifier = level_node->subdir;
2463 char *type_string = ARTWORK_DIRECTORY(type);
2464 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2465 char *token_main = getCacheToken(token_prefix, "CACHED");
2466 boolean set_cache_timestamps = TRUE;
2469 setHashEntry(artworkinfo_cache_new, token_main, "true");
2471 if (set_cache_timestamps)
2473 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2474 LEVELINFO_FILENAME);
2475 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2476 ARTWORKINFO_FILENAME(type));
2477 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2478 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2480 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2481 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2483 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2484 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2486 checked_free(filename_levelinfo);
2487 checked_free(filename_artworkinfo);
2488 checked_free(timestamp_levelinfo);
2489 checked_free(timestamp_artworkinfo);
2492 ldi = *artwork_info;
2493 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2495 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2496 char *value = getSetupValue(artworkinfo_tokens[i].type,
2497 artworkinfo_tokens[i].value);
2499 setHashEntry(artworkinfo_cache_new, token, value);
2504 /* -------------------------------------------------------------------------- */
2505 /* functions for loading level info and custom artwork info */
2506 /* -------------------------------------------------------------------------- */
2508 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2509 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2511 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2512 TreeInfo *node_parent,
2513 char *level_directory,
2514 char *directory_name)
2516 static unsigned long progress_delay = 0;
2517 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2518 char *directory_path = getPath2(level_directory, directory_name);
2519 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2520 SetupFileHash *setup_file_hash;
2521 LevelDirTree *leveldir_new = NULL;
2524 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2525 if (!options.debug && !fileExists(filename))
2527 free(directory_path);
2533 setup_file_hash = loadSetupFileHash(filename);
2535 if (setup_file_hash == NULL)
2537 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2539 free(directory_path);
2545 leveldir_new = newTreeInfo();
2548 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2550 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2552 leveldir_new->subdir = getStringCopy(directory_name);
2554 checkSetupFileHashIdentifier(setup_file_hash, filename,
2555 getCookie("LEVELINFO"));
2557 /* set all structure fields according to the token/value pairs */
2558 ldi = *leveldir_new;
2559 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2560 setSetupInfo(levelinfo_tokens, i,
2561 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2562 *leveldir_new = ldi;
2564 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2565 setString(&leveldir_new->name, leveldir_new->subdir);
2567 if (leveldir_new->identifier == NULL)
2568 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2570 if (leveldir_new->name_sorting == NULL)
2571 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2573 if (node_parent == NULL) /* top level group */
2575 leveldir_new->basepath = getStringCopy(level_directory);
2576 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2578 else /* sub level group */
2580 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2581 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2585 if (leveldir_new->levels < 1)
2586 leveldir_new->levels = 1;
2589 leveldir_new->last_level =
2590 leveldir_new->first_level + leveldir_new->levels - 1;
2592 leveldir_new->in_user_dir =
2593 (!strEqual(leveldir_new->basepath, options.level_directory));
2595 /* adjust some settings if user's private level directory was detected */
2596 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2597 leveldir_new->in_user_dir &&
2598 (strEqual(leveldir_new->subdir, getLoginName()) ||
2599 strEqual(leveldir_new->name, getLoginName()) ||
2600 strEqual(leveldir_new->author, getRealName())))
2602 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2603 leveldir_new->readonly = FALSE;
2606 leveldir_new->user_defined =
2607 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2609 leveldir_new->color = LEVELCOLOR(leveldir_new);
2611 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2613 leveldir_new->handicap_level = /* set handicap to default value */
2614 (leveldir_new->user_defined || !leveldir_new->handicap ?
2615 leveldir_new->last_level : leveldir_new->first_level);
2618 if (leveldir_new->level_group ||
2619 DelayReached(&progress_delay, progress_delay_value))
2620 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2622 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2626 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2628 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2630 /* skip level sets without levels (which are probably artwork base sets) */
2632 freeSetupFileHash(setup_file_hash);
2633 free(directory_path);
2641 pushTreeInfo(node_first, leveldir_new);
2643 freeSetupFileHash(setup_file_hash);
2645 if (leveldir_new->level_group)
2647 /* create node to link back to current level directory */
2648 createParentTreeInfoNode(leveldir_new);
2650 /* recursively step into sub-directory and look for more level series */
2651 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2652 leveldir_new, directory_path);
2655 free(directory_path);
2661 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2662 TreeInfo *node_parent,
2663 char *level_directory)
2666 struct dirent *dir_entry;
2667 boolean valid_entry_found = FALSE;
2669 if ((dir = opendir(level_directory)) == NULL)
2671 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2675 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2677 struct stat file_status;
2678 char *directory_name = dir_entry->d_name;
2679 char *directory_path = getPath2(level_directory, directory_name);
2681 /* skip entries for current and parent directory */
2682 if (strEqual(directory_name, ".") ||
2683 strEqual(directory_name, ".."))
2685 free(directory_path);
2689 /* find out if directory entry is itself a directory */
2690 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2691 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2693 free(directory_path);
2697 free(directory_path);
2699 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2700 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2701 strEqual(directory_name, MUSIC_DIRECTORY))
2704 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2711 /* special case: top level directory may directly contain "levelinfo.conf" */
2712 if (node_parent == NULL && !valid_entry_found)
2714 /* check if this directory directly contains a file "levelinfo.conf" */
2715 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2716 level_directory, ".");
2719 if (!valid_entry_found)
2720 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2724 boolean AdjustGraphicsForEMC()
2726 boolean settings_changed = FALSE;
2728 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2729 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2731 return settings_changed;
2734 void LoadLevelInfo()
2736 InitUserLevelDirectory(getLoginName());
2738 DrawInitText("Loading level series", 120, FC_GREEN);
2740 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2741 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2743 /* after loading all level set information, clone the level directory tree
2744 and remove all level sets without levels (these may still contain artwork
2745 to be offered in the setup menu as "custom artwork", and are therefore
2746 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2747 leveldir_first_all = leveldir_first;
2748 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2750 AdjustGraphicsForEMC();
2752 /* before sorting, the first entries will be from the user directory */
2753 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2755 if (leveldir_first == NULL)
2756 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2758 sortTreeInfo(&leveldir_first);
2761 dumpTreeInfo(leveldir_first, 0);
2765 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2766 TreeInfo *node_parent,
2767 char *base_directory,
2768 char *directory_name, int type)
2770 char *directory_path = getPath2(base_directory, directory_name);
2771 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2772 SetupFileHash *setup_file_hash = NULL;
2773 TreeInfo *artwork_new = NULL;
2776 if (fileExists(filename))
2777 setup_file_hash = loadSetupFileHash(filename);
2779 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2782 struct dirent *dir_entry;
2783 boolean valid_file_found = FALSE;
2785 if ((dir = opendir(directory_path)) != NULL)
2787 while ((dir_entry = readdir(dir)) != NULL)
2789 char *entry_name = dir_entry->d_name;
2791 if (FileIsArtworkType(entry_name, type))
2793 valid_file_found = TRUE;
2801 if (!valid_file_found)
2803 if (!strEqual(directory_name, "."))
2804 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2806 free(directory_path);
2813 artwork_new = newTreeInfo();
2816 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2818 setTreeInfoToDefaults(artwork_new, type);
2820 artwork_new->subdir = getStringCopy(directory_name);
2822 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2825 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2828 /* set all structure fields according to the token/value pairs */
2830 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2831 setSetupInfo(levelinfo_tokens, i,
2832 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2835 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2836 setString(&artwork_new->name, artwork_new->subdir);
2838 if (artwork_new->identifier == NULL)
2839 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2841 if (artwork_new->name_sorting == NULL)
2842 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2845 if (node_parent == NULL) /* top level group */
2847 artwork_new->basepath = getStringCopy(base_directory);
2848 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2850 else /* sub level group */
2852 artwork_new->basepath = getStringCopy(node_parent->basepath);
2853 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2856 artwork_new->in_user_dir =
2857 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2859 /* (may use ".sort_priority" from "setup_file_hash" above) */
2860 artwork_new->color = ARTWORKCOLOR(artwork_new);
2862 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2864 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2866 if (strEqual(artwork_new->subdir, "."))
2868 if (artwork_new->user_defined)
2870 setString(&artwork_new->identifier, "private");
2871 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2875 setString(&artwork_new->identifier, "classic");
2876 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2879 /* set to new values after changing ".sort_priority" */
2880 artwork_new->color = ARTWORKCOLOR(artwork_new);
2882 setString(&artwork_new->class_desc,
2883 getLevelClassDescription(artwork_new));
2887 setString(&artwork_new->identifier, artwork_new->subdir);
2890 setString(&artwork_new->name, artwork_new->identifier);
2891 setString(&artwork_new->name_sorting, artwork_new->name);
2895 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2898 pushTreeInfo(node_first, artwork_new);
2900 freeSetupFileHash(setup_file_hash);
2902 free(directory_path);
2908 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2909 TreeInfo *node_parent,
2910 char *base_directory, int type)
2913 struct dirent *dir_entry;
2914 boolean valid_entry_found = FALSE;
2916 if ((dir = opendir(base_directory)) == NULL)
2918 /* display error if directory is main "options.graphics_directory" etc. */
2919 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2920 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2925 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2927 struct stat file_status;
2928 char *directory_name = dir_entry->d_name;
2929 char *directory_path = getPath2(base_directory, directory_name);
2931 /* skip directory entries for current and parent directory */
2932 if (strEqual(directory_name, ".") ||
2933 strEqual(directory_name, ".."))
2935 free(directory_path);
2939 /* skip directory entries which are not a directory or are not accessible */
2940 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2941 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2943 free(directory_path);
2947 free(directory_path);
2949 /* check if this directory contains artwork with or without config file */
2950 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2952 directory_name, type);
2957 /* check if this directory directly contains artwork itself */
2958 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2959 base_directory, ".",
2961 if (!valid_entry_found)
2962 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2966 static TreeInfo *getDummyArtworkInfo(int type)
2968 /* this is only needed when there is completely no artwork available */
2969 TreeInfo *artwork_new = newTreeInfo();
2971 setTreeInfoToDefaults(artwork_new, type);
2973 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
2974 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2975 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2977 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
2978 setString(&artwork_new->name, UNDEFINED_FILENAME);
2979 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2984 void LoadArtworkInfo()
2986 LoadArtworkInfoCache();
2988 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
2990 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2991 options.graphics_directory,
2992 TREE_TYPE_GRAPHICS_DIR);
2993 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2994 getUserGraphicsDir(),
2995 TREE_TYPE_GRAPHICS_DIR);
2997 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2998 options.sounds_directory,
2999 TREE_TYPE_SOUNDS_DIR);
3000 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3002 TREE_TYPE_SOUNDS_DIR);
3004 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3005 options.music_directory,
3006 TREE_TYPE_MUSIC_DIR);
3007 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3009 TREE_TYPE_MUSIC_DIR);
3011 if (artwork.gfx_first == NULL)
3012 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3013 if (artwork.snd_first == NULL)
3014 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3015 if (artwork.mus_first == NULL)
3016 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3018 /* before sorting, the first entries will be from the user directory */
3019 artwork.gfx_current =
3020 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3021 if (artwork.gfx_current == NULL)
3022 artwork.gfx_current =
3023 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3024 if (artwork.gfx_current == NULL)
3025 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3027 artwork.snd_current =
3028 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3029 if (artwork.snd_current == NULL)
3030 artwork.snd_current =
3031 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3032 if (artwork.snd_current == NULL)
3033 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3035 artwork.mus_current =
3036 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3037 if (artwork.mus_current == NULL)
3038 artwork.mus_current =
3039 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3040 if (artwork.mus_current == NULL)
3041 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3043 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3044 artwork.snd_current_identifier = artwork.snd_current->identifier;
3045 artwork.mus_current_identifier = artwork.mus_current->identifier;
3048 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3049 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3050 printf("music set == %s\n\n", artwork.mus_current_identifier);
3053 sortTreeInfo(&artwork.gfx_first);
3054 sortTreeInfo(&artwork.snd_first);
3055 sortTreeInfo(&artwork.mus_first);
3058 dumpTreeInfo(artwork.gfx_first, 0);
3059 dumpTreeInfo(artwork.snd_first, 0);
3060 dumpTreeInfo(artwork.mus_first, 0);
3064 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3065 LevelDirTree *level_node)
3067 static unsigned long progress_delay = 0;
3068 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3069 int type = (*artwork_node)->type;
3071 /* recursively check all level directories for artwork sub-directories */
3075 /* check all tree entries for artwork, but skip parent link entries */
3076 if (!level_node->parent_link)
3078 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3079 boolean cached = (artwork_new != NULL);
3083 pushTreeInfo(artwork_node, artwork_new);
3087 TreeInfo *topnode_last = *artwork_node;
3088 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3089 ARTWORK_DIRECTORY(type));
3091 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3093 if (topnode_last != *artwork_node) /* check for newly added node */
3095 artwork_new = *artwork_node;
3097 setString(&artwork_new->identifier, level_node->subdir);
3098 setString(&artwork_new->name, level_node->name);
3099 setString(&artwork_new->name_sorting, level_node->name_sorting);
3101 artwork_new->sort_priority = level_node->sort_priority;
3102 artwork_new->color = LEVELCOLOR(artwork_new);
3108 /* insert artwork info (from old cache or filesystem) into new cache */
3109 if (artwork_new != NULL)
3110 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3114 if (level_node->level_group ||
3115 DelayReached(&progress_delay, progress_delay_value))
3116 DrawInitText(level_node->name, 150, FC_YELLOW);
3119 if (level_node->node_group != NULL)
3120 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3122 level_node = level_node->next;
3126 void LoadLevelArtworkInfo()
3128 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3130 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3131 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3132 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3134 SaveArtworkInfoCache();
3136 /* needed for reloading level artwork not known at ealier stage */
3138 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3140 artwork.gfx_current =
3141 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3142 if (artwork.gfx_current == NULL)
3143 artwork.gfx_current =
3144 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3145 if (artwork.gfx_current == NULL)
3146 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3149 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3151 artwork.snd_current =
3152 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3153 if (artwork.snd_current == NULL)
3154 artwork.snd_current =
3155 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3156 if (artwork.snd_current == NULL)
3157 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3160 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3162 artwork.mus_current =
3163 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3164 if (artwork.mus_current == NULL)
3165 artwork.mus_current =
3166 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3167 if (artwork.mus_current == NULL)
3168 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3171 sortTreeInfo(&artwork.gfx_first);
3172 sortTreeInfo(&artwork.snd_first);
3173 sortTreeInfo(&artwork.mus_first);
3176 dumpTreeInfo(artwork.gfx_first, 0);
3177 dumpTreeInfo(artwork.snd_first, 0);
3178 dumpTreeInfo(artwork.mus_first, 0);
3182 static void SaveUserLevelInfo()
3184 LevelDirTree *level_info;
3189 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3191 if (!(file = fopen(filename, MODE_WRITE)))
3193 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3198 level_info = newTreeInfo();
3200 /* always start with reliable default values */
3201 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3203 setString(&level_info->name, getLoginName());
3204 setString(&level_info->author, getRealName());
3205 level_info->levels = 100;
3206 level_info->first_level = 1;
3208 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3210 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3211 getCookie("LEVELINFO")));
3214 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3216 if (i == LEVELINFO_TOKEN_NAME ||
3217 i == LEVELINFO_TOKEN_AUTHOR ||
3218 i == LEVELINFO_TOKEN_LEVELS ||
3219 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3220 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3222 /* just to make things nicer :) */
3223 if (i == LEVELINFO_TOKEN_AUTHOR)
3224 fprintf(file, "\n");
3227 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3231 SetFilePermissions(filename, PERMS_PRIVATE);
3233 freeTreeInfo(level_info);
3237 char *getSetupValue(int type, void *value)
3239 static char value_string[MAX_LINE_LEN];
3247 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3251 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3255 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3259 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3263 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3267 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3271 sprintf(value_string, "%d", *(int *)value);
3275 if (*(char **)value == NULL)
3278 strcpy(value_string, *(char **)value);
3282 value_string[0] = '\0';
3286 if (type & TYPE_GHOSTED)
3287 strcpy(value_string, "n/a");
3289 return value_string;
3292 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3296 static char token_string[MAX_LINE_LEN];
3297 int token_type = token_info[token_nr].type;
3298 void *setup_value = token_info[token_nr].value;
3299 char *token_text = token_info[token_nr].text;
3300 char *value_string = getSetupValue(token_type, setup_value);
3302 /* build complete token string */
3303 sprintf(token_string, "%s%s", prefix, token_text);
3305 /* build setup entry line */
3306 line = getFormattedSetupEntry(token_string, value_string);
3308 if (token_type == TYPE_KEY_X11)
3310 Key key = *(Key *)setup_value;
3311 char *keyname = getKeyNameFromKey(key);
3313 /* add comment, if useful */
3314 if (!strEqual(keyname, "(undefined)") &&
3315 !strEqual(keyname, "(unknown)"))
3317 /* add at least one whitespace */
3319 for (i = strlen(line); i < token_comment_position; i++)
3323 strcat(line, keyname);
3330 void LoadLevelSetup_LastSeries()
3332 /* ----------------------------------------------------------------------- */
3333 /* ~/.<program>/levelsetup.conf */
3334 /* ----------------------------------------------------------------------- */
3336 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3337 SetupFileHash *level_setup_hash = NULL;
3339 /* always start with reliable default values */
3340 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3342 if ((level_setup_hash = loadSetupFileHash(filename)))
3344 char *last_level_series =
3345 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3347 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3349 if (leveldir_current == NULL)
3350 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3352 checkSetupFileHashIdentifier(level_setup_hash, filename,
3353 getCookie("LEVELSETUP"));
3355 freeSetupFileHash(level_setup_hash);
3358 Error(ERR_WARN, "using default setup values");
3363 void SaveLevelSetup_LastSeries()
3365 /* ----------------------------------------------------------------------- */
3366 /* ~/.<program>/levelsetup.conf */
3367 /* ----------------------------------------------------------------------- */
3369 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3370 char *level_subdir = leveldir_current->subdir;
3373 InitUserDataDirectory();
3375 if (!(file = fopen(filename, MODE_WRITE)))
3377 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3382 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3383 getCookie("LEVELSETUP")));
3384 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3389 SetFilePermissions(filename, PERMS_PRIVATE);
3394 static void checkSeriesInfo()
3396 static char *level_directory = NULL;
3398 struct dirent *dir_entry;
3400 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3402 level_directory = getPath2((leveldir_current->in_user_dir ?
3403 getUserLevelDir(NULL) :
3404 options.level_directory),
3405 leveldir_current->fullpath);
3407 if ((dir = opendir(level_directory)) == NULL)
3409 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3413 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3415 if (strlen(dir_entry->d_name) > 4 &&
3416 dir_entry->d_name[3] == '.' &&
3417 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3419 char levelnum_str[4];
3422 strncpy(levelnum_str, dir_entry->d_name, 3);
3423 levelnum_str[3] = '\0';
3425 levelnum_value = atoi(levelnum_str);
3428 if (levelnum_value < leveldir_current->first_level)
3430 Error(ERR_WARN, "additional level %d found", levelnum_value);
3431 leveldir_current->first_level = levelnum_value;
3433 else if (levelnum_value > leveldir_current->last_level)
3435 Error(ERR_WARN, "additional level %d found", levelnum_value);
3436 leveldir_current->last_level = levelnum_value;
3445 void LoadLevelSetup_SeriesInfo()
3448 SetupFileHash *level_setup_hash = NULL;
3449 char *level_subdir = leveldir_current->subdir;
3451 /* always start with reliable default values */
3452 level_nr = leveldir_current->first_level;
3454 checkSeriesInfo(leveldir_current);
3456 /* ----------------------------------------------------------------------- */
3457 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3458 /* ----------------------------------------------------------------------- */
3460 level_subdir = leveldir_current->subdir;
3462 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3464 if ((level_setup_hash = loadSetupFileHash(filename)))
3468 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3472 level_nr = atoi(token_value);
3474 if (level_nr < leveldir_current->first_level)
3475 level_nr = leveldir_current->first_level;
3476 if (level_nr > leveldir_current->last_level)
3477 level_nr = leveldir_current->last_level;
3480 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3484 int level_nr = atoi(token_value);
3486 if (level_nr < leveldir_current->first_level)
3487 level_nr = leveldir_current->first_level;
3488 if (level_nr > leveldir_current->last_level + 1)
3489 level_nr = leveldir_current->last_level;
3491 if (leveldir_current->user_defined || !leveldir_current->handicap)
3492 level_nr = leveldir_current->last_level;
3494 leveldir_current->handicap_level = level_nr;
3497 checkSetupFileHashIdentifier(level_setup_hash, filename,
3498 getCookie("LEVELSETUP"));
3500 freeSetupFileHash(level_setup_hash);
3503 Error(ERR_WARN, "using default setup values");
3508 void SaveLevelSetup_SeriesInfo()
3511 char *level_subdir = leveldir_current->subdir;
3512 char *level_nr_str = int2str(level_nr, 0);
3513 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3516 /* ----------------------------------------------------------------------- */
3517 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3518 /* ----------------------------------------------------------------------- */
3520 InitLevelSetupDirectory(level_subdir);
3522 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3524 if (!(file = fopen(filename, MODE_WRITE)))
3526 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3531 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3532 getCookie("LEVELSETUP")));
3533 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3535 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3536 handicap_level_str));
3540 SetFilePermissions(filename, PERMS_PRIVATE);