1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
22 #if !defined(PLATFORM_WIN32)
24 #include <sys/param.h>
34 #define NUM_LEVELCLASS_DESC 8
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
49 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
50 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
51 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
57 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
61 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
62 IS_LEVELCLASS_BD(n) ? 2 : \
63 IS_LEVELCLASS_EM(n) ? 3 : \
64 IS_LEVELCLASS_SP(n) ? 4 : \
65 IS_LEVELCLASS_DX(n) ? 5 : \
66 IS_LEVELCLASS_SB(n) ? 6 : \
67 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
68 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
72 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
73 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
74 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
78 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
79 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
80 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
83 #define TOKEN_VALUE_POSITION_SHORT 32
84 #define TOKEN_VALUE_POSITION_DEFAULT 40
85 #define TOKEN_COMMENT_POSITION_DEFAULT 60
87 #define MAX_COOKIE_LEN 256
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
94 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
102 /* ------------------------------------------------------------------------- */
104 /* ------------------------------------------------------------------------- */
106 static char *getLevelClassDescription(TreeInfo *ti)
108 int position = ti->sort_priority / 100;
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
113 return "Unknown Level Class";
116 static char *getUserLevelDir(char *level_subdir)
118 static char *userlevel_dir = NULL;
119 char *data_dir = getUserGameDataDir();
120 char *userlevel_subdir = LEVELS_DIRECTORY;
122 checked_free(userlevel_dir);
124 if (level_subdir != NULL)
125 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
127 userlevel_dir = getPath2(data_dir, userlevel_subdir);
129 return userlevel_dir;
132 static char *getScoreDir(char *level_subdir)
134 static char *score_dir = NULL;
135 char *data_dir = getCommonDataDir();
136 char *score_subdir = SCORES_DIRECTORY;
138 checked_free(score_dir);
140 if (level_subdir != NULL)
141 score_dir = getPath3(data_dir, score_subdir, level_subdir);
143 score_dir = getPath2(data_dir, score_subdir);
148 static char *getLevelSetupDir(char *level_subdir)
150 static char *levelsetup_dir = NULL;
151 char *data_dir = getUserGameDataDir();
152 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
154 checked_free(levelsetup_dir);
156 if (level_subdir != NULL)
157 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
159 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
161 return levelsetup_dir;
164 static char *getCacheDir()
166 static char *cache_dir = NULL;
168 if (cache_dir == NULL)
169 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
176 static char *level_dir = NULL;
179 return options.level_directory;
181 checked_free(level_dir);
183 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184 options.level_directory), node->fullpath);
189 char *getCurrentLevelDir()
191 return getLevelDirFromTreeInfo(leveldir_current);
194 static char *getTapeDir(char *level_subdir)
196 static char *tape_dir = NULL;
197 char *data_dir = getUserGameDataDir();
198 char *tape_subdir = TAPES_DIRECTORY;
200 checked_free(tape_dir);
202 if (level_subdir != NULL)
203 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
205 tape_dir = getPath2(data_dir, tape_subdir);
210 static char *getSolutionTapeDir()
212 static char *tape_dir = NULL;
213 char *data_dir = getCurrentLevelDir();
214 char *tape_subdir = TAPES_DIRECTORY;
216 checked_free(tape_dir);
218 tape_dir = getPath2(data_dir, tape_subdir);
223 static char *getDefaultGraphicsDir(char *graphics_subdir)
225 static char *graphics_dir = NULL;
227 if (graphics_subdir == NULL)
228 return options.graphics_directory;
230 checked_free(graphics_dir);
232 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
237 static char *getDefaultSoundsDir(char *sounds_subdir)
239 static char *sounds_dir = NULL;
241 if (sounds_subdir == NULL)
242 return options.sounds_directory;
244 checked_free(sounds_dir);
246 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
251 static char *getDefaultMusicDir(char *music_subdir)
253 static char *music_dir = NULL;
255 if (music_subdir == NULL)
256 return options.music_directory;
258 checked_free(music_dir);
260 music_dir = getPath2(options.music_directory, music_subdir);
265 static char *getDefaultArtworkSet(int type)
267 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
269 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
272 static char *getDefaultArtworkDir(int type)
274 return (type == TREE_TYPE_GRAPHICS_DIR ?
275 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276 type == TREE_TYPE_SOUNDS_DIR ?
277 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278 type == TREE_TYPE_MUSIC_DIR ?
279 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
282 static char *getUserGraphicsDir()
284 static char *usergraphics_dir = NULL;
286 if (usergraphics_dir == NULL)
287 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
289 return usergraphics_dir;
292 static char *getUserSoundsDir()
294 static char *usersounds_dir = NULL;
296 if (usersounds_dir == NULL)
297 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
299 return usersounds_dir;
302 static char *getUserMusicDir()
304 static char *usermusic_dir = NULL;
306 if (usermusic_dir == NULL)
307 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
309 return usermusic_dir;
312 static char *getSetupArtworkDir(TreeInfo *ti)
314 static char *artwork_dir = NULL;
316 checked_free(artwork_dir);
318 artwork_dir = getPath2(ti->basepath, ti->fullpath);
323 char *setLevelArtworkDir(TreeInfo *ti)
325 char **artwork_path_ptr, **artwork_set_ptr;
326 TreeInfo *level_artwork;
328 if (ti == NULL || leveldir_current == NULL)
331 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
334 checked_free(*artwork_path_ptr);
336 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
340 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
341 normally result in using the artwork configured in the setup menu. But
342 if an artwork subdirectory exists (which might contain custom artwork
343 or an artwork configuration file), this level artwork must be treated
344 as relative to the default "classic" artwork, not to the artwork that
345 is currently configured in the setup menu. */
347 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
349 checked_free(*artwork_set_ptr);
353 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
354 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
358 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
359 *artwork_set_ptr = NULL;
365 return *artwork_set_ptr;
368 inline static char *getLevelArtworkSet(int type)
370 if (leveldir_current == NULL)
373 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
376 inline static char *getLevelArtworkDir(int type)
378 if (leveldir_current == NULL)
379 return UNDEFINED_FILENAME;
381 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
384 char *getTapeFilename(int nr)
386 static char *filename = NULL;
387 char basename[MAX_FILENAME_LEN];
389 checked_free(filename);
391 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
392 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
397 char *getSolutionTapeFilename(int nr)
399 static char *filename = NULL;
400 char basename[MAX_FILENAME_LEN];
402 checked_free(filename);
404 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
405 filename = getPath2(getSolutionTapeDir(), basename);
410 char *getScoreFilename(int nr)
412 static char *filename = NULL;
413 char basename[MAX_FILENAME_LEN];
415 checked_free(filename);
417 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
418 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
423 char *getSetupFilename()
425 static char *filename = NULL;
427 checked_free(filename);
429 filename = getPath2(getSetupDir(), SETUP_FILENAME);
434 char *getEditorSetupFilename()
436 static char *filename = NULL;
438 checked_free(filename);
439 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
441 if (fileExists(filename))
444 checked_free(filename);
445 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
450 char *getHelpAnimFilename()
452 static char *filename = NULL;
454 checked_free(filename);
456 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
461 char *getHelpTextFilename()
463 static char *filename = NULL;
465 checked_free(filename);
467 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
472 char *getLevelSetInfoFilename()
474 static char *filename = NULL;
489 for (i = 0; basenames[i] != NULL; i++)
491 checked_free(filename);
492 filename = getPath2(getCurrentLevelDir(), basenames[i]);
494 if (fileExists(filename))
501 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
503 static char *filename = NULL;
506 sprintf(basename, "%s_%d.txt",
507 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
509 checked_free(filename);
510 filename = getPath2(getCurrentLevelDir(), basename);
512 if (fileExists(filename))
518 static char *getCorrectedArtworkBasename(char *basename)
520 char *basename_corrected = basename;
522 #if defined(PLATFORM_MSDOS)
523 if (program.filename_prefix != NULL)
525 int prefix_len = strlen(program.filename_prefix);
527 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
528 basename_corrected = &basename[prefix_len];
530 /* if corrected filename is still longer than standard MS-DOS filename
531 size (8 characters + 1 dot + 3 characters file extension), shorten
532 filename by writing file extension after 8th basename character */
533 if (strlen(basename_corrected) > 8 + 1 + 3)
535 static char *msdos_filename = NULL;
537 checked_free(msdos_filename);
539 msdos_filename = getStringCopy(basename_corrected);
540 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
542 basename_corrected = msdos_filename;
547 return basename_corrected;
550 char *getCustomImageFilename(char *basename)
552 static char *filename = NULL;
553 boolean skip_setup_artwork = FALSE;
555 checked_free(filename);
557 basename = getCorrectedArtworkBasename(basename);
559 if (!setup.override_level_graphics)
561 /* 1st try: look for special artwork in current level series directory */
562 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
563 if (fileExists(filename))
568 /* check if there is special artwork configured in level series config */
569 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
571 /* 2nd try: look for special artwork configured in level series config */
572 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
573 if (fileExists(filename))
578 /* take missing artwork configured in level set config from default */
579 skip_setup_artwork = TRUE;
583 if (!skip_setup_artwork)
585 /* 3rd try: look for special artwork in configured artwork directory */
586 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
587 if (fileExists(filename))
593 /* 4th try: look for default artwork in new default artwork directory */
594 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
595 if (fileExists(filename))
600 /* 5th try: look for default artwork in old default artwork directory */
601 filename = getPath2(options.graphics_directory, basename);
602 if (fileExists(filename))
605 return NULL; /* cannot find specified artwork file anywhere */
608 char *getCustomSoundFilename(char *basename)
610 static char *filename = NULL;
611 boolean skip_setup_artwork = FALSE;
613 checked_free(filename);
615 basename = getCorrectedArtworkBasename(basename);
617 if (!setup.override_level_sounds)
619 /* 1st try: look for special artwork in current level series directory */
620 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
621 if (fileExists(filename))
626 /* check if there is special artwork configured in level series config */
627 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
629 /* 2nd try: look for special artwork configured in level series config */
630 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
631 if (fileExists(filename))
636 /* take missing artwork configured in level set config from default */
637 skip_setup_artwork = TRUE;
641 if (!skip_setup_artwork)
643 /* 3rd try: look for special artwork in configured artwork directory */
644 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
645 if (fileExists(filename))
651 /* 4th try: look for default artwork in new default artwork directory */
652 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
653 if (fileExists(filename))
658 /* 5th try: look for default artwork in old default artwork directory */
659 filename = getPath2(options.sounds_directory, basename);
660 if (fileExists(filename))
663 return NULL; /* cannot find specified artwork file anywhere */
666 char *getCustomMusicFilename(char *basename)
668 static char *filename = NULL;
669 boolean skip_setup_artwork = FALSE;
671 checked_free(filename);
673 basename = getCorrectedArtworkBasename(basename);
675 if (!setup.override_level_music)
677 /* 1st try: look for special artwork in current level series directory */
678 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
679 if (fileExists(filename))
684 /* check if there is special artwork configured in level series config */
685 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
687 /* 2nd try: look for special artwork configured in level series config */
688 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
689 if (fileExists(filename))
694 /* take missing artwork configured in level set config from default */
695 skip_setup_artwork = TRUE;
699 if (!skip_setup_artwork)
701 /* 3rd try: look for special artwork in configured artwork directory */
702 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
703 if (fileExists(filename))
709 /* 4th try: look for default artwork in new default artwork directory */
710 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
711 if (fileExists(filename))
716 /* 5th try: look for default artwork in old default artwork directory */
717 filename = getPath2(options.music_directory, basename);
718 if (fileExists(filename))
721 return NULL; /* cannot find specified artwork file anywhere */
724 char *getCustomArtworkFilename(char *basename, int type)
726 if (type == ARTWORK_TYPE_GRAPHICS)
727 return getCustomImageFilename(basename);
728 else if (type == ARTWORK_TYPE_SOUNDS)
729 return getCustomSoundFilename(basename);
730 else if (type == ARTWORK_TYPE_MUSIC)
731 return getCustomMusicFilename(basename);
733 return UNDEFINED_FILENAME;
736 char *getCustomArtworkConfigFilename(int type)
738 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
741 char *getCustomArtworkLevelConfigFilename(int type)
743 static char *filename = NULL;
745 checked_free(filename);
747 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
752 char *getCustomMusicDirectory(void)
754 static char *directory = NULL;
755 boolean skip_setup_artwork = FALSE;
757 checked_free(directory);
759 if (!setup.override_level_music)
761 /* 1st try: look for special artwork in current level series directory */
762 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
763 if (fileExists(directory))
768 /* check if there is special artwork configured in level series config */
769 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
771 /* 2nd try: look for special artwork configured in level series config */
772 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
773 if (fileExists(directory))
778 /* take missing artwork configured in level set config from default */
779 skip_setup_artwork = TRUE;
783 if (!skip_setup_artwork)
785 /* 3rd try: look for special artwork in configured artwork directory */
786 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
787 if (fileExists(directory))
793 /* 4th try: look for default artwork in new default artwork directory */
794 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
795 if (fileExists(directory))
800 /* 5th try: look for default artwork in old default artwork directory */
801 directory = getStringCopy(options.music_directory);
802 if (fileExists(directory))
805 return NULL; /* cannot find specified artwork file anywhere */
808 void InitTapeDirectory(char *level_subdir)
810 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
811 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
812 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
815 void InitScoreDirectory(char *level_subdir)
817 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
818 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
819 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
822 static void SaveUserLevelInfo();
824 void InitUserLevelDirectory(char *level_subdir)
826 if (!fileExists(getUserLevelDir(level_subdir)))
828 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
829 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
830 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
836 void InitLevelSetupDirectory(char *level_subdir)
838 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
839 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
840 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
843 void InitCacheDirectory()
845 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
846 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
850 /* ------------------------------------------------------------------------- */
851 /* some functions to handle lists of level and artwork directories */
852 /* ------------------------------------------------------------------------- */
854 TreeInfo *newTreeInfo()
856 return checked_calloc(sizeof(TreeInfo));
859 TreeInfo *newTreeInfo_setDefaults(int type)
861 TreeInfo *ti = newTreeInfo();
863 setTreeInfoToDefaults(ti, type);
868 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
870 node_new->next = *node_first;
871 *node_first = node_new;
874 int numTreeInfo(TreeInfo *node)
887 boolean validLevelSeries(TreeInfo *node)
889 return (node != NULL && !node->node_group && !node->parent_link);
892 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
897 if (node->node_group) /* enter level group (step down into tree) */
898 return getFirstValidTreeInfoEntry(node->node_group);
899 else if (node->parent_link) /* skip start entry of level group */
901 if (node->next) /* get first real level series entry */
902 return getFirstValidTreeInfoEntry(node->next);
903 else /* leave empty level group and go on */
904 return getFirstValidTreeInfoEntry(node->node_parent->next);
906 else /* this seems to be a regular level series */
910 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
915 if (node->node_parent == NULL) /* top level group */
916 return *node->node_top;
917 else /* sub level group */
918 return node->node_parent->node_group;
921 int numTreeInfoInGroup(TreeInfo *node)
923 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
926 int posTreeInfo(TreeInfo *node)
928 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
933 if (node_cmp == node)
937 node_cmp = node_cmp->next;
943 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
945 TreeInfo *node_default = node;
960 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
962 if (identifier == NULL)
967 if (node->node_group)
969 TreeInfo *node_group;
971 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
976 else if (!node->parent_link)
978 if (strEqual(identifier, node->identifier))
988 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
989 TreeInfo *node, boolean skip_sets_without_levels)
996 if (!node->parent_link && !node->level_group &&
997 skip_sets_without_levels && node->levels == 0)
998 return cloneTreeNode(node_top, node_parent, node->next,
999 skip_sets_without_levels);
1002 node_new = getTreeInfoCopy(node); /* copy complete node */
1004 node_new = newTreeInfo();
1006 *node_new = *node; /* copy complete node */
1009 node_new->node_top = node_top; /* correct top node link */
1010 node_new->node_parent = node_parent; /* correct parent node link */
1012 if (node->level_group)
1013 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1014 skip_sets_without_levels);
1016 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1017 skip_sets_without_levels);
1022 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1024 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1026 *ti_new = ti_cloned;
1029 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1031 boolean settings_changed = FALSE;
1035 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1036 !strEqual(node->graphics_set, node->graphics_set_ecs))
1038 setString(&node->graphics_set, node->graphics_set_ecs);
1039 settings_changed = TRUE;
1041 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1042 !strEqual(node->graphics_set, node->graphics_set_aga))
1044 setString(&node->graphics_set, node->graphics_set_aga);
1045 settings_changed = TRUE;
1048 if (node->node_group != NULL)
1049 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1054 return settings_changed;
1057 void dumpTreeInfo(TreeInfo *node, int depth)
1061 printf("Dumping TreeInfo:\n");
1065 for (i = 0; i < (depth + 1) * 3; i++)
1068 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1069 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1071 if (node->node_group != NULL)
1072 dumpTreeInfo(node->node_group, depth + 1);
1078 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1079 int (*compare_function)(const void *,
1082 int num_nodes = numTreeInfo(*node_first);
1083 TreeInfo **sort_array;
1084 TreeInfo *node = *node_first;
1090 /* allocate array for sorting structure pointers */
1091 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1093 /* writing structure pointers to sorting array */
1094 while (i < num_nodes && node) /* double boundary check... */
1096 sort_array[i] = node;
1102 /* sorting the structure pointers in the sorting array */
1103 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1106 /* update the linkage of list elements with the sorted node array */
1107 for (i = 0; i < num_nodes - 1; i++)
1108 sort_array[i]->next = sort_array[i + 1];
1109 sort_array[num_nodes - 1]->next = NULL;
1111 /* update the linkage of the main list anchor pointer */
1112 *node_first = sort_array[0];
1116 /* now recursively sort the level group structures */
1120 if (node->node_group != NULL)
1121 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1127 void sortTreeInfo(TreeInfo **node_first)
1129 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1133 /* ========================================================================= */
1134 /* some stuff from "files.c" */
1135 /* ========================================================================= */
1137 #if defined(PLATFORM_WIN32)
1139 #define S_IRGRP S_IRUSR
1142 #define S_IROTH S_IRUSR
1145 #define S_IWGRP S_IWUSR
1148 #define S_IWOTH S_IWUSR
1151 #define S_IXGRP S_IXUSR
1154 #define S_IXOTH S_IXUSR
1157 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1162 #endif /* PLATFORM_WIN32 */
1164 /* file permissions for newly written files */
1165 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1166 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1167 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1169 #define MODE_W_PRIVATE (S_IWUSR)
1170 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1171 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1173 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1174 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1176 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1177 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1181 static char *dir = NULL;
1183 #if defined(PLATFORM_WIN32)
1186 dir = checked_malloc(MAX_PATH + 1);
1188 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1191 #elif defined(PLATFORM_UNIX)
1194 if ((dir = getenv("HOME")) == NULL)
1198 if ((pwd = getpwuid(getuid())) != NULL)
1199 dir = getStringCopy(pwd->pw_dir);
1211 char *getCommonDataDir(void)
1213 static char *common_data_dir = NULL;
1215 #if defined(PLATFORM_WIN32)
1216 if (common_data_dir == NULL)
1218 char *dir = checked_malloc(MAX_PATH + 1);
1220 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1221 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1222 common_data_dir = getPath2(dir, program.userdata_subdir);
1224 common_data_dir = options.rw_base_directory;
1227 if (common_data_dir == NULL)
1228 common_data_dir = options.rw_base_directory;
1231 return common_data_dir;
1234 char *getPersonalDataDir(void)
1236 static char *personal_data_dir = NULL;
1238 #if defined(PLATFORM_MACOSX)
1239 if (personal_data_dir == NULL)
1240 personal_data_dir = getPath2(getHomeDir(), "Documents");
1242 if (personal_data_dir == NULL)
1243 personal_data_dir = getHomeDir();
1246 return personal_data_dir;
1249 char *getUserGameDataDir(void)
1251 static char *user_game_data_dir = NULL;
1253 if (user_game_data_dir == NULL)
1254 user_game_data_dir = getPath2(getPersonalDataDir(),
1255 program.userdata_subdir);
1257 return user_game_data_dir;
1260 void updateUserGameDataDir()
1262 #if defined(PLATFORM_MACOSX)
1263 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1264 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1266 /* convert old Unix style game data directory to Mac OS X style, if needed */
1267 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1269 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1271 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1272 userdata_dir_old, userdata_dir_new);
1274 /* continue using Unix style data directory -- this should not happen */
1275 program.userdata_path = getPath2(getPersonalDataDir(),
1276 program.userdata_subdir_unix);
1280 free(userdata_dir_old);
1286 return getUserGameDataDir();
1289 static mode_t posix_umask(mode_t mask)
1291 #if defined(PLATFORM_UNIX)
1298 static int posix_mkdir(const char *pathname, mode_t mode)
1300 #if defined(PLATFORM_WIN32)
1301 return mkdir(pathname);
1303 return mkdir(pathname, mode);
1307 void createDirectory(char *dir, char *text, int permission_class)
1309 /* leave "other" permissions in umask untouched, but ensure group parts
1310 of USERDATA_DIR_MODE are not masked */
1311 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1312 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1313 mode_t normal_umask = posix_umask(0);
1314 mode_t group_umask = ~(dir_mode & S_IRWXG);
1315 posix_umask(normal_umask & group_umask);
1317 if (!fileExists(dir))
1318 if (posix_mkdir(dir, dir_mode) != 0)
1319 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1321 posix_umask(normal_umask); /* reset normal umask */
1324 void InitUserDataDirectory()
1326 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1329 void SetFilePermissions(char *filename, int permission_class)
1331 chmod(filename, (permission_class == PERMS_PRIVATE ?
1332 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1335 char *getCookie(char *file_type)
1337 static char cookie[MAX_COOKIE_LEN + 1];
1339 if (strlen(program.cookie_prefix) + 1 +
1340 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1341 return "[COOKIE ERROR]"; /* should never happen */
1343 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1344 program.cookie_prefix, file_type,
1345 program.version_major, program.version_minor);
1350 int getFileVersionFromCookieString(const char *cookie)
1352 const char *ptr_cookie1, *ptr_cookie2;
1353 const char *pattern1 = "_FILE_VERSION_";
1354 const char *pattern2 = "?.?";
1355 const int len_cookie = strlen(cookie);
1356 const int len_pattern1 = strlen(pattern1);
1357 const int len_pattern2 = strlen(pattern2);
1358 const int len_pattern = len_pattern1 + len_pattern2;
1359 int version_major, version_minor;
1361 if (len_cookie <= len_pattern)
1364 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1365 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1367 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1370 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1371 ptr_cookie2[1] != '.' ||
1372 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1375 version_major = ptr_cookie2[0] - '0';
1376 version_minor = ptr_cookie2[2] - '0';
1378 return VERSION_IDENT(version_major, version_minor, 0, 0);
1381 boolean checkCookieString(const char *cookie, const char *template)
1383 const char *pattern = "_FILE_VERSION_?.?";
1384 const int len_cookie = strlen(cookie);
1385 const int len_template = strlen(template);
1386 const int len_pattern = strlen(pattern);
1388 if (len_cookie != len_template)
1391 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1397 /* ------------------------------------------------------------------------- */
1398 /* setup file list and hash handling functions */
1399 /* ------------------------------------------------------------------------- */
1401 char *getFormattedSetupEntry(char *token, char *value)
1404 static char entry[MAX_LINE_LEN];
1406 /* if value is an empty string, just return token without value */
1410 /* start with the token and some spaces to format output line */
1411 sprintf(entry, "%s:", token);
1412 for (i = strlen(entry); i < token_value_position; i++)
1415 /* continue with the token's value */
1416 strcat(entry, value);
1421 SetupFileList *newSetupFileList(char *token, char *value)
1423 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1425 new->token = getStringCopy(token);
1426 new->value = getStringCopy(value);
1433 void freeSetupFileList(SetupFileList *list)
1438 checked_free(list->token);
1439 checked_free(list->value);
1442 freeSetupFileList(list->next);
1447 char *getListEntry(SetupFileList *list, char *token)
1452 if (strEqual(list->token, token))
1455 return getListEntry(list->next, token);
1458 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1463 if (strEqual(list->token, token))
1465 checked_free(list->value);
1467 list->value = getStringCopy(value);
1471 else if (list->next == NULL)
1472 return (list->next = newSetupFileList(token, value));
1474 return setListEntry(list->next, token, value);
1477 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1482 if (list->next == NULL)
1483 return (list->next = newSetupFileList(token, value));
1485 return addListEntry(list->next, token, value);
1489 static void printSetupFileList(SetupFileList *list)
1494 printf("token: '%s'\n", list->token);
1495 printf("value: '%s'\n", list->value);
1497 printSetupFileList(list->next);
1502 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1503 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1504 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1505 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1507 #define insert_hash_entry hashtable_insert
1508 #define search_hash_entry hashtable_search
1509 #define change_hash_entry hashtable_change
1510 #define remove_hash_entry hashtable_remove
1513 static unsigned int get_hash_from_key(void *key)
1518 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1519 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1520 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1521 it works better than many other constants, prime or not) has never been
1522 adequately explained.
1524 If you just want to have a good hash function, and cannot wait, djb2
1525 is one of the best string hash functions i know. It has excellent
1526 distribution and speed on many different sets of keys and table sizes.
1527 You are not likely to do better with one of the "well known" functions
1528 such as PJW, K&R, etc.
1530 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1533 char *str = (char *)key;
1534 unsigned int hash = 5381;
1537 while ((c = *str++))
1538 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1543 static int keys_are_equal(void *key1, void *key2)
1545 return (strEqual((char *)key1, (char *)key2));
1548 SetupFileHash *newSetupFileHash()
1550 SetupFileHash *new_hash =
1551 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1553 if (new_hash == NULL)
1554 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1559 void freeSetupFileHash(SetupFileHash *hash)
1564 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1567 char *getHashEntry(SetupFileHash *hash, char *token)
1572 return search_hash_entry(hash, token);
1575 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1582 value_copy = getStringCopy(value);
1584 /* change value; if it does not exist, insert it as new */
1585 if (!change_hash_entry(hash, token, value_copy))
1586 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1587 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1590 char *removeHashEntry(SetupFileHash *hash, char *token)
1595 return remove_hash_entry(hash, token);
1599 static void printSetupFileHash(SetupFileHash *hash)
1601 BEGIN_HASH_ITERATION(hash, itr)
1603 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1604 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1606 END_HASH_ITERATION(hash, itr)
1610 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1611 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1613 static void loadSetupFileData(void *setup_file_data, char *filename,
1614 boolean top_recursion_level, boolean is_hash)
1616 static SetupFileHash *include_filename_hash = NULL;
1617 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1618 char *token, *value, *line_ptr;
1619 void *insert_ptr = NULL;
1620 boolean read_continued_line = FALSE;
1621 boolean token_value_separator_found;
1622 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1623 boolean token_value_separator_warning = FALSE;
1627 int token_count = 0;
1629 if (!(file = fopen(filename, MODE_READ)))
1631 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1636 /* use "insert pointer" to store list end for constant insertion complexity */
1638 insert_ptr = setup_file_data;
1640 /* on top invocation, create hash to mark included files (to prevent loops) */
1641 if (top_recursion_level)
1642 include_filename_hash = newSetupFileHash();
1644 /* mark this file as already included (to prevent including it again) */
1645 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1649 /* read next line of input file */
1650 if (!fgets(line, MAX_LINE_LEN, file))
1653 /* check if line was completely read and is terminated by line break */
1654 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1657 /* cut trailing line break (this can be newline and/or carriage return) */
1658 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1659 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1662 /* copy raw input line for later use (mainly debugging output) */
1663 strcpy(line_raw, line);
1665 if (read_continued_line)
1667 /* cut leading whitespaces from input line */
1668 for (line_ptr = line; *line_ptr; line_ptr++)
1669 if (*line_ptr != ' ' && *line_ptr != '\t')
1672 /* append new line to existing line, if there is enough space */
1673 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1674 strcat(previous_line, line_ptr);
1676 strcpy(line, previous_line); /* copy storage buffer to line */
1678 read_continued_line = FALSE;
1681 /* if the last character is '\', continue at next line */
1682 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1684 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1685 strcpy(previous_line, line); /* copy line to storage buffer */
1687 read_continued_line = TRUE;
1692 /* cut trailing comment from input line */
1693 for (line_ptr = line; *line_ptr; line_ptr++)
1695 if (*line_ptr == '#')
1702 /* cut trailing whitespaces from input line */
1703 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1704 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1707 /* ignore empty lines */
1711 /* cut leading whitespaces from token */
1712 for (token = line; *token; token++)
1713 if (*token != ' ' && *token != '\t')
1716 /* start with empty value as reliable default */
1719 token_value_separator_found = FALSE;
1721 /* find end of token to determine start of value */
1722 for (line_ptr = token; *line_ptr; line_ptr++)
1725 /* first look for an explicit token/value separator, like ':' or '=' */
1726 if (*line_ptr == ':' || *line_ptr == '=')
1728 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1731 *line_ptr = '\0'; /* terminate token string */
1732 value = line_ptr + 1; /* set beginning of value */
1734 token_value_separator_found = TRUE;
1740 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1741 /* fallback: if no token/value separator found, also allow whitespaces */
1742 if (!token_value_separator_found)
1744 for (line_ptr = token; *line_ptr; line_ptr++)
1746 if (*line_ptr == ' ' || *line_ptr == '\t')
1748 *line_ptr = '\0'; /* terminate token string */
1749 value = line_ptr + 1; /* set beginning of value */
1751 token_value_separator_found = TRUE;
1757 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1758 if (token_value_separator_found)
1760 if (!token_value_separator_warning)
1762 Error(ERR_INFO_LINE, "-");
1763 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1764 Error(ERR_INFO, "- config file: '%s'", filename);
1766 token_value_separator_warning = TRUE;
1769 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1775 /* cut trailing whitespaces from token */
1776 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1777 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1780 /* cut leading whitespaces from value */
1781 for (; *value; value++)
1782 if (*value != ' ' && *value != '\t')
1787 value = "true"; /* treat tokens without value as "true" */
1792 if (strEqual(token, "include"))
1794 if (getHashEntry(include_filename_hash, value) == NULL)
1796 char *basepath = getBasePath(filename);
1797 char *basename = getBaseName(value);
1798 char *filename_include = getPath2(basepath, basename);
1801 Error(ERR_INFO, "[including file '%s']", filename_include);
1804 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1808 free(filename_include);
1812 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1818 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1820 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1829 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1830 if (token_value_separator_warning)
1831 Error(ERR_INFO_LINE, "-");
1834 if (token_count == 0)
1835 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1837 if (top_recursion_level)
1838 freeSetupFileHash(include_filename_hash);
1841 void saveSetupFileHash(SetupFileHash *hash, char *filename)
1845 if (!(file = fopen(filename, MODE_WRITE)))
1847 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
1852 BEGIN_HASH_ITERATION(hash, itr)
1854 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
1855 HASH_ITERATION_VALUE(itr)));
1857 END_HASH_ITERATION(hash, itr)
1862 SetupFileList *loadSetupFileList(char *filename)
1864 SetupFileList *setup_file_list = newSetupFileList("", "");
1865 SetupFileList *first_valid_list_entry;
1867 loadSetupFileData(setup_file_list, filename, TRUE, FALSE);
1869 first_valid_list_entry = setup_file_list->next;
1871 /* free empty list header */
1872 setup_file_list->next = NULL;
1873 freeSetupFileList(setup_file_list);
1875 return first_valid_list_entry;
1878 SetupFileHash *loadSetupFileHash(char *filename)
1880 SetupFileHash *setup_file_hash = newSetupFileHash();
1882 loadSetupFileData(setup_file_hash, filename, TRUE, TRUE);
1884 return setup_file_hash;
1887 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1888 char *filename, char *identifier)
1890 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1893 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
1894 else if (!checkCookieString(value, identifier))
1895 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
1899 /* ========================================================================= */
1900 /* setup file stuff */
1901 /* ========================================================================= */
1903 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1904 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1905 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1907 /* level directory info */
1908 #define LEVELINFO_TOKEN_IDENTIFIER 0
1909 #define LEVELINFO_TOKEN_NAME 1
1910 #define LEVELINFO_TOKEN_NAME_SORTING 2
1911 #define LEVELINFO_TOKEN_AUTHOR 3
1912 #define LEVELINFO_TOKEN_YEAR 4
1913 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
1914 #define LEVELINFO_TOKEN_IMPORTED_BY 6
1915 #define LEVELINFO_TOKEN_TESTED_BY 7
1916 #define LEVELINFO_TOKEN_LEVELS 8
1917 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
1918 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
1919 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
1920 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
1921 #define LEVELINFO_TOKEN_READONLY 13
1922 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
1923 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
1924 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
1925 #define LEVELINFO_TOKEN_SOUNDS_SET 17
1926 #define LEVELINFO_TOKEN_MUSIC_SET 18
1927 #define LEVELINFO_TOKEN_FILENAME 19
1928 #define LEVELINFO_TOKEN_FILETYPE 20
1929 #define LEVELINFO_TOKEN_HANDICAP 21
1930 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
1932 #define NUM_LEVELINFO_TOKENS 23
1934 static LevelDirTree ldi;
1936 static struct TokenInfo levelinfo_tokens[] =
1938 /* level directory info */
1939 { TYPE_STRING, &ldi.identifier, "identifier" },
1940 { TYPE_STRING, &ldi.name, "name" },
1941 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1942 { TYPE_STRING, &ldi.author, "author" },
1943 { TYPE_STRING, &ldi.year, "year" },
1944 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1945 { TYPE_STRING, &ldi.imported_by, "imported_by" },
1946 { TYPE_STRING, &ldi.tested_by, "tested_by" },
1947 { TYPE_INTEGER, &ldi.levels, "levels" },
1948 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1949 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1950 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
1951 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1952 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1953 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
1954 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
1955 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1956 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1957 { TYPE_STRING, &ldi.music_set, "music_set" },
1958 { TYPE_STRING, &ldi.level_filename, "filename" },
1959 { TYPE_STRING, &ldi.level_filetype, "filetype" },
1960 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
1961 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
1964 static struct TokenInfo artworkinfo_tokens[] =
1966 /* artwork directory info */
1967 { TYPE_STRING, &ldi.identifier, "identifier" },
1968 { TYPE_STRING, &ldi.subdir, "subdir" },
1969 { TYPE_STRING, &ldi.name, "name" },
1970 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1971 { TYPE_STRING, &ldi.author, "author" },
1972 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1973 { TYPE_STRING, &ldi.basepath, "basepath" },
1974 { TYPE_STRING, &ldi.fullpath, "fullpath" },
1975 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
1976 { TYPE_INTEGER, &ldi.color, "color" },
1977 { TYPE_STRING, &ldi.class_desc, "class_desc" },
1982 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
1986 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1987 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1988 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1989 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1992 ti->node_parent = NULL;
1993 ti->node_group = NULL;
2000 ti->fullpath = NULL;
2001 ti->basepath = NULL;
2002 ti->identifier = NULL;
2003 ti->name = getStringCopy(ANONYMOUS_NAME);
2004 ti->name_sorting = NULL;
2005 ti->author = getStringCopy(ANONYMOUS_NAME);
2008 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2009 ti->latest_engine = FALSE; /* default: get from level */
2010 ti->parent_link = FALSE;
2011 ti->in_user_dir = FALSE;
2012 ti->user_defined = FALSE;
2014 ti->class_desc = NULL;
2016 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2018 if (ti->type == TREE_TYPE_LEVEL_DIR)
2020 ti->imported_from = NULL;
2021 ti->imported_by = NULL;
2022 ti->tested_by = NULL;
2024 ti->graphics_set_ecs = NULL;
2025 ti->graphics_set_aga = NULL;
2026 ti->graphics_set = NULL;
2027 ti->sounds_set = NULL;
2028 ti->music_set = NULL;
2029 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2030 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2031 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2033 ti->level_filename = NULL;
2034 ti->level_filetype = NULL;
2037 ti->first_level = 0;
2039 ti->level_group = FALSE;
2040 ti->handicap_level = 0;
2041 ti->readonly = TRUE;
2042 ti->handicap = TRUE;
2043 ti->skip_levels = FALSE;
2047 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2051 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2053 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2058 /* copy all values from the parent structure */
2060 ti->type = parent->type;
2062 ti->node_top = parent->node_top;
2063 ti->node_parent = parent;
2064 ti->node_group = NULL;
2071 ti->fullpath = NULL;
2072 ti->basepath = NULL;
2073 ti->identifier = NULL;
2074 ti->name = getStringCopy(ANONYMOUS_NAME);
2075 ti->name_sorting = NULL;
2076 ti->author = getStringCopy(parent->author);
2077 ti->year = getStringCopy(parent->year);
2079 ti->sort_priority = parent->sort_priority;
2080 ti->latest_engine = parent->latest_engine;
2081 ti->parent_link = FALSE;
2082 ti->in_user_dir = parent->in_user_dir;
2083 ti->user_defined = parent->user_defined;
2084 ti->color = parent->color;
2085 ti->class_desc = getStringCopy(parent->class_desc);
2087 ti->infotext = getStringCopy(parent->infotext);
2089 if (ti->type == TREE_TYPE_LEVEL_DIR)
2091 ti->imported_from = getStringCopy(parent->imported_from);
2092 ti->imported_by = getStringCopy(parent->imported_by);
2093 ti->tested_by = getStringCopy(parent->tested_by);
2095 ti->graphics_set_ecs = NULL;
2096 ti->graphics_set_aga = NULL;
2097 ti->graphics_set = NULL;
2098 ti->sounds_set = NULL;
2099 ti->music_set = NULL;
2100 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2101 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2102 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2104 ti->level_filename = NULL;
2105 ti->level_filetype = NULL;
2108 ti->first_level = 0;
2110 ti->level_group = FALSE;
2111 ti->handicap_level = 0;
2112 ti->readonly = TRUE;
2113 ti->handicap = TRUE;
2114 ti->skip_levels = FALSE;
2118 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2120 TreeInfo *ti_copy = newTreeInfo();
2122 /* copy all values from the original structure */
2124 ti_copy->type = ti->type;
2126 ti_copy->node_top = ti->node_top;
2127 ti_copy->node_parent = ti->node_parent;
2128 ti_copy->node_group = ti->node_group;
2129 ti_copy->next = ti->next;
2131 ti_copy->cl_first = ti->cl_first;
2132 ti_copy->cl_cursor = ti->cl_cursor;
2134 ti_copy->subdir = getStringCopy(ti->subdir);
2135 ti_copy->fullpath = getStringCopy(ti->fullpath);
2136 ti_copy->basepath = getStringCopy(ti->basepath);
2137 ti_copy->identifier = getStringCopy(ti->identifier);
2138 ti_copy->name = getStringCopy(ti->name);
2139 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2140 ti_copy->author = getStringCopy(ti->author);
2141 ti_copy->year = getStringCopy(ti->year);
2142 ti_copy->imported_from = getStringCopy(ti->imported_from);
2143 ti_copy->imported_by = getStringCopy(ti->imported_by);
2144 ti_copy->tested_by = getStringCopy(ti->tested_by);
2146 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2147 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2148 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2149 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2150 ti_copy->music_set = getStringCopy(ti->music_set);
2151 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2152 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2153 ti_copy->music_path = getStringCopy(ti->music_path);
2155 ti_copy->level_filename = getStringCopy(ti->level_filename);
2156 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2158 ti_copy->levels = ti->levels;
2159 ti_copy->first_level = ti->first_level;
2160 ti_copy->last_level = ti->last_level;
2161 ti_copy->sort_priority = ti->sort_priority;
2163 ti_copy->latest_engine = ti->latest_engine;
2165 ti_copy->level_group = ti->level_group;
2166 ti_copy->parent_link = ti->parent_link;
2167 ti_copy->in_user_dir = ti->in_user_dir;
2168 ti_copy->user_defined = ti->user_defined;
2169 ti_copy->readonly = ti->readonly;
2170 ti_copy->handicap = ti->handicap;
2171 ti_copy->skip_levels = ti->skip_levels;
2173 ti_copy->color = ti->color;
2174 ti_copy->class_desc = getStringCopy(ti->class_desc);
2175 ti_copy->handicap_level = ti->handicap_level;
2177 ti_copy->infotext = getStringCopy(ti->infotext);
2182 static void freeTreeInfo(TreeInfo *ti)
2187 checked_free(ti->subdir);
2188 checked_free(ti->fullpath);
2189 checked_free(ti->basepath);
2190 checked_free(ti->identifier);
2192 checked_free(ti->name);
2193 checked_free(ti->name_sorting);
2194 checked_free(ti->author);
2195 checked_free(ti->year);
2197 checked_free(ti->class_desc);
2199 checked_free(ti->infotext);
2201 if (ti->type == TREE_TYPE_LEVEL_DIR)
2203 checked_free(ti->imported_from);
2204 checked_free(ti->imported_by);
2205 checked_free(ti->tested_by);
2207 checked_free(ti->graphics_set_ecs);
2208 checked_free(ti->graphics_set_aga);
2209 checked_free(ti->graphics_set);
2210 checked_free(ti->sounds_set);
2211 checked_free(ti->music_set);
2213 checked_free(ti->graphics_path);
2214 checked_free(ti->sounds_path);
2215 checked_free(ti->music_path);
2217 checked_free(ti->level_filename);
2218 checked_free(ti->level_filetype);
2224 void setSetupInfo(struct TokenInfo *token_info,
2225 int token_nr, char *token_value)
2227 int token_type = token_info[token_nr].type;
2228 void *setup_value = token_info[token_nr].value;
2230 if (token_value == NULL)
2233 /* set setup field to corresponding token value */
2238 *(boolean *)setup_value = get_boolean_from_string(token_value);
2242 *(Key *)setup_value = getKeyFromKeyName(token_value);
2246 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2250 *(int *)setup_value = get_integer_from_string(token_value);
2254 checked_free(*(char **)setup_value);
2255 *(char **)setup_value = getStringCopy(token_value);
2263 static int compareTreeInfoEntries(const void *object1, const void *object2)
2265 const TreeInfo *entry1 = *((TreeInfo **)object1);
2266 const TreeInfo *entry2 = *((TreeInfo **)object2);
2267 int class_sorting1, class_sorting2;
2270 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2272 class_sorting1 = LEVELSORTING(entry1);
2273 class_sorting2 = LEVELSORTING(entry2);
2277 class_sorting1 = ARTWORKSORTING(entry1);
2278 class_sorting2 = ARTWORKSORTING(entry2);
2281 if (entry1->parent_link || entry2->parent_link)
2282 compare_result = (entry1->parent_link ? -1 : +1);
2283 else if (entry1->sort_priority == entry2->sort_priority)
2285 char *name1 = getStringToLower(entry1->name_sorting);
2286 char *name2 = getStringToLower(entry2->name_sorting);
2288 compare_result = strcmp(name1, name2);
2293 else if (class_sorting1 == class_sorting2)
2294 compare_result = entry1->sort_priority - entry2->sort_priority;
2296 compare_result = class_sorting1 - class_sorting2;
2298 return compare_result;
2301 static void createParentTreeInfoNode(TreeInfo *node_parent)
2305 if (node_parent == NULL)
2308 ti_new = newTreeInfo();
2309 setTreeInfoToDefaults(ti_new, node_parent->type);
2311 ti_new->node_parent = node_parent;
2312 ti_new->parent_link = TRUE;
2314 setString(&ti_new->identifier, node_parent->identifier);
2315 setString(&ti_new->name, ".. (parent directory)");
2316 setString(&ti_new->name_sorting, ti_new->name);
2318 setString(&ti_new->subdir, "..");
2319 setString(&ti_new->fullpath, node_parent->fullpath);
2321 ti_new->sort_priority = node_parent->sort_priority;
2322 ti_new->latest_engine = node_parent->latest_engine;
2324 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2326 pushTreeInfo(&node_parent->node_group, ti_new);
2330 /* -------------------------------------------------------------------------- */
2331 /* functions for handling level and custom artwork info cache */
2332 /* -------------------------------------------------------------------------- */
2334 static void LoadArtworkInfoCache()
2336 InitCacheDirectory();
2338 if (artworkinfo_cache_old == NULL)
2340 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2342 /* try to load artwork info hash from already existing cache file */
2343 artworkinfo_cache_old = loadSetupFileHash(filename);
2345 /* if no artwork info cache file was found, start with empty hash */
2346 if (artworkinfo_cache_old == NULL)
2347 artworkinfo_cache_old = newSetupFileHash();
2352 if (artworkinfo_cache_new == NULL)
2353 artworkinfo_cache_new = newSetupFileHash();
2356 static void SaveArtworkInfoCache()
2358 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2360 InitCacheDirectory();
2362 saveSetupFileHash(artworkinfo_cache_new, filename);
2367 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2369 static char *prefix = NULL;
2371 checked_free(prefix);
2373 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2378 /* (identical to above function, but separate string buffer needed -- nasty) */
2379 static char *getCacheToken(char *prefix, char *suffix)
2381 static char *token = NULL;
2383 checked_free(token);
2385 token = getStringCat2WithSeparator(prefix, suffix, ".");
2390 static char *getFileTimestamp(char *filename)
2392 struct stat file_status;
2394 if (stat(filename, &file_status) != 0) /* cannot stat file */
2395 return getStringCopy(i_to_a(0));
2397 return getStringCopy(i_to_a(file_status.st_mtime));
2400 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2402 struct stat file_status;
2404 if (timestamp_string == NULL)
2407 if (stat(filename, &file_status) != 0) /* cannot stat file */
2410 return (file_status.st_mtime != atoi(timestamp_string));
2413 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2415 char *identifier = level_node->subdir;
2416 char *type_string = ARTWORK_DIRECTORY(type);
2417 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2418 char *token_main = getCacheToken(token_prefix, "CACHED");
2419 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2420 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2421 TreeInfo *artwork_info = NULL;
2423 if (!use_artworkinfo_cache)
2430 artwork_info = newTreeInfo();
2431 setTreeInfoToDefaults(artwork_info, type);
2433 /* set all structure fields according to the token/value pairs */
2434 ldi = *artwork_info;
2435 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2437 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2438 char *value = getHashEntry(artworkinfo_cache_old, token);
2440 setSetupInfo(artworkinfo_tokens, i, value);
2442 /* check if cache entry for this item is invalid or incomplete */
2446 Error(ERR_WARN, "cache entry '%s' invalid", token);
2453 *artwork_info = ldi;
2458 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2459 LEVELINFO_FILENAME);
2460 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2461 ARTWORKINFO_FILENAME(type));
2463 /* check if corresponding "levelinfo.conf" file has changed */
2464 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2465 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2467 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2470 /* check if corresponding "<artworkinfo>.conf" file has changed */
2471 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2472 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2474 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2479 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2482 checked_free(filename_levelinfo);
2483 checked_free(filename_artworkinfo);
2486 if (!cached && artwork_info != NULL)
2488 freeTreeInfo(artwork_info);
2493 return artwork_info;
2496 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2497 LevelDirTree *level_node, int type)
2499 char *identifier = level_node->subdir;
2500 char *type_string = ARTWORK_DIRECTORY(type);
2501 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2502 char *token_main = getCacheToken(token_prefix, "CACHED");
2503 boolean set_cache_timestamps = TRUE;
2506 setHashEntry(artworkinfo_cache_new, token_main, "true");
2508 if (set_cache_timestamps)
2510 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2511 LEVELINFO_FILENAME);
2512 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2513 ARTWORKINFO_FILENAME(type));
2514 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2515 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2517 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2518 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2520 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2521 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2523 checked_free(filename_levelinfo);
2524 checked_free(filename_artworkinfo);
2525 checked_free(timestamp_levelinfo);
2526 checked_free(timestamp_artworkinfo);
2529 ldi = *artwork_info;
2530 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2532 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2533 char *value = getSetupValue(artworkinfo_tokens[i].type,
2534 artworkinfo_tokens[i].value);
2536 setHashEntry(artworkinfo_cache_new, token, value);
2541 /* -------------------------------------------------------------------------- */
2542 /* functions for loading level info and custom artwork info */
2543 /* -------------------------------------------------------------------------- */
2545 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2546 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2548 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2549 TreeInfo *node_parent,
2550 char *level_directory,
2551 char *directory_name)
2553 static unsigned long progress_delay = 0;
2554 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2555 char *directory_path = getPath2(level_directory, directory_name);
2556 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2557 SetupFileHash *setup_file_hash;
2558 LevelDirTree *leveldir_new = NULL;
2561 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2562 if (!options.debug && !fileExists(filename))
2564 free(directory_path);
2570 setup_file_hash = loadSetupFileHash(filename);
2572 if (setup_file_hash == NULL)
2574 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2576 free(directory_path);
2582 leveldir_new = newTreeInfo();
2585 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2587 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2589 leveldir_new->subdir = getStringCopy(directory_name);
2591 checkSetupFileHashIdentifier(setup_file_hash, filename,
2592 getCookie("LEVELINFO"));
2594 /* set all structure fields according to the token/value pairs */
2595 ldi = *leveldir_new;
2596 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2597 setSetupInfo(levelinfo_tokens, i,
2598 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2599 *leveldir_new = ldi;
2601 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2602 setString(&leveldir_new->name, leveldir_new->subdir);
2604 if (leveldir_new->identifier == NULL)
2605 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2607 if (leveldir_new->name_sorting == NULL)
2608 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2610 if (node_parent == NULL) /* top level group */
2612 leveldir_new->basepath = getStringCopy(level_directory);
2613 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2615 else /* sub level group */
2617 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2618 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2622 if (leveldir_new->levels < 1)
2623 leveldir_new->levels = 1;
2626 leveldir_new->last_level =
2627 leveldir_new->first_level + leveldir_new->levels - 1;
2629 leveldir_new->in_user_dir =
2630 (!strEqual(leveldir_new->basepath, options.level_directory));
2632 /* adjust some settings if user's private level directory was detected */
2633 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2634 leveldir_new->in_user_dir &&
2635 (strEqual(leveldir_new->subdir, getLoginName()) ||
2636 strEqual(leveldir_new->name, getLoginName()) ||
2637 strEqual(leveldir_new->author, getRealName())))
2639 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2640 leveldir_new->readonly = FALSE;
2643 leveldir_new->user_defined =
2644 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2646 leveldir_new->color = LEVELCOLOR(leveldir_new);
2648 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2650 leveldir_new->handicap_level = /* set handicap to default value */
2651 (leveldir_new->user_defined || !leveldir_new->handicap ?
2652 leveldir_new->last_level : leveldir_new->first_level);
2655 if (leveldir_new->level_group ||
2656 DelayReached(&progress_delay, progress_delay_value))
2657 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2659 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2663 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2665 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2667 /* skip level sets without levels (which are probably artwork base sets) */
2669 freeSetupFileHash(setup_file_hash);
2670 free(directory_path);
2678 pushTreeInfo(node_first, leveldir_new);
2680 freeSetupFileHash(setup_file_hash);
2682 if (leveldir_new->level_group)
2684 /* create node to link back to current level directory */
2685 createParentTreeInfoNode(leveldir_new);
2687 /* recursively step into sub-directory and look for more level series */
2688 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2689 leveldir_new, directory_path);
2692 free(directory_path);
2698 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2699 TreeInfo *node_parent,
2700 char *level_directory)
2703 struct dirent *dir_entry;
2704 boolean valid_entry_found = FALSE;
2706 if ((dir = opendir(level_directory)) == NULL)
2708 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2712 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2714 struct stat file_status;
2715 char *directory_name = dir_entry->d_name;
2716 char *directory_path = getPath2(level_directory, directory_name);
2718 /* skip entries for current and parent directory */
2719 if (strEqual(directory_name, ".") ||
2720 strEqual(directory_name, ".."))
2722 free(directory_path);
2726 /* find out if directory entry is itself a directory */
2727 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2728 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2730 free(directory_path);
2734 free(directory_path);
2736 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2737 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2738 strEqual(directory_name, MUSIC_DIRECTORY))
2741 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2748 /* special case: top level directory may directly contain "levelinfo.conf" */
2749 if (node_parent == NULL && !valid_entry_found)
2751 /* check if this directory directly contains a file "levelinfo.conf" */
2752 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2753 level_directory, ".");
2756 if (!valid_entry_found)
2757 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2761 boolean AdjustGraphicsForEMC()
2763 boolean settings_changed = FALSE;
2765 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2766 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2768 return settings_changed;
2771 void LoadLevelInfo()
2773 InitUserLevelDirectory(getLoginName());
2775 DrawInitText("Loading level series", 120, FC_GREEN);
2777 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2778 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2780 /* after loading all level set information, clone the level directory tree
2781 and remove all level sets without levels (these may still contain artwork
2782 to be offered in the setup menu as "custom artwork", and are therefore
2783 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2784 leveldir_first_all = leveldir_first;
2785 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2787 AdjustGraphicsForEMC();
2789 /* before sorting, the first entries will be from the user directory */
2790 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2792 if (leveldir_first == NULL)
2793 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2795 sortTreeInfo(&leveldir_first);
2798 dumpTreeInfo(leveldir_first, 0);
2802 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2803 TreeInfo *node_parent,
2804 char *base_directory,
2805 char *directory_name, int type)
2807 char *directory_path = getPath2(base_directory, directory_name);
2808 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2809 SetupFileHash *setup_file_hash = NULL;
2810 TreeInfo *artwork_new = NULL;
2813 if (fileExists(filename))
2814 setup_file_hash = loadSetupFileHash(filename);
2816 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2819 struct dirent *dir_entry;
2820 boolean valid_file_found = FALSE;
2822 if ((dir = opendir(directory_path)) != NULL)
2824 while ((dir_entry = readdir(dir)) != NULL)
2826 char *entry_name = dir_entry->d_name;
2828 if (FileIsArtworkType(entry_name, type))
2830 valid_file_found = TRUE;
2838 if (!valid_file_found)
2840 if (!strEqual(directory_name, "."))
2841 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2843 free(directory_path);
2850 artwork_new = newTreeInfo();
2853 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2855 setTreeInfoToDefaults(artwork_new, type);
2857 artwork_new->subdir = getStringCopy(directory_name);
2859 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2862 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2865 /* set all structure fields according to the token/value pairs */
2867 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2868 setSetupInfo(levelinfo_tokens, i,
2869 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2872 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2873 setString(&artwork_new->name, artwork_new->subdir);
2875 if (artwork_new->identifier == NULL)
2876 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2878 if (artwork_new->name_sorting == NULL)
2879 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2882 if (node_parent == NULL) /* top level group */
2884 artwork_new->basepath = getStringCopy(base_directory);
2885 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2887 else /* sub level group */
2889 artwork_new->basepath = getStringCopy(node_parent->basepath);
2890 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2893 artwork_new->in_user_dir =
2894 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2896 /* (may use ".sort_priority" from "setup_file_hash" above) */
2897 artwork_new->color = ARTWORKCOLOR(artwork_new);
2899 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2901 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2903 if (strEqual(artwork_new->subdir, "."))
2905 if (artwork_new->user_defined)
2907 setString(&artwork_new->identifier, "private");
2908 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2912 setString(&artwork_new->identifier, "classic");
2913 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2916 /* set to new values after changing ".sort_priority" */
2917 artwork_new->color = ARTWORKCOLOR(artwork_new);
2919 setString(&artwork_new->class_desc,
2920 getLevelClassDescription(artwork_new));
2924 setString(&artwork_new->identifier, artwork_new->subdir);
2927 setString(&artwork_new->name, artwork_new->identifier);
2928 setString(&artwork_new->name_sorting, artwork_new->name);
2932 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2935 pushTreeInfo(node_first, artwork_new);
2937 freeSetupFileHash(setup_file_hash);
2939 free(directory_path);
2945 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2946 TreeInfo *node_parent,
2947 char *base_directory, int type)
2950 struct dirent *dir_entry;
2951 boolean valid_entry_found = FALSE;
2953 if ((dir = opendir(base_directory)) == NULL)
2955 /* display error if directory is main "options.graphics_directory" etc. */
2956 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2957 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2962 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2964 struct stat file_status;
2965 char *directory_name = dir_entry->d_name;
2966 char *directory_path = getPath2(base_directory, directory_name);
2968 /* skip directory entries for current and parent directory */
2969 if (strEqual(directory_name, ".") ||
2970 strEqual(directory_name, ".."))
2972 free(directory_path);
2976 /* skip directory entries which are not a directory or are not accessible */
2977 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2978 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2980 free(directory_path);
2984 free(directory_path);
2986 /* check if this directory contains artwork with or without config file */
2987 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2989 directory_name, type);
2994 /* check if this directory directly contains artwork itself */
2995 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2996 base_directory, ".",
2998 if (!valid_entry_found)
2999 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3003 static TreeInfo *getDummyArtworkInfo(int type)
3005 /* this is only needed when there is completely no artwork available */
3006 TreeInfo *artwork_new = newTreeInfo();
3008 setTreeInfoToDefaults(artwork_new, type);
3010 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3011 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3012 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3014 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3015 setString(&artwork_new->name, UNDEFINED_FILENAME);
3016 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3021 void LoadArtworkInfo()
3023 LoadArtworkInfoCache();
3025 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3027 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3028 options.graphics_directory,
3029 TREE_TYPE_GRAPHICS_DIR);
3030 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3031 getUserGraphicsDir(),
3032 TREE_TYPE_GRAPHICS_DIR);
3034 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3035 options.sounds_directory,
3036 TREE_TYPE_SOUNDS_DIR);
3037 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3039 TREE_TYPE_SOUNDS_DIR);
3041 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3042 options.music_directory,
3043 TREE_TYPE_MUSIC_DIR);
3044 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3046 TREE_TYPE_MUSIC_DIR);
3048 if (artwork.gfx_first == NULL)
3049 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3050 if (artwork.snd_first == NULL)
3051 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3052 if (artwork.mus_first == NULL)
3053 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3055 /* before sorting, the first entries will be from the user directory */
3056 artwork.gfx_current =
3057 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3058 if (artwork.gfx_current == NULL)
3059 artwork.gfx_current =
3060 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3061 if (artwork.gfx_current == NULL)
3062 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3064 artwork.snd_current =
3065 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3066 if (artwork.snd_current == NULL)
3067 artwork.snd_current =
3068 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3069 if (artwork.snd_current == NULL)
3070 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3072 artwork.mus_current =
3073 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3074 if (artwork.mus_current == NULL)
3075 artwork.mus_current =
3076 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3077 if (artwork.mus_current == NULL)
3078 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3080 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3081 artwork.snd_current_identifier = artwork.snd_current->identifier;
3082 artwork.mus_current_identifier = artwork.mus_current->identifier;
3085 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3086 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3087 printf("music set == %s\n\n", artwork.mus_current_identifier);
3090 sortTreeInfo(&artwork.gfx_first);
3091 sortTreeInfo(&artwork.snd_first);
3092 sortTreeInfo(&artwork.mus_first);
3095 dumpTreeInfo(artwork.gfx_first, 0);
3096 dumpTreeInfo(artwork.snd_first, 0);
3097 dumpTreeInfo(artwork.mus_first, 0);
3101 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3102 LevelDirTree *level_node)
3104 static unsigned long progress_delay = 0;
3105 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3106 int type = (*artwork_node)->type;
3108 /* recursively check all level directories for artwork sub-directories */
3112 /* check all tree entries for artwork, but skip parent link entries */
3113 if (!level_node->parent_link)
3115 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3116 boolean cached = (artwork_new != NULL);
3120 pushTreeInfo(artwork_node, artwork_new);
3124 TreeInfo *topnode_last = *artwork_node;
3125 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3126 ARTWORK_DIRECTORY(type));
3128 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3130 if (topnode_last != *artwork_node) /* check for newly added node */
3132 artwork_new = *artwork_node;
3134 setString(&artwork_new->identifier, level_node->subdir);
3135 setString(&artwork_new->name, level_node->name);
3136 setString(&artwork_new->name_sorting, level_node->name_sorting);
3138 artwork_new->sort_priority = level_node->sort_priority;
3139 artwork_new->color = LEVELCOLOR(artwork_new);
3145 /* insert artwork info (from old cache or filesystem) into new cache */
3146 if (artwork_new != NULL)
3147 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3151 if (level_node->level_group ||
3152 DelayReached(&progress_delay, progress_delay_value))
3153 DrawInitText(level_node->name, 150, FC_YELLOW);
3156 if (level_node->node_group != NULL)
3157 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3159 level_node = level_node->next;
3163 void LoadLevelArtworkInfo()
3165 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3167 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3168 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3169 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3171 SaveArtworkInfoCache();
3173 /* needed for reloading level artwork not known at ealier stage */
3175 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3177 artwork.gfx_current =
3178 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3179 if (artwork.gfx_current == NULL)
3180 artwork.gfx_current =
3181 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3182 if (artwork.gfx_current == NULL)
3183 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3186 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3188 artwork.snd_current =
3189 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3190 if (artwork.snd_current == NULL)
3191 artwork.snd_current =
3192 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3193 if (artwork.snd_current == NULL)
3194 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3197 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3199 artwork.mus_current =
3200 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3201 if (artwork.mus_current == NULL)
3202 artwork.mus_current =
3203 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3204 if (artwork.mus_current == NULL)
3205 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3208 sortTreeInfo(&artwork.gfx_first);
3209 sortTreeInfo(&artwork.snd_first);
3210 sortTreeInfo(&artwork.mus_first);
3213 dumpTreeInfo(artwork.gfx_first, 0);
3214 dumpTreeInfo(artwork.snd_first, 0);
3215 dumpTreeInfo(artwork.mus_first, 0);
3219 static void SaveUserLevelInfo()
3221 LevelDirTree *level_info;
3226 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3228 if (!(file = fopen(filename, MODE_WRITE)))
3230 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3235 level_info = newTreeInfo();
3237 /* always start with reliable default values */
3238 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3240 setString(&level_info->name, getLoginName());
3241 setString(&level_info->author, getRealName());
3242 level_info->levels = 100;
3243 level_info->first_level = 1;
3245 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3247 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3248 getCookie("LEVELINFO")));
3251 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3253 if (i == LEVELINFO_TOKEN_NAME ||
3254 i == LEVELINFO_TOKEN_AUTHOR ||
3255 i == LEVELINFO_TOKEN_LEVELS ||
3256 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3257 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3259 /* just to make things nicer :) */
3260 if (i == LEVELINFO_TOKEN_AUTHOR)
3261 fprintf(file, "\n");
3264 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3268 SetFilePermissions(filename, PERMS_PRIVATE);
3270 freeTreeInfo(level_info);
3274 char *getSetupValue(int type, void *value)
3276 static char value_string[MAX_LINE_LEN];
3284 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3288 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3292 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3296 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3300 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3304 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3308 sprintf(value_string, "%d", *(int *)value);
3312 if (*(char **)value == NULL)
3315 strcpy(value_string, *(char **)value);
3319 value_string[0] = '\0';
3323 if (type & TYPE_GHOSTED)
3324 strcpy(value_string, "n/a");
3326 return value_string;
3329 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3333 static char token_string[MAX_LINE_LEN];
3334 int token_type = token_info[token_nr].type;
3335 void *setup_value = token_info[token_nr].value;
3336 char *token_text = token_info[token_nr].text;
3337 char *value_string = getSetupValue(token_type, setup_value);
3339 /* build complete token string */
3340 sprintf(token_string, "%s%s", prefix, token_text);
3342 /* build setup entry line */
3343 line = getFormattedSetupEntry(token_string, value_string);
3345 if (token_type == TYPE_KEY_X11)
3347 Key key = *(Key *)setup_value;
3348 char *keyname = getKeyNameFromKey(key);
3350 /* add comment, if useful */
3351 if (!strEqual(keyname, "(undefined)") &&
3352 !strEqual(keyname, "(unknown)"))
3354 /* add at least one whitespace */
3356 for (i = strlen(line); i < token_comment_position; i++)
3360 strcat(line, keyname);
3367 void LoadLevelSetup_LastSeries()
3369 /* ----------------------------------------------------------------------- */
3370 /* ~/.<program>/levelsetup.conf */
3371 /* ----------------------------------------------------------------------- */
3373 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3374 SetupFileHash *level_setup_hash = NULL;
3376 /* always start with reliable default values */
3377 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3379 if ((level_setup_hash = loadSetupFileHash(filename)))
3381 char *last_level_series =
3382 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3384 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3386 if (leveldir_current == NULL)
3387 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3389 checkSetupFileHashIdentifier(level_setup_hash, filename,
3390 getCookie("LEVELSETUP"));
3392 freeSetupFileHash(level_setup_hash);
3395 Error(ERR_WARN, "using default setup values");
3400 void SaveLevelSetup_LastSeries()
3402 /* ----------------------------------------------------------------------- */
3403 /* ~/.<program>/levelsetup.conf */
3404 /* ----------------------------------------------------------------------- */
3406 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3407 char *level_subdir = leveldir_current->subdir;
3410 InitUserDataDirectory();
3412 if (!(file = fopen(filename, MODE_WRITE)))
3414 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3419 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3420 getCookie("LEVELSETUP")));
3421 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3426 SetFilePermissions(filename, PERMS_PRIVATE);
3431 static void checkSeriesInfo()
3433 static char *level_directory = NULL;
3435 struct dirent *dir_entry;
3437 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3439 level_directory = getPath2((leveldir_current->in_user_dir ?
3440 getUserLevelDir(NULL) :
3441 options.level_directory),
3442 leveldir_current->fullpath);
3444 if ((dir = opendir(level_directory)) == NULL)
3446 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3450 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3452 if (strlen(dir_entry->d_name) > 4 &&
3453 dir_entry->d_name[3] == '.' &&
3454 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3456 char levelnum_str[4];
3459 strncpy(levelnum_str, dir_entry->d_name, 3);
3460 levelnum_str[3] = '\0';
3462 levelnum_value = atoi(levelnum_str);
3465 if (levelnum_value < leveldir_current->first_level)
3467 Error(ERR_WARN, "additional level %d found", levelnum_value);
3468 leveldir_current->first_level = levelnum_value;
3470 else if (levelnum_value > leveldir_current->last_level)
3472 Error(ERR_WARN, "additional level %d found", levelnum_value);
3473 leveldir_current->last_level = levelnum_value;
3482 void LoadLevelSetup_SeriesInfo()
3485 SetupFileHash *level_setup_hash = NULL;
3486 char *level_subdir = leveldir_current->subdir;
3488 /* always start with reliable default values */
3489 level_nr = leveldir_current->first_level;
3491 checkSeriesInfo(leveldir_current);
3493 /* ----------------------------------------------------------------------- */
3494 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3495 /* ----------------------------------------------------------------------- */
3497 level_subdir = leveldir_current->subdir;
3499 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3501 if ((level_setup_hash = loadSetupFileHash(filename)))
3505 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3509 level_nr = atoi(token_value);
3511 if (level_nr < leveldir_current->first_level)
3512 level_nr = leveldir_current->first_level;
3513 if (level_nr > leveldir_current->last_level)
3514 level_nr = leveldir_current->last_level;
3517 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3521 int level_nr = atoi(token_value);
3523 if (level_nr < leveldir_current->first_level)
3524 level_nr = leveldir_current->first_level;
3525 if (level_nr > leveldir_current->last_level + 1)
3526 level_nr = leveldir_current->last_level;
3528 if (leveldir_current->user_defined || !leveldir_current->handicap)
3529 level_nr = leveldir_current->last_level;
3531 leveldir_current->handicap_level = level_nr;
3534 checkSetupFileHashIdentifier(level_setup_hash, filename,
3535 getCookie("LEVELSETUP"));
3537 freeSetupFileHash(level_setup_hash);
3540 Error(ERR_WARN, "using default setup values");
3545 void SaveLevelSetup_SeriesInfo()
3548 char *level_subdir = leveldir_current->subdir;
3549 char *level_nr_str = int2str(level_nr, 0);
3550 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3553 /* ----------------------------------------------------------------------- */
3554 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3555 /* ----------------------------------------------------------------------- */
3557 InitLevelSetupDirectory(level_subdir);
3559 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3561 if (!(file = fopen(filename, MODE_WRITE)))
3563 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3568 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3569 getCookie("LEVELSETUP")));
3570 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3572 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3573 handicap_level_str));
3577 SetFilePermissions(filename, PERMS_PRIVATE);